diff --git a/.gitignore b/.gitignore index 601e434200..a45279fb21 100644 --- a/.gitignore +++ b/.gitignore @@ -25,16 +25,6 @@ local.properties # PDT-specific .buildpath -########### -## Symlinks -########### - -*-sym.* -*-symdir/ -[Ee]xternal[Ff]iles/ -/src/Plugins/*-sym -/src/Presentation/SmartStore.Web/Themes/*-sym - ########################################################### ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. @@ -43,23 +33,48 @@ local.properties # User-specific files *.suo *.user +*.userosscache *.sln.docstates -# Build results +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs +# Build results [Dd]ebug/ +[Dd]ebugPublic/ [Rr]elease/ +[Rr]eleases/ x64/ -# build/ +x86/ +bld/ [Bb]in/ [Oo]bj/ +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + *_i.c *_p.c +*_i.h *.ilk *.meta *.obj @@ -79,6 +94,7 @@ x64/ *.vssscc .builds *.pidb +*.svclog *.scc # Visual C++ cache files @@ -93,6 +109,7 @@ ipch/ *.psess *.vsp *.vspx +*.sap # Guidance Automation Toolkit *.gpState @@ -108,8 +125,16 @@ _TeamCity* *.dotCover # NCrunch -*.ncrunch* +_NCrunch_* .*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ # Installshield output folder [Ee]xpress/ @@ -128,31 +153,58 @@ DocProject/Help/html publish/ # Publish Web Output -*.Publish.xml +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted *.pubxml - -# NuGet Packages Directory -## TODO: If you have NuGet Package Restore enabled, uncomment the next line -/src/packages +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets # Windows Azure Build Output csx *.build.csdef +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + # Windows Store app package directory AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ # Others # sql/ -*.Cache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl -*.[Pp]ublish.xml +*.dbproj.schemaview *.pfx *.publishsettings +node_modules/ +orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ @@ -165,12 +217,42 @@ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files -App_Data/*.mdf -App_Data/*.ldf +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ -################### -## Windows detritus -################### +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ # Windows image file caches Thumbs.db @@ -185,6 +267,21 @@ $RECYCLE.BIN/ # Mac crap .DS_Store + +########### +## Symlinks +########### + +*-sym.* +*-symdir/ +[Ee]xternal[Ff]iles/ +/src/Plugins/*-sym +/src/Presentation/SmartStore.Web/Themes/*-sym + + +########### +## Specific +########### # Backups [Bb]ackup/ [Bb]ackups/ @@ -194,22 +291,27 @@ _[Bb]ackups/ *.orig Kopie von* -########### -## Specific -########### /build /src/Presentation/SmartStore.Web/Plugins /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/Settings.txt /src/Presentation/SmartStore.Web/App_Data/InstalledPlugins.txt -/src/Presentation/SmartStore.Web/App_Data/Licensing.key +/src/Presentation/SmartStore.Web/App_Data/Licenses.lic /src/Presentation/SmartStore.Web/Media # Import/Export /src/Presentation/SmartStore.Web/Content/files/[Ee]xport[Ii]mport/*.* +/src/Presentation/SmartStore.Web/[Ee]xchange/* # Media /src/Presentation/SmartStore.Web/Media/[Tt]humbs /src/Presentation/SmartStore.Web/Media/[Uu]ploaded +# Tools +/src/Tools/SmartStore.WebApi.Client/packages +/src/Tools/SmartStore.WebApi.Client/SmartStore.WebApi.Client.sln + src/SmartStoreNET.Packager.sln +Log.txt diff --git a/CREDITS.txt b/CREDITS.txt index f03fe54186..88b2d0a6be 100644 --- a/CREDITS.txt +++ b/CREDITS.txt @@ -13,7 +13,7 @@ copyright notices, and licenses. 51Degrees --------------------------------------------- -WebSite: http://http://51degrees.mobi/ +WebSite: http://51degrees.mobi/ Copyright: Copyright 2010 - 2013 51Degrees.mobi Limited License: Mozilla Public License 2.0 (MPL-2.0) @@ -44,7 +44,7 @@ License: MIT AutoMapper --------------------------------------------- -WebSite: http://http://www.automapper.org +WebSite: http://www.automapper.org Copyright: Copyright 2008-2013 Jimmy Bogard and other contributors License: MIT @@ -58,7 +58,7 @@ License: Apache License 2.0 (Apache) DotNetOpenAuth --------------------------------------------- -Website: http://http://dotnetopenauth.net/ +Website: http://dotnetopenauth.net/ Copyright: Copyright (c) 2011, Outercurve Foundation License: MS-PL @@ -145,12 +145,6 @@ Copyright: Copyright (c) 2007 James Newton-King License: MIT -LESS CSS for .NET ---------------------------------------------- -Website: http://www.dotlesscss.org/ -License: Apache License v2.0 - - GeoLite Free by MaxMind --------------------------------------------- Website: http://www.maxmind.com diff --git a/README.md b/README.md index 3ff934079c..a7a83aee3a 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,16 @@ SmartStore.NET is a free, open source, full-featured e-commerce solution for com **SmartStore.NET includes all essential features to create multilingual and multi-currency stores** targeting desktop or mobile devices and enabling SEO optimized rich product catalogs with support for an unlimited number of products and categories, variants, bundles, datasheets, ESD, discounts, coupons and many more. -A comprehensive set of tools for CRM & CMS, sales, marketing, payment & shipping handling among others makes SmartStore.NET a powerful all-in-one solution fulfilling all your needs... totally FREE! +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 delivers a beautiful and configurable shop frontend 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 to your buyers. +**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. 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/net](http://www.smartstore.com/net) +* **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) ## Highlights @@ -26,7 +26,7 @@ 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 frontend and backend. +* 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 @@ -54,18 +54,18 @@ The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC * and many more... ## Project Status -SmartStore.NET V2.1 has been released on November 24. 2014. The highlights are: - -* Drastically **simplified plugin development, debugging & deployment** -* **Theme inheritance**: create derived child themes with minimum effort by overriding only small parts (static files and even variables) -* New backend design and cleaner frontend theme -* More comfortable catalog management & product editing -* New payment plugins `Pay with Amazon` & `Accarda-Kar` -* **Preview Mode**: virtually test themes and shops more easily -* **Simplified checkout** process -* **New Image Gallery** component in public product details page -* Support for **hierarchical SEO slugs**, e.g.: samsung/galaxy/s5/16gb/white -* Support for new EU VAT regulation for digital goods (2008/8/EG directive) +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 + ##Try it online diff --git a/SmartStoreNET.Tasks.Targets b/SmartStoreNET.Tasks.Targets index a5f8c21e04..f184eaa7eb 100644 --- a/SmartStoreNET.Tasks.Targets +++ b/SmartStoreNET.Tasks.Targets @@ -2,7 +2,7 @@ - + @@ -44,7 +44,7 @@ x86 $(BUILD_NUMBER) - 2.1.1 + 2.6.0 $(StageFolder) .$(Version) diff --git a/build.bat b/build.bat index e017b455af..b58175809a 100644 --- a/build.bat +++ b/build.bat @@ -1,21 +1,24 @@ -set MSBuildPath=%windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe +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" -@IF NOT EXIST %MSBuildPath% @ECHO COULDN'T FIND MSBUILD: %MSBuildPath% (Is .NET 4 installed?) -ELSE GOTO END + "%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" + ) do ( + if exist %%b ( + call %%b x86 + goto build + ) +) + +echo "Unable to detect suitable environment. Build may not succeed." -:CheckOS -IF EXIST "%PROGRAMFILES(X86)%" (GOTO 64BIT) ELSE (GOTO 32BIT) +:build -:64BIT -echo 64-bit... -set MSBuildPath="%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild.exe" -GOTO END - -:32BIT -echo 32-bit... -set MSBuildPath="%ProgramFiles%\MSBuild\12.0\Bin\MSBuild.exe" -GOTO END - -:END - -%MSBuildPath% SmartStoreNET.proj /p:DebugSymbols=false /p:DebugType=None /P:SlnName=SmartStoreNET /maxcpucount %* \ No newline at end of file +msbuild SmartStoreNET.proj /p:DebugSymbols=false /p:DebugType=None /P:SlnName=SmartStoreNET /maxcpucount %* \ No newline at end of file diff --git a/changelog.md b/changelog.md index 9344a3b6a0..0dac88a9fd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,46 +1,334 @@ # Release Notes -## SmartStore.NET 2.2 +## SmartStore.NET 2.6 + +### Highlights +* Major improvements in Importer & Exporter: better field mapping, higher performance, bug fixes etc. +* 'PayPal PLUS' payment plugin +* 'paydirekt' payment plugin +* 'Viveum' payment plugin +* 'BeezUP' export provider +* (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. + +### New Features +* #961 Fix "Open Redirection Vulnerability" +* #571 Option to display another checkbox on confirm page to let the customer accept that his email address can be handed over to a third party +* #870 Implement address import for customers (both billing & shipping address) +* #886 Add setting to hide manufacturer images on product detail page and to hide default image for manufacturers +* Import localized SEO names for product and categories +* #477 Implement option to specify the number of exported and imported pictures +* #859 Make checkout attributes suitable for multi-stores +* Product details: Select attribute and gift card values by query string parameters +* #950 make contact form comply with current German law + +### Improvements +* Major improvements in Importer: better field mapping, higher performance, bug fixes etc. +* (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. +* Localization & SEO: language switcher now takes language specific SEO slugs into account when building links +* Smarter import of plugin resource files with graceful fallbacks (de-DE > de > de-* > en-US > en > en-* > *) +* (Perf) Faster language resource file import +* Exports the product detail link including the attribute query string when exporting attribute combinations +* #918 Compare products: Display base price information +* Export email attachments needs to be stored in database because the temp file may not exist anymore when sending the email +* #913 Use HTML5 Input types (tel, email) +* Added paging to frontend order list +* Added paging to backend checkout attribute list +* #977 Show PAngV base/delivery amount also +* Updated LiveEngage LiveChat plugin + +### Bugfixes +* TaskScheduler could fail polling when primary store url is an external IP address +* Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage). +* "The provider failed at the Execute method: Member 'CurrentValues' cannot be called for the entity of type 'Product'" when exporting product attribute combinations +* Bundles without selected attributes could throw an exception on product detail page +* GMC feed did not export the product type and Billiger did not export shop_cat (category path) +* The error message of a payment provider when capturing a payment was not displayed +* Adding new shipping method threw an exception +* Attribute Values: Assigning IsPreselected to more than one value causes an error +* BizImporter: fixed redirection bug when default file extension in .biz wasn't .html +* Fixed: Export deployment emails were always send manually +* Manually notifying gift card recipient threw an exception +* Loading shipping by weight grid failed under SQL CE +* #949 Import: ProcessSlugs does not process explicitly specified "SeName", if product name did not change in an update scenario +* Customer import: Creates customer role duplicates for inserted customers +* GMC feed does not generate the sale price if the sale price is set for a future date +* Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child" +* Download nor sample download were removable when editing product +* Copied product must not share sample download of source product. Could produce "The DELETE statement conflicted with the REFERENCE constraint 'FK_dbo.Product_dbo.Download_SampleDownloadId'". +* #921 Specification attribute options with single quotation marks are causing a Javascript error +* #971 Product is added to cart automatically if it has a non-required file upload attribute +* #973 Bundle item upload is nowhere linked +* Base price in product list ignored PriceDisplayType (catalog settings) and possibly displayed the wrong base price info +* Private messages: Fixes "No route in the route table matches the supplied values" +* Payone: Hash string incorrect for frontend API payments where the order has more than 9 products +* Export mail notification: Download link not working if SSL is enabled +* Discount rule has spent amount including sub total option can cause wrong discount calculation if the cart contains a product several times +* #986 File uploads possible through /content/filemanager/index.html + + +## SmartStore.NET 2.5 + +### Highlights + * 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 + +### New Features +* New export and import framework +* Import of customer and category data +* #141 Payment and shipping methods by customer roles +* #67 Restrict payment methods to countries +* #94 Restrict payment methods to shipping methods +* #747 Restrict payment methods by old versus new customer (plugin) +* #584 Email attachment support for message templates +* Attach order invoice PDF automatically to order notification emails +* #526 Min/Max amount option for which the payment method should be offered during checkout +* (Dev) New _SyncMapping_ service: enables easier entity synchronization with external systems +* (Dev) #792 ViewEngine: Enable vbhtml views per configuration +* (Dev) Plugin developers can now render child actions into a dynamically created special tab called 'Plugins' +* #718 ShopConnector: Import option for "Published" and "Disable buy\wishlist button" +* #702 Facebook and Twitter external authentication suitable for multi-stores +* New scheduled task: Clear e-mail queue +* New scheduled task: Clear uploadeded transient media files +* #704 Make primary store currency suitable for multi-stores +* #727 Web-API: Option to deactivate TimestampOlderThanLastRequest validation +* #731 Web-API: Allow deletion and inserting of product category and manufacturer assignments +* #733 Option to set a display order for homepage products +* #607 HTML capable full description for payment methods displayed in checkout +* #732 Product list: Option to display the pre-selected price instead of the lowest price +* New payment provider for Offline Payment Plugin: Purchase Order Number +* #202 Implement option for product list 'default sort order' +* #360 Import & export product variant combinations +* #722 System > SEO Names: Implement editing of an UrlRecord +* Admin > System > System Info shows used application memory (RAM) +* Added option to make VATIN mandatory during customer registration +* #840 Activity log: Have option to exclude search engine activity +* #841 Activity log for deleting an order +* More settings to control creation of SEO names +* GMC feed: Supporting fields multipack, bundle, adult, energy efficiency class and custom label (0 to 4) +* #760 Setting to set a customer role for new registered users +* #800 Multi-store: Option to display all orders of all stores for customer in frontend +* #457 Added option to hide the default image for categories and products +* #451 Add message token for product shipping surcharge +* #436 Make %Order.Product(s)% token to link the product detail page and a add product thumbnail +* #339 Meta robots setting for page indexing of search engines +* PayPal: Option for API security protocol +* Product filter: Option to sort filter results by their display order rather than by number of matches +* Elmar Shopinfo: Option to export delivery time as availability +* #654 Place user agreement for downloadable files in checkout process +* #398 EU law: add 'revocation' form and revocation waiver for ESD +* #738 Implement download of pictures via URLs in product import +* Web-API: Bridge to import framework: uploading import files to import profile directory +* Setting to round down calculated reward points +* #695 Implement checkbox in checkout to let customers subscribe to newsletters +* #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) 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 +* TaskScheduler: Supports cron expressions to define task execution frequency +* TaskScheduler: Editing tasks does not require app restart anymore +* TaskScheduler: Enhanced UI +* #721 Message Queue: implemented "Delete all" +* #725 Prevent LowestProductPrice being 0 +* #709 News feed produced invalid RSS feed. Added content:encoded. Added maximum news age setting for feed export. +* #735 Include SKUs of attribute combinations when filtering the backend product list +* Filter for homepage and published\unpublished products in backend product list +* Reduce database round trips initiated by price calculation +* Google Analytics: added support for mobile devices +* (Dev) TaskScheduler: Tasks can propagate progress info (percentage & message) +* (Dev) TaskScheduler: Cancellation request is sent to tasks on app shutdown +* ShippingByWeight & ShippingByTotal: Support for multiple zip ranges (comma separated) +* Two more options to handle customer numbers: display customer number in frontend & let customers enter their customer number if it's still empty +* #62 free shipping info on product detail page +* Display base price in CompactProductBox +* Automatically redirect to referrer after login +* #826 Image gallery: the viewport height was fixed to 300 px, but now respects MediaSettings > ImageSize. +* #249 Make UI editor for 'SeoSettings.ExtraRobotsDisallows' +* Debitoor: Customer VAT number not transmitted anymore because it appears on the Debitoor invoice. +* #778 Web-API: Increase MaxExpansionDepth for using expand pathes +* #767 Remove assignments to a grouped product if the grouped product is deleted +* #773 Reduce number of guest records created by search engine requests +* #791 Preselected attributes or attribute combinations should always be appended as querystring to product page links +* Simplified handling of SEO names +* URLs are not converted to lower case anymore +* Product grid sortable by name, price and created on +* #26 Display company or name in order list +* Added inline editing of country grid +* #790 Improved language editing +* #843 Implement a product picker +* #850 Use new product picker for selecting required products +* Trusted Shops: badge will be displayed in mobile themes, payment info link replaced compare list link in footer +* Product filter: Specification attributes are sorted by display order rather than alphabetically by name +* #856 Don't route topics which are excluded from sitemap +* #851 Replace reCAPTCHA with "I'm not a robot" CAPTCHA +* #713 Display gift card remaining amount in frontend order details and order messages +* #736 Render PayPal Express Button in minibasket +* PayPal: Support for partial refunds +* Offline credit card payment: Option to exclude credit card types + +### Bugfixes +* #523 Redirecting to payment provider performed by core instead of plugin +* Preselected price was wrong for product attributes with multiple preselected values (same on product detail page) +* #749 Visual Studio 2015 compilation error: CS0009: Metadata file. SmartStore.Licensing.dll could not be opened -- Illegal tables in compressed metadata stream +* PayPal Express: fixed capture method +* #770 Resizing browser with product details page causes product image to disappear +* GMC feed: Availability value "available for order" deprecated +* Mobile: Shopping cart warnings weren't displayed to customers +* Tax provider and payment method were automatically activated when there were no active provider\method +* #784 Biz-Importer: Name of delivery time must not be imported empty +* #776 Preview: Manufacturer and Product in Multi Store +* #755 Some methods still loading all products in one go +* #796 Selected specification in product filter mask is displayed with default language (not localized) +* #805 Product filter is reset if 'product sorting' or 'view mode' or 'amount of displayed products per page' is changed +* Hide link to a topic page if it is limited to stores +* #829 Activity log: Searching by customer email out of function +* Product import: Store mappings were not applied when inserting new records +* Faulty permission handling in ajax grid actions (no message, infinite loading icon) +* Grouped products: Display order was not correct +* Deletion of a customer could delete all newsletter subscriptions +* PayPal: Fixed "The request was aborted: Could not create SSL/TLS secure channel." +* PayPal Express: Void and refund out of function ("The transaction id is not valid") +* Customer could not delete his avatar +* Facebook authentication: Email missing in verification +* Attribute with a product linkage throws exception if added to cart +* Number of products per product tag could be incorrect in a multi-store + + +## SmartStore.NET 2.2.2 + +### New Features +* SmartStore.NET User Guide +* #210 Implement multi-store support for import/export +* Added zip code to shipping by weight computation method +* Skrill payment plugin (distributed via Marketplace) +* (Dev) DevTool plugin: added option to display all widget zones in public store +* New options for manufacturer display on the homepage +* Added optional customer number field + +### Improvements +* (Perf) several minor optimizations for faster app startup and page rendering +* UI: optimized image gallery widget (white background & nicer animations) + enhanced modal dialog fade animations +* (Soft) deletion of SEO slug supporting entities now also deletes the corresponding url records +* License checker now supports IDN mapping for domain names +* #716 Supporting of paged google-product data query for SQL-Server Compact Edition +* #648 Add hint for * at mandatory form fields at address creation +* Added link to imprint and disclaimer to footer in mobile theme +* #521 Display bonus points in order export +* Updated GMC taxonomy files +* MsieJsEngine now is the default LESS script engine + +### Bugfixes +* #694 Product variant attribute in product page should not be preselected implicitly +* Fixed: If currencies are limited to one for a multi-store, this currency should dominate the setting for the primary store currency +* #563 Scheduled Tasks: ensure that 'LastEndUtc' is ALWAYS set +* Topics grid: fixed 'maxJsonLength exceeded' error +* Debitoor: Fixed "The property named 'lines.0.productOrService' should be defined" +* Send currency code of primary store currency (not of working currency) to payment gateway +* #691 Product quantity not added to cart on mobile theme +* #186 Mobile: variant images do not refresh +* #671 Bundle products: display base price according to applied discount +* #619 Display base price according to applied tier price +* #726 PAngV: basket displays wrong base price when attribute price adjustment has been set +* Weight adjustment of attributes weren't applied in shopping cart overview +* Shipping by weight calculates wrong surcharge if attribute combination prices are set +* Don't let database hooks call other hooks. +* There was no payment redirect if only one payment method is available in checkout + + +## SmartStore.NET 2.2.1 + +### New Features +* #582 GMC feed: Option to include\exclude a product +* #652 Web API: Support for file upload and multipart mime +* New option to display product thumbnails in instant search (NOTE: the feature can be enabled via _Configuration > Settings > Catalog Settings > Search_) +* #594 Option to prevent a message from being automatically sent +* Category filtering by store for backend category tree and list +* #554 Flag to indicate that a new payment notification (IPN) arrived for an order since last opening\editing of the order +* Setting whether and how to display sub-categories on a category page (hide, above products, bottom of page) +* Debitoor: Extend option "Book invoice if paid" to also (optionally) mail the invoice as a PDF +* #508 Limit country settings to stores +* #205 Add multistore support for forums + +### Improvements +* PDF converter: first init is much faster now +* Localized properties for forums and forum groups + +### Bugfixes +* PDF converter: fixed _QFont::setPixelSize_ bug +* Fixed System.NullReferenceException at admin/order/pdfpackagingslips +* #660 Don't show shipping cost hint in product detail page if product has free shipping + +## SmartStore.NET 2.2.0 ### New Features -* New mobile theme: MobileLight (a light variant of the default mobile theme) +* New mobile theme: _MobileLight_ (a light variant of the default mobile theme) * Localization: in a multi-language environment missing language resources fall back to default language's resources (instead of returning the ugly resource key) -* #428 Implement category option to override global list view type +* Quantity unit management +* #428 New option to override global category list view type +* #672 New option to automatically create mutual associations for cross sell and related products * #485 Enable shop admin to change creation date of a blog entry * #258 Implement email validation in checkout -* Quantity unit management -* Option to determine the maximum amount of filter items -* Option to determine whether all filter groups should be displayed expanded -* #459 New field to determine tag for page titles on widget level +* New option to specify the maximum number of displayed filter items +* New option to specify whether all filter groups should be displayed expanded +* #459 New field to specify html tag for page titles on widget level * Added _shrink database_ to backend UI +* #588 Alternative localized category name used as title for category pages +* #592 Add a second category description displayed beyond products on category page +* Promotion feeds now export the preselected price (including price adjustments and overrides) +* Implement 'free shipping threshold' for feed creation +* _Billiger_ feed: Implement new field for shipping costs to austria * (Developer) Added `BeginTransaction()` and `UseTransaction()` methods to `IDbContext` ### Improvements +* (Perf) product list rendering up to 10x (!) faster now (depends on page size and view mode) +* The data grid in the backend now preserves client state between requests (page, pageSize, sorting, filtering etc.) +* Excel Import & Export: much lower memory consumption and increased performance * Moving pictures from DB to FS or vice versa is lightning fast now, consumes much lower memory and is encapsulated in a transaction which ensures reliable rollback after failure. Plus the database gets automatically shrinked after moving to FS. * Feed plugins: product query now paged to reduce memory payload -* Null DeliveryTimeId when deleting products. Otherwise deleted products can prevent deletion of delivery times. +* #589 Backend product list: add filter for products with "no category mapping" and "no manufacturer mapping" +* Minor UI tweaks in checkout process * Payone: CC-Check via client API, not via Server API (requires PCI certification) * #189 Allow deletion of multiple reviews * #622 UI: Redesign table in Sales > Orders > Order > Tab Products * #625 Bundles can be ordered if an attribute combination of a bundle item is not available -* Minor UI tweaks in checkout process +* #666 Export addresses in customer export +* New shopping cart setting ShowItemsFromWishlistToCartButton +* XML product export now is paged and more data gets exported +* #560 Return requests: Add fields for last update, last update of requested action and general notes +* #626 Add `IsActive` flag for checkout attributes ### Bugfixes -* Amazon payments: Declined authorization IPN did not void the payment status +* Instant search box did not display all results when SQL Fulltext Search was enabled +* Print order as PDF redirected to login although the admin already was logged in +* #621 PDF Order: does not take overridden attribute combination price into account (in order line) +* _Amazon payments_: Declined authorization IPN did not void the payment status * Fixed „Payment method couldn't be loaded“ when order amount is zero * #598 Wrong input parameter name for ReturnRequestSubmit * #557 Localize MVC validation strings * Fixed rare bug "The length of the string exceeds the value set on the maxJsonLength property" (Controller: Order, Action: OrderNotesSelect) -* Debitoor: Adding order notes can result in infinite order update event loop with thousands of order notes +* _Debitoor_: Adding order notes can result in infinite order update event loop with thousands of order notes * Tax rates persisted on order item level to avoid rounding issues (required for Debitoor, Accarda and Payone) -* Print order as pdf redirected to login although the admin already was logged in -* #621 PDF Order: does not take overridden attribute combination price into account (in order line) * Hide additional shipping surcharge when display prices permission is not granted * Fixed "Adding a relationship with an entity which is in the Deleted state is not allowed" when adding bundles to cart * Fixed price calculation of multiple bundles issue * Fixed auto add required products for bundle items -* Fixes #641: Protocol in sitemap should be HTTPS when _ForceSslForAllPages_ is true - +* Fixes #641: Protocol in sitemap should be HTTPS when `ForceSslForAllPages` is true +* #640 Do not display shipping infos for grouped products in product lists +* #634 Bundle price in list should not differ from price in details if there is only one bundle item with one attribute +* Do not copy associated product when copying a bundle product +* Null DeliveryTimeId when deleting products. Otherwise deleted products can prevent deletion of delivery times. +* Fixed: Product with inactive attribute combination could be moved to wishlist, copied to cart and be purchased +* Mobile Theme: Show prices according to selected variant value, display of deliverytime +* Bundles: Display base price according to Catalog Setting > Product Detail > Display Base Price ## SmartStore.NET 2.1.1 diff --git a/lib/SmartStore.Licensing/SmartStore.Licensing.dll b/lib/SmartStore.Licensing/SmartStore.Licensing.dll new file mode 100644 index 0000000000..8acdf39c24 Binary files /dev/null and b/lib/SmartStore.Licensing/SmartStore.Licensing.dll differ diff --git a/src/AssemblySharedInfo.cs b/src/AssemblySharedInfo.cs index 8fd027386d..79225de4c7 100644 --- a/src/AssemblySharedInfo.cs +++ b/src/AssemblySharedInfo.cs @@ -1,6 +1,4 @@ using System.Reflection; -using System.Runtime.InteropServices; -using System.Security; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information @@ -9,6 +7,6 @@ [assembly: AssemblyDescription("SmartStore.NET")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("SmartStore AG")] -[assembly: AssemblyCopyright("Copyright © SmartStore AG 2014")] +[assembly: AssemblyCopyright("Copyright © SmartStore AG 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/AssemblyVersionInfo.cs b/src/AssemblyVersionInfo.cs index c1406de818..f74f0edf78 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.1.0.0")] +[assembly: AssemblyVersion("2.6.0.0")] -[assembly: AssemblyFileVersion("2.1.1.0")] -[assembly: AssemblyInformationalVersion("2.1.1.0")] +[assembly: AssemblyFileVersion("2.6.0.0")] +[assembly: AssemblyInformationalVersion("2.6.0.0")] diff --git a/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs b/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs index 06733ed1ed..b02ea8dd58 100644 --- a/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs +++ b/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs @@ -1,89 +1,372 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.Web.Hosting; using Autofac; using SmartStore.Core.Infrastructure; -using SmartStore.Core.Infrastructure.DependencyManagement; namespace SmartStore.Core.Async { public static class AsyncRunner { - private static readonly TaskFactory _myTaskFactory; + private static readonly BackgroundWorkHost _host = new BackgroundWorkHost(); - static AsyncRunner() + /// + /// Gets the global cancellation token which signals the application shutdown + /// + public static CancellationToken AppShutdownCancellationToken { - _myTaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default); + get { return _host.ShutdownCancellationTokenSource.Token; } } + /// + /// Executes an async Task method which has a void return value synchronously + /// + /// Task method to execute public static void RunSync(Func func) { - _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult(); + var oldContext = SynchronizationContext.Current; + var synch = new ExclusiveSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(synch); + synch.Post(async _ => + { + try + { + await func(); + } + catch (Exception e) + { + synch.InnerException = e; + throw; + } + finally + { + synch.EndMessageLoop(); + } + }, 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) { - return _myTaskFactory.StartNew>(func).Unwrap().GetAwaiter().GetResult(); + var oldContext = SynchronizationContext.Current; + var synch = new ExclusiveSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(synch); + TResult ret = default(TResult); + synch.Post(async _ => + { + try + { + ret = await func(); + } + catch (Exception e) + { + synch.InnerException = e; + throw; + } + finally + { + synch.EndMessageLoop(); + } + }, null); + synch.BeginMessageLoop(); + SynchronizationContext.SetSynchronizationContext(oldContext); + return ret; } - public static Task Run(Action action) + public static Task Run(Action action) { return Run(action, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); } - public static Task Run(Action action, CancellationToken cancellationToken) + public static Task Run(Action action, CancellationToken cancellationToken) { return Run(action, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default); } - public static Task Run(Action action, TaskCreationOptions options) + public static Task Run(Action action, TaskCreationOptions options) { return Run(action, CancellationToken.None, options, TaskScheduler.Default); } - public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options) + public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options) { return Run(action, cancellationToken, options, TaskScheduler.Default); } - public static Task Run(Action action, TaskScheduler scheduler) + public static Task Run(Action action, TaskScheduler scheduler) { return Run(action, CancellationToken.None, TaskCreationOptions.None, scheduler); } - - public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler) + + public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler) { Guard.ArgumentNotNull(() => action); Guard.ArgumentNotNull(() => scheduler); + var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; + options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool) + var t = Task.Factory.StartNew(() => { - using (var container = EngineContext.Current.ContainerManager.Container.BeginLifetimeScope(AutofacLifetimeScopeProvider.HttpRequestTag)) + var accessor = EngineContext.Current.ContainerManager.ScopeAccessor; + using (accessor.BeginContextAwareScope()) { - action(container); + action(accessor.GetLifetimeScope(null), ct); } - }, cancellationToken, options, scheduler); + }, ct, options, scheduler); + + _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, TaskCreationOptions options, TaskScheduler scheduler) { Guard.ArgumentNotNull(() => state); Guard.ArgumentNotNull(() => action); Guard.ArgumentNotNull(() => scheduler); + var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; + options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool) + + var t = Task.Factory.StartNew((o) => + { + var accessor = EngineContext.Current.ContainerManager.ScopeAccessor; + using (accessor.BeginContextAwareScope()) + { + action(accessor.GetLifetimeScope(null), ct, o); + } + }, state, ct, options, scheduler); + + _host.Register(t, ct); + + return t; + } + + + + public static Task Run(Func function) + { + return Run(function, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); + } + + public static Task Run(Func function, CancellationToken cancellationToken) + { + return Run(function, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default); + } + + public static Task Run(Func function, TaskCreationOptions options) + { + return Run(function, CancellationToken.None, options, TaskScheduler.Default); + } + + public static Task Run(Func function, CancellationToken cancellationToken, TaskCreationOptions options) + { + return Run(function, cancellationToken, options, TaskScheduler.Default); + } + + public static Task Run(Func function, TaskScheduler scheduler) + { + return Run(function, CancellationToken.None, TaskCreationOptions.None, scheduler); + } + + public static Task Run(Func function, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler) + { + Guard.ArgumentNotNull(() => function); + Guard.ArgumentNotNull(() => scheduler); + + var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; + options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool) + + var t = Task.Factory.StartNew(() => + { + var accessor = EngineContext.Current.ContainerManager.ScopeAccessor; + using (accessor.BeginContextAwareScope()) + { + return function(accessor.GetLifetimeScope(null), ct); + } + }, ct, options, scheduler); + + _host.Register(t, ct); + + return t; + } + + public static Task Run(Func function, object state, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler) + { + Guard.ArgumentNotNull(() => state); + Guard.ArgumentNotNull(() => function); + Guard.ArgumentNotNull(() => scheduler); + + var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; + options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool) + var t = Task.Factory.StartNew((o) => { - using (var container = EngineContext.Current.ContainerManager.Container.BeginLifetimeScope(AutofacLifetimeScopeProvider.HttpRequestTag)) + var accessor = EngineContext.Current.ContainerManager.ScopeAccessor; + using (accessor.BeginContextAwareScope()) { - action(container, o); + return function(accessor.GetLifetimeScope(null), ct, o); } - }, state, cancellationToken, options, scheduler); + }, state, ct, options, scheduler); + + _host.Register(t, ct); return t; } + + private class ExclusiveSynchronizationContext : SynchronizationContext + { + private bool _done; + readonly AutoResetEvent _workItemsWaiting = new AutoResetEvent(false); + readonly Queue> _items = new Queue>(); + + public Exception InnerException { get; set; } + + public override void Send(SendOrPostCallback d, object state) + { + throw new NotSupportedException("We cannot send to the same thread"); + } + + public override void Post(SendOrPostCallback d, object state) + { + lock (_items) + { + _items.Enqueue(Tuple.Create(d, state)); + } + _workItemsWaiting.Set(); + } + + public void EndMessageLoop() + { + Post(_ => _done = true, null); + } + + public void BeginMessageLoop() + { + while (!_done) + { + Tuple task = null; + lock (_items) + { + if (_items.Count > 0) + { + task = _items.Dequeue(); + } + } + if (task != null) + { + task.Item1(task.Item2); + if (InnerException != null) // the method threw an exeption + { + throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException); + } + } + else + { + _workItemsWaiting.WaitOne(); + } + } + } + + public override SynchronizationContext CreateCopy() + { + return this; + } + } + + } + + internal class BackgroundWorkHost : IRegisteredObject + { + private readonly CancellationTokenSource _shutdownCancellationTokenSource = new CancellationTokenSource(); + private int _numRunningWorkItems; + + public BackgroundWorkHost() + { + HostingEnvironment.RegisterObject(this); + } + + public CancellationTokenSource ShutdownCancellationTokenSource + { + get { return _shutdownCancellationTokenSource; } + } + + public void Stop(bool immediate) + { + int num; + lock (this) + { + _shutdownCancellationTokenSource.Cancel(); + num = _numRunningWorkItems; + } + if (num == 0) + { + FinalShutdown(); + } + } + + public CancellationTokenSource CreateCompositeCancellationTokenSource(CancellationToken userCancellationToken) + { + if (userCancellationToken == CancellationToken.None) + { + return _shutdownCancellationTokenSource; + } + return CancellationTokenSource.CreateLinkedTokenSource(_shutdownCancellationTokenSource.Token, userCancellationToken); + } + + public void Register(Task work, CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) + { + lock (this) + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + _numRunningWorkItems++; + } + + work.ContinueWith( + WorkItemComplete, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + } + + private void WorkItemComplete(Task work) + { + int num; + bool isCancellationRequested; + lock (this) + { + num = --_numRunningWorkItems; + isCancellationRequested = _shutdownCancellationTokenSource.IsCancellationRequested; + } + if (num == 0 && isCancellationRequested) + { + FinalShutdown(); + } + } + + private void FinalShutdown() + { + HostingEnvironment.UnregisterObject(this); + } + } } diff --git a/src/Libraries/SmartStore.Core/Async/AsyncState.cs b/src/Libraries/SmartStore.Core/Async/AsyncState.cs index 2a4fb4a1d9..ce99542612 100644 --- a/src/Libraries/SmartStore.Core/Async/AsyncState.cs +++ b/src/Libraries/SmartStore.Core/Async/AsyncState.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Runtime.Caching; -using System.Text; using System.Threading; namespace SmartStore.Core.Async @@ -11,8 +8,8 @@ namespace SmartStore.Core.Async public class AsyncState { - private readonly static AsyncState s_instance = new AsyncState(); - private readonly MemoryCache _cache = MemoryCache.Default; + private static readonly AsyncState s_instance = new AsyncState(); + private readonly MemoryCache _cache = new MemoryCache("SmartStore.AsyncState"); private AsyncState() { @@ -35,6 +32,22 @@ public T Get(string name = null) 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; @@ -46,13 +59,13 @@ private T Get(out CancellationTokenSource cancelTokenSource, string name = nu { cancelTokenSource = null; var key = BuildKey(name); - + var value = _cache.Get(key) as StateInfo; if (value != null) { cancelTokenSource = value.CancellationTokenSource; - return (T)(value.Progress); + return (T)value.Progress; } return default(T); @@ -60,14 +73,32 @@ private T Get(out CancellationTokenSource cancelTokenSource, string name = nu public void Set(T state, string name = null, bool neverExpires = false) { - this.Set(state, null, name, neverExpires); + 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); - this.Set(default(T), cancelTokenSource, name); + Set(default(T), cancelTokenSource, name); } private void Set(T state, CancellationTokenSource cancelTokenSource, string name = null, bool neverExpires = false) @@ -79,7 +110,10 @@ private void Set(T state, CancellationTokenSource cancelTokenSource, string n if (value != null) { // exists already, so update - value.Progress = state; + if (state != null) + { + value.Progress = state; + } if (cancelTokenSource != null && value.CancellationTokenSource == null) { value.CancellationTokenSource = cancelTokenSource; @@ -88,7 +122,10 @@ private void Set(T state, CancellationTokenSource cancelTokenSource, string n var policy = new CacheItemPolicy { SlidingExpiration = neverExpires ? TimeSpan.Zero : TimeSpan.FromMinutes(15) }; - _cache.Set(key, value ?? new StateInfo { Progress = state, CancellationTokenSource = cancelTokenSource }, policy); + _cache.Set( + key, + value ?? new StateInfo { Progress = state, CancellationTokenSource = cancelTokenSource }, + policy); } public bool Remove(string name = null) diff --git a/src/Libraries/SmartStore.Core/BaseEntity.cs b/src/Libraries/SmartStore.Core/BaseEntity.cs index 9ff517f329..0c90dc9a9a 100644 --- a/src/Libraries/SmartStore.Core/BaseEntity.cs +++ b/src/Libraries/SmartStore.Core/BaseEntity.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.Serialization; @@ -12,8 +13,7 @@ namespace SmartStore.Core /// [DataContract] public abstract partial class BaseEntity - { - + { /// /// Gets or sets the entity identifier /// @@ -21,7 +21,8 @@ public abstract partial class BaseEntity [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } - public Type GetUnproxiedType() + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public Type GetUnproxiedType() { var t = GetType(); if (t.AssemblyQualifiedName.StartsWith("System.Data.Entity.")) @@ -36,9 +37,9 @@ public Type GetUnproxiedType() /// 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 IsTransient + public virtual bool IsTransientRecord() { - get { return Id == 0; } + return Id == 0; } public override bool Equals(object obj) @@ -57,16 +58,17 @@ protected virtual bool Equals(BaseEntity other) if (HasSameNonDefaultIds(other)) { var otherType = other.GetUnproxiedType(); - var thisType = this.GetUnproxiedType(); + var thisType = GetUnproxiedType(); return thisType.Equals(otherType); } return false; } - public override int GetHashCode() + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] + public override int GetHashCode() { - if (this.IsTransient) + if (IsTransientRecord()) { return base.GetHashCode(); } @@ -77,7 +79,7 @@ public override int GetHashCode() // It's possible for two objects to return the same hash code based on // identically valued properties, even if they're of two different types, // so we include the object's type in the hash calculation - int hashCode = GetUnproxiedType().GetHashCode(); + var hashCode = GetUnproxiedType().GetHashCode(); return (hashCode * 31) ^ Id.GetHashCode(); } } @@ -95,7 +97,7 @@ public override int GetHashCode() private bool HasSameNonDefaultIds(BaseEntity other) { - return !this.IsTransient && !other.IsTransient && this.Id == other.Id; + return !this.IsTransientRecord() && !other.IsTransientRecord() && this.Id == other.Id; } } } diff --git a/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs b/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs index 94c65017c3..4af855729a 100644 --- a/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs @@ -4,20 +4,13 @@ using System.Linq; using System.Web; using System.Web.Caching; -using SmartStore.Core.Fakes; namespace SmartStore.Core.Caching { public partial class AspNetCache : ICache { - private const string REGION_NAME = "$$SmartStoreNET$$"; - - // AspNetCache object does not have a ContainsKey() method: - // Therefore we 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 FAKE_NULL = "__[NULL]__"; + private const string RegionName = "$$SmartStoreNET$$"; public IEnumerable> Entries { @@ -28,9 +21,9 @@ public IEnumerable> Entries return from entry in HttpRuntime.Cache.Cast() let key = entry.Key.ToString() - where key.StartsWith(REGION_NAME) + where key.StartsWith(RegionName) select new KeyValuePair( - key.Substring(REGION_NAME.Length), + key.Substring(RegionName.Length), entry.Value); } } @@ -40,12 +33,7 @@ public object Get(string key) if (HttpRuntime.Cache == null) return null; - var value = HttpRuntime.Cache.Get(BuildKey(key)); - - if (value.Equals(FAKE_NULL)) - return null; - - return value; + return HttpRuntime.Cache.Get(BuildKey(key)); } public void Set(string key, object value, int? cacheTime) @@ -56,12 +44,13 @@ public void Set(string key, object value, int? cacheTime) key = BuildKey(key); var absoluteExpiration = Cache.NoAbsoluteExpiration; - if (cacheTime.GetValueOrDefault() > 0) + if (cacheTime.HasValue) { + var span = cacheTime.Value == 0 ? TimeSpan.FromMilliseconds(10) : TimeSpan.FromMinutes(cacheTime.Value); absoluteExpiration = DateTime.UtcNow + TimeSpan.FromMinutes(cacheTime.Value); } - HttpRuntime.Cache.Insert(key, value ?? FAKE_NULL, null, absoluteExpiration, Cache.NoSlidingExpiration); + HttpRuntime.Cache.Insert(key, value, null, absoluteExpiration, Cache.NoSlidingExpiration); } public bool Contains(string key) @@ -82,7 +71,7 @@ public void Remove(string key) public static string BuildKey(string key) { - return key.HasValue() ? REGION_NAME + key : null; + return key.HasValue() ? RegionName + key : null; } public bool IsSingleton diff --git a/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs b/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs index bdf35da722..6949aec542 100644 --- a/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs +++ b/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs @@ -12,7 +12,7 @@ public static class ICacheManagerExtensions { public static T Get(this ICacheManager cacheManager, string key) { - return cacheManager.Get(key, () => { return default(T); }); + return cacheManager.Get(key, () => default(T)); } } @@ -21,7 +21,12 @@ public partial class CacheManager : ICacheManager where TCache : ICache private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); private readonly ICache _cache; - public CacheManager(Func fn) + // 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 CacheManager(Func fn) { this._cache = fn(typeof(TCache)); } @@ -32,35 +37,40 @@ public T Get(string key, Func acquirer, int? cacheTime = null) if (_cache.Contains(key)) { - return (T)_cache.Get(key); + return GetExisting(key); } - else + + using (EnterReadLock()) { - using (EnterReadLock()) + if (!_cache.Contains(key)) { - if (!_cache.Contains(key)) - { - var value = acquirer(); - this.Set(key, value, cacheTime); + var value = acquirer(); + this.Set(key, value, cacheTime); - return value; - } + return value; } - - return (T)_cache.Get(key); } - } + + return GetExisting(key); + } + + private T GetExisting(string key) + { + var value = _cache.Get(key); + + if (value.Equals(FakeNull)) + return default(T); + + return (T)_cache.Get(key); + } public void Set(string key, object value, int? cacheTime = null) { Guard.ArgumentNotEmpty(() => key); - - if (value == null) - return; using (EnterWriteLock()) { - _cache.Set(key, value, cacheTime); + _cache.Set(key, value ?? FakeNull, cacheTime); } } diff --git a/src/Libraries/SmartStore.Core/Caching/ICache.cs b/src/Libraries/SmartStore.Core/Caching/ICache.cs index a250d20bc0..b9fc134a8d 100644 --- a/src/Libraries/SmartStore.Core/Caching/ICache.cs +++ b/src/Libraries/SmartStore.Core/Caching/ICache.cs @@ -26,7 +26,7 @@ public interface ICache /// Adds the cache item with the specified key /// /// Key - /// Data + /// Data /// Cache time in minutes void Set(string key, object value, int? cacheTime); diff --git a/src/Libraries/SmartStore.Core/Caching/NullCache.cs b/src/Libraries/SmartStore.Core/Caching/NullCache.cs index ea96b38717..f02414d682 100644 --- a/src/Libraries/SmartStore.Core/Caching/NullCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/NullCache.cs @@ -7,8 +7,7 @@ namespace SmartStore.Core.Caching /// public partial class NullCache : ICacheManager { - - private readonly static ICacheManager s_instance = new NullCache(); + private static readonly ICacheManager s_instance = new NullCache(); public static ICacheManager Instance { diff --git a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs index a293c6cd4f..7ab183061f 100644 --- a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs @@ -9,7 +9,7 @@ namespace SmartStore.Core.Caching public partial class RequestCache : ICache { - private const string REGION_NAME = "$$SmartStoreNET$$"; + private const string RegionName = "$$SmartStoreNET$$"; private readonly HttpContextBase _context; public RequestCache(HttpContextBase context) @@ -39,9 +39,9 @@ public IEnumerable> Entries string key = enumerator.Key as string; if (key == null) continue; - if (key.StartsWith(REGION_NAME)) + if (key.StartsWith(RegionName)) { - yield return new KeyValuePair(key.Substring(REGION_NAME.Length), enumerator.Value); + yield return new KeyValuePair(key.Substring(RegionName.Length), enumerator.Value); } } } @@ -64,13 +64,10 @@ public void Set(string key, object value, int? cacheTime) key = BuildKey(key); - if (value != null) - { - if (items.Contains(key)) - items[key] = value; - else - items.Add(key, value); - } + if (items.Contains(key)) + items[key] = value; + else + items.Add(key, value); } public bool Contains(string key) @@ -93,7 +90,7 @@ public void Remove(string key) private string BuildKey(string key) { - return key.HasValue() ? REGION_NAME + key : null; + return key.HasValue() ? RegionName + key : null; } public bool IsSingleton diff --git a/src/Libraries/SmartStore.Core/Caching/StaticCache.cs b/src/Libraries/SmartStore.Core/Caching/StaticCache.cs index 3e68892de3..c4fc216e76 100644 --- a/src/Libraries/SmartStore.Core/Caching/StaticCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/StaticCache.cs @@ -42,9 +42,10 @@ public void Set(string key, object value, int? cacheTime) { var cacheItem = new CacheItem(key, value); CacheItemPolicy policy = null; - if (cacheTime.GetValueOrDefault() > 0) + if (cacheTime.HasValue) { - policy = new CacheItemPolicy { AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime.Value) }; + var span = cacheTime.Value == 0 ? TimeSpan.FromMilliseconds(10) : TimeSpan.FromMinutes(cacheTime.Value); + policy = new CacheItemPolicy { AbsoluteExpiration = DateTime.Now + span }; } Cache.Add(cacheItem, policy); diff --git a/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs b/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs new file mode 100644 index 0000000000..789c363da0 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace SmartStore.Collections +{ + /// + /// Manages data keys and offers a combination of eager and lazy data loading + /// + public class LazyMultimap : Multimap + { + private readonly Func> _load; + private readonly List _loaded; // to avoid database round trips with empty results + private List _collect; + //private int _roundTripCount; + + /// + /// Constructor + /// + /// int[] keys like Entity.Id, Multimap{int, T}> delegate to load data + /// Keys of eager loaded data + public LazyMultimap(Func> load, IEnumerable collect = null) + { + _load = load; + _loaded = new List(); + + _collect = collect == null ? new List() : new List(collect); + } + + protected virtual void Load(IEnumerable keys) + { + 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 data. Load it if not already loaded yet. + /// + /// Data key + /// Collection of data + public virtual ICollection Load(int key) + { + if (key == 0) + { + return new List(); + } + + if (!_loaded.Contains(key)) + { + Load(new int[] { key }); + } + + // better not override indexer cause of stack overflow risk + var result = base[key]; + + Debug.Assert(_loaded.Contains(key), "Possible missing multimap result for key {0} and type {1}.".FormatInvariant(key, typeof(T).Name), ""); + + return result; + } + + /// + /// Collect keys for combined loading + /// + /// Data keys + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + public virtual void Collect(IEnumerable keys) + { + if (keys != null && keys.Any()) + { + _collect = _collect.Union(keys).ToList(); + } + } + + /// + /// Collect single key for combined loading + /// + /// Data key + public virtual void Collect(int key) + { + if (key != 0 && !_collect.Contains(key)) + { + _collect.Add(key); + } + } + + public override void Clear() + { + _loaded.Clear(); + _collect.Clear(); + //_roundTripCount = 0; + + base.Clear(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/MostRecentlyUsedList.cs b/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs similarity index 89% rename from src/Libraries/SmartStore.Core/Infrastructure/MostRecentlyUsedList.cs rename to src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs index cc5aacaab8..6d81d0bc3a 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/MostRecentlyUsedList.cs +++ b/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace SmartStore.Core.Infrastructure +namespace SmartStore.Collections { public class MostRecentlyUsedList : IEnumerable { @@ -14,6 +14,7 @@ public MostRecentlyUsedList(int maxSize) _maxSize = maxSize; _mru = new List(); } + public MostRecentlyUsedList(IEnumerable collection, int maxSize) { _maxSize = maxSize; @@ -21,13 +22,15 @@ public MostRecentlyUsedList(IEnumerable collection, int maxSize) Normalize(); } + public MostRecentlyUsedList(string collection, int maxSize) { _maxSize = maxSize; - _mru = (collection.SplitSafe(Delimiter) as IEnumerable).ToList(); + _mru = collection.SplitSafe(Delimiter).Cast().ToList(); Normalize(); } + public MostRecentlyUsedList(IEnumerable collection, T newItem, int maxSize) { _maxSize = maxSize; @@ -35,10 +38,11 @@ public MostRecentlyUsedList(IEnumerable collection, T newItem, int maxSize) Add(newItem); } + public MostRecentlyUsedList(string collection, T newItem, int maxSize) { _maxSize = maxSize; - _mru = (collection.SplitSafe(Delimiter) as IEnumerable).ToList(); + _mru = collection.SplitSafe(Delimiter).Cast().ToList(); Add(newItem); } diff --git a/src/Libraries/SmartStore.Core/Collections/QuerystringBuilder.cs b/src/Libraries/SmartStore.Core/Collections/Querystring.cs similarity index 93% rename from src/Libraries/SmartStore.Core/Collections/QuerystringBuilder.cs rename to src/Libraries/SmartStore.Core/Collections/Querystring.cs index 76606ef24d..9df23fc868 100644 --- a/src/Libraries/SmartStore.Core/Collections/QuerystringBuilder.cs +++ b/src/Libraries/SmartStore.Core/Collections/Querystring.cs @@ -1,6 +1,7 @@ using System.Text; using System.Web; using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; namespace SmartStore.Collections { @@ -28,6 +29,7 @@ public static QueryString Current /// /// 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)) @@ -45,22 +47,23 @@ public static string ExtractQuerystring(string s) /// /// the string to parse /// the QueryString object - public QueryString FillFromString(string s) + public QueryString FillFromString(string s, bool urlDecode = false) { base.Clear(); if (string.IsNullOrEmpty(s)) { return this; } + foreach (string keyValuePair in ExtractQuerystring(s).Split('&')) { if (string.IsNullOrEmpty(keyValuePair)) { continue; } + string[] split = keyValuePair.Split('='); - base.Add(split[0], - split.Length == 2 ? split[1] : ""); + base.Add(split[0], split.Length == 2 ? (urlDecode ? HttpUtility.UrlDecode(split[1]) : split[1]) : ""); } return this; } @@ -73,7 +76,7 @@ public QueryString FromCurrent() { if (HttpContext.Current != null) { - return FillFromString(HttpContext.Current.Request.QueryString.ToString()); + return FillFromString(HttpContext.Current.Request.QueryString.ToString(), true); } base.Clear(); return this; @@ -182,7 +185,7 @@ public override string ToString() { if (!string.IsNullOrEmpty(base.Keys[i])) { - foreach (string val in base[base.Keys[i]].Split(',')) + foreach (string val in base[base.Keys[i]].EmptyNull().Split(',')) { builder.Append((builder.Length == 0) ? "?" : "&").Append( HttpUtility.UrlEncode(base.Keys[i])).Append("=").Append(val); diff --git a/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs b/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs new file mode 100644 index 0000000000..79a4362f30 --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Linq.Expressions; +using System.Collections.Concurrent; + +namespace SmartStore.ComponentModel +{ + public class FastActivator + { + private static readonly ConcurrentDictionary _activatorsCache = new ConcurrentDictionary(); + + public FastActivator(ConstructorInfo constructorInfo) + { + Guard.ArgumentNotNull(() => constructorInfo); + + Constructor = constructorInfo; + Invoker = MakeFastInvoker(constructorInfo); + ParameterTypes = constructorInfo.GetParameters().Select(p => p.ParameterType).ToArray(); + } + + /// + /// Gets the backing . + /// + public ConstructorInfo Constructor { get; private set; } + + /// + /// Gets the parameter types from the backing + /// + public Type[] ParameterTypes { get; private set; } + + /// + /// Gets the constructor invoker. + /// + public Func Invoker { get; private set; } + + /// + /// Creates an instance of the type using the specified parameters. + /// + /// A reference to the newly created object. + public object Activate(params object[] parameters) + { + return Invoker(parameters); + } + + /// + /// Creates a single fast constructor invoker. The result is not cached. + /// + /// constructorInfo to create invoker for. + /// a fast invoker. + public static Func MakeFastInvoker(ConstructorInfo constructorInfo) + { + var paramsInfo = constructorInfo.GetParameters(); + + var parametersExpression = Expression.Parameter(typeof(object[]), "args"); + var argumentsExpression = new Expression[paramsInfo.Length]; + + for (int paramIndex = 0; paramIndex < paramsInfo.Length; paramIndex++) + { + var indexExpression = Expression.Constant(paramIndex); + var parameterType = paramsInfo[paramIndex].ParameterType; + + var parameterIndexExpression = Expression.ArrayIndex(parametersExpression, indexExpression); + var convertExpression = Expression.Convert(parameterIndexExpression, parameterType); + argumentsExpression[paramIndex] = convertExpression; + + if (!parameterType.GetTypeInfo().IsValueType) + continue; + + var nullConditionExpression = Expression.Equal(parameterIndexExpression, Expression.Constant(null)); + argumentsExpression[paramIndex] = Expression.Condition(nullConditionExpression, Expression.Default(parameterType), convertExpression); + } + + var newExpression = Expression.New(constructorInfo, argumentsExpression); + var lambda = Expression.Lambda>(newExpression, parametersExpression); + + return lambda.Compile(); + } + + #region Static + + /// + /// Creates and caches fast constructor invokers + /// + /// The type to extract fast constructor invokers for + /// A cached array of all public instance constructors from the given type. + /// The parameterless default constructor is always excluded from the list of activators + public static FastActivator[] GetActivators(Type type) + { + return GetActivatorsCore(type); + } + + private static FastActivator[] GetActivatorsCore(Type type) + { + FastActivator[] activators; + if (!_activatorsCache.TryGetValue(type, out activators)) + { + var candidates = GetCandidateConstructors(type); + activators = candidates.Select(c => new FastActivator(c)).ToArray(); + _activatorsCache.TryAdd(type, activators); + } + + return activators; + } + + /// + /// Creates an instance of the specified type using the constructor that best matches the specified parameters. + /// + /// The type of object to create. + /// + /// An array of arguments that match in number, order, and type the parameters of the constructor to invoke. + /// If args is an empty array or null, the constructor that takes no parameters (the default constructor) is invoked. + /// + /// A reference to the newly created object. + public static T CreateInstance(params object[] args) + { + return (T)CreateInstance(typeof(T), args); + } + + /// + /// Creates an instance of the specified type using the constructor that best matches the specified parameters. + /// + /// The type of object to create. + /// + /// An array of arguments that match in number, order, and type the parameters of the constructor to invoke. + /// If args is an empty array or null, the constructor that takes no parameters (the default constructor) is invoked. + /// + /// A reference to the newly created object. + public static object CreateInstance(Type type, params object[] args) + { + Guard.ArgumentNotNull(type, "type"); + + if (args == null || args.Length == 0) + { + // don't struggle with FastActivator: native reflection is really fast with default constructor! + return Activator.CreateInstance(type); + } + + var activators = GetActivatorsCore(type); + var matchingActivator = FindMatchingActivatorCore(activators, type, args); + + if (matchingActivator == null) + { + throw new ArgumentException("No matching contructor was found for the given arguments.", "args"); + } + + return matchingActivator.Activate(args); + } + + public static FastActivator FindMatchingActivator(Type type, params object[] args) + { + var activators = GetActivatorsCore(type); + var matchingActivator = FindMatchingActivatorCore(activators, type, args); + + return matchingActivator; + } + + private static FastActivator FindMatchingActivatorCore(FastActivator[] activators, Type type, object[] args) + { + if (activators.Length == 0) + { + return null; + } + + if (activators.Length == 1) + { + // this seems to be bad design, but it's on purpose for performance reasons. + // In nearly ALL cases there is only one constructor. + return activators[0]; + } + + var argTypes = args.Select(x => x.GetType()).ToArray(); + var constructor = type.GetConstructor( + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, + argTypes, + null); + + if (constructor != null) + { + var matchingActivator = activators.FirstOrDefault(a => a.Constructor == constructor); + return matchingActivator; + } + + return null; + } + + private static IEnumerable GetCandidateConstructors(Type type) + { + var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + return constructors.Where(c => c.GetParameters().Length > 0); + } + + private static void CheckIsValidType(Type type) + { + if (type.IsAbstract || type.IsInterface) + { + throw new ArgumentException("The type to create activators for must be concrete. Type: {0}".FormatInvariant(type.ToString()), "type"); + } + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs b/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs new file mode 100644 index 0000000000..068b84f008 --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs @@ -0,0 +1,671 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace SmartStore.ComponentModel +{ + public enum PropertyCachingStrategy + { + /// + /// Don't cache FastProperty instances + /// + Uncached = 0, + /// + /// Always cache FastProperty instances + /// + Cached = 1, + /// + /// Always cache FastProperty instances. PLUS cache all other properties of the declaring type. + /// + EagerCached = 2 + } + + public class FastProperty + { + // Delegate type for a by-ref property getter + private delegate TValue ByRefFunc(ref TDeclaringType arg); + + private static readonly MethodInfo CallPropertyGetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod("CallPropertyGetter"); + private static readonly MethodInfo CallPropertyGetterByReferenceOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod("CallPropertyGetterByReference"); + private static readonly MethodInfo CallNullSafePropertyGetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod("CallNullSafePropertyGetter"); + private static readonly MethodInfo CallNullSafePropertyGetterByReferenceOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod("CallNullSafePropertyGetterByReference"); + private static readonly MethodInfo CallPropertySetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod("CallPropertySetter"); + + private static readonly ConcurrentDictionary _singlePropertiesCache = new ConcurrentDictionary(); + + // Using an array rather than IEnumerable, as target will be called on the hot path numerous times. + private static readonly ConcurrentDictionary> _propertiesCache = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> _visiblePropertiesCache = new ConcurrentDictionary>(); + + private Action _valueSetter; + private bool? _isPublicSettable; + private bool? _isSequenceType; + + /// + /// Initializes a . + /// This constructor does not cache the helper. For caching, use . + /// + [SuppressMessage("ReSharper", "VirtualMemberCallInContructor")] + public FastProperty(PropertyInfo property) + { + Guard.ArgumentNotNull(() => property); + + Property = property; + Name = property.Name; + ValueGetter = MakeFastPropertyGetter(property); + } + + /// + /// Gets the backing . + /// + public PropertyInfo Property { get; private set; } + + /// + /// Gets (or sets in derived types) the property name. + /// + public virtual string Name { get; protected set; } + + /// + /// Gets the property value getter. + /// + public Func ValueGetter { get; private set; } + + public bool IsPublicSettable + { + get + { + if (!_isPublicSettable.HasValue) + { + _isPublicSettable = Property.CanWrite && Property.GetSetMethod(false) != null; + } + return _isPublicSettable.Value; + } + } + + public bool IsSequenceType + { + get + { + if (!_isSequenceType.HasValue) + { + _isSequenceType = Property.PropertyType != typeof(string) && Property.PropertyType.IsSubClass(typeof(IEnumerable<>)); + } + return _isSequenceType.Value; + } + } + + /// + /// Gets the property value setter. + /// + public Action ValueSetter + { + get + { + if (_valueSetter == null) + { + // We'll allow safe races here. + _valueSetter = MakeFastPropertySetter(Property); + } + + return _valueSetter; + } + } + + /// + /// Returns the property value for the specified . + /// + /// The object whose property value will be returned. + /// The property value. + public object GetValue(object instance) + { + return ValueGetter(instance); + } + + /// + /// Sets the property value for the specified . + /// + /// The object whose property value will be set. + /// The property value. + public void SetValue(object instance, object value) + { + ValueSetter(instance, value); + } + + /// + /// Creates and caches fast property helpers that expose getters for every public get property on the + /// underlying type. + /// + /// 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) + { + return GetProperties(instance.GetType()); + } + + /// + /// Creates and caches fast property helpers that expose getters for every public get property on the + /// specified type. + /// + /// The type to extract property accessors for. + /// A cached array of all public property getters from the type of target instance. + /// + public static IReadOnlyDictionary GetProperties(Type type, PropertyCachingStrategy cachingStrategy = PropertyCachingStrategy.Cached) + { + var propertiesCache = cachingStrategy > PropertyCachingStrategy.Uncached ? _propertiesCache : CreateVolatileCache(); + + return (IReadOnlyDictionary)GetProperties(type, CreateInstance, propertiesCache); + } + + /// + /// + /// Creates and caches fast property helpers that expose getters for every non-hidden get property + /// on the specified type. + /// + /// + /// excludes properties defined on base types that have been + /// hidden by definitions using the new keyword. + /// + /// + /// The instance to extract property accessors for. + /// + /// A cached array of all public property getters from the instance's type. + /// + public static IReadOnlyDictionary GetVisibleProperties(object instance, PropertyCachingStrategy cachingStrategy = PropertyCachingStrategy.Cached) + { + var propertiesCache = cachingStrategy > PropertyCachingStrategy.Uncached ? _propertiesCache : CreateVolatileCache(); + var visiblePropertiesCache = cachingStrategy > PropertyCachingStrategy.Uncached ? _visiblePropertiesCache : CreateVolatileCache(); + + return (IReadOnlyDictionary)GetVisibleProperties(instance.GetType(), CreateInstance, propertiesCache, visiblePropertiesCache); + } + + /// + /// + /// Creates and caches fast property helpers that expose getters for every non-hidden get property + /// on the specified type. + /// + /// + /// excludes properties defined on base types that have been + /// hidden by definitions using the new keyword. + /// + /// + /// The type to extract property accessors for. + /// + /// A cached array of all public property getters from the type. + /// + public static IReadOnlyDictionary GetVisibleProperties(Type type, PropertyCachingStrategy cachingStrategy = PropertyCachingStrategy.Cached) + { + var propertiesCache = cachingStrategy > PropertyCachingStrategy.Uncached ? _propertiesCache : CreateVolatileCache(); + var visiblePropertiesCache = cachingStrategy > PropertyCachingStrategy.Uncached ? _visiblePropertiesCache : CreateVolatileCache(); + + return (IReadOnlyDictionary)GetVisibleProperties(type, CreateInstance, propertiesCache, visiblePropertiesCache); + } + + public static FastProperty GetProperty( + Expression> property, + PropertyCachingStrategy cachingStrategy = PropertyCachingStrategy.Cached) + { + return GetProperty(property.ExtractPropertyInfo(), cachingStrategy); + } + + public static FastProperty GetProperty( + Type type, + string propertyName, + PropertyCachingStrategy cachingStrategy = PropertyCachingStrategy.Cached) + { + Guard.ArgumentNotNull(() => type); + Guard.ArgumentNotEmpty(() => propertyName); + + FastProperty fastProperty = null; + + if (TryGetCachedProperty(type, propertyName, cachingStrategy == PropertyCachingStrategy.EagerCached, out fastProperty)) + { + return fastProperty; + } + + 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); + if (pi != null) + { + fastProperty = CreateInstance(pi); + if (cachingStrategy > PropertyCachingStrategy.Uncached) + { + _singlePropertiesCache.TryAdd(key, fastProperty); + } + } + } + + return fastProperty; + } + + public static FastProperty GetProperty( + PropertyInfo propertyInfo, + PropertyCachingStrategy cachingStrategy = PropertyCachingStrategy.Cached) + { + Guard.ArgumentNotNull(() => propertyInfo); + + FastProperty fastProperty = null; + + if (TryGetCachedProperty(propertyInfo.ReflectedType, propertyInfo.Name, cachingStrategy == PropertyCachingStrategy.EagerCached, out fastProperty)) + { + return fastProperty; + } + + var key = new PropertyKey(propertyInfo.ReflectedType, propertyInfo.Name); + if (!_singlePropertiesCache.TryGetValue(key, out fastProperty)) + { + fastProperty = CreateInstance(propertyInfo); + if (cachingStrategy > PropertyCachingStrategy.Uncached) + { + _singlePropertiesCache.TryAdd(key, fastProperty); + } + } + + return fastProperty; + } + + private static bool TryGetCachedProperty( + Type type, + string propertyName, + bool eagerCached, + out FastProperty fastProperty) + { + fastProperty = null; + IDictionary allProperties; + + if (eagerCached) + { + allProperties = (IDictionary)GetProperties(type); + allProperties.TryGetValue(propertyName, out fastProperty); + } + + if (fastProperty == null && _propertiesCache.TryGetValue(type, out allProperties)) + { + allProperties.TryGetValue(propertyName, out fastProperty); + } + + return fastProperty != null; + } + + /// + /// Creates a single fast property getter. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. + /// + public static Func MakeFastPropertyGetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + + return MakeFastPropertyGetter( + propertyInfo, + CallPropertyGetterOpenGenericMethod, + CallPropertyGetterByReferenceOpenGenericMethod); + } + + /// + /// Creates a single fast property getter which is safe for a null input object. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. + /// + public static Func MakeNullSafeFastPropertyGetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + + return MakeFastPropertyGetter( + propertyInfo, + CallNullSafePropertyGetterOpenGenericMethod, + CallNullSafePropertyGetterByReferenceOpenGenericMethod); + } + + private static Func MakeFastPropertyGetter( + PropertyInfo propertyInfo, + MethodInfo propertyGetterWrapperMethod, + MethodInfo propertyGetterByRefWrapperMethod) + { + Debug.Assert(propertyInfo != null); + + // Must be a generic method with a Func<,> parameter + Debug.Assert(propertyGetterWrapperMethod != null); + Debug.Assert(propertyGetterWrapperMethod.IsGenericMethodDefinition); + Debug.Assert(propertyGetterWrapperMethod.GetParameters().Length == 2); + + // Must be a generic method with a ByRefFunc<,> parameter + Debug.Assert(propertyGetterByRefWrapperMethod != null); + Debug.Assert(propertyGetterByRefWrapperMethod.IsGenericMethodDefinition); + Debug.Assert(propertyGetterByRefWrapperMethod.GetParameters().Length == 2); + + var getMethod = propertyInfo.GetMethod; + Debug.Assert(getMethod != null); + Debug.Assert(!getMethod.IsStatic); + Debug.Assert(getMethod.GetParameters().Length == 0); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "target". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + if (getMethod.DeclaringType.GetTypeInfo().IsValueType) + { + // Create a delegate (ref TDeclaringType) -> TValue + return MakeFastPropertyGetter( + typeof(ByRefFunc<,>), + getMethod, + propertyGetterByRefWrapperMethod); + } + else + { + // Create a delegate TDeclaringType -> TValue + return MakeFastPropertyGetter( + typeof(Func<,>), + getMethod, + propertyGetterWrapperMethod); + } + } + + private static Func MakeFastPropertyGetter( + Type openGenericDelegateType, + MethodInfo propertyGetMethod, + MethodInfo openGenericWrapperMethod) + { + var typeInput = propertyGetMethod.DeclaringType; + var typeOutput = propertyGetMethod.ReturnType; + + var delegateType = openGenericDelegateType.MakeGenericType(typeInput, typeOutput); + var propertyGetterDelegate = propertyGetMethod.CreateDelegate(delegateType); + + var wrapperDelegateMethod = openGenericWrapperMethod.MakeGenericMethod(typeInput, typeOutput); + var accessorDelegate = wrapperDelegateMethod.CreateDelegate( + typeof(Func), + propertyGetterDelegate); + + return (Func)accessorDelegate; + } + + /// + /// Creates a single fast property setter for reference types. The result is not cached. + /// + /// propertyInfo to extract the setter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. This only works for reference types. + /// + public static Action MakeFastPropertySetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + Debug.Assert(!propertyInfo.DeclaringType.GetTypeInfo().IsValueType); + + var setMethod = propertyInfo.SetMethod; + Debug.Assert(setMethod != null); + Debug.Assert(!setMethod.IsStatic); + Debug.Assert(setMethod.ReturnType == typeof(void)); + var parameters = setMethod.GetParameters(); + Debug.Assert(parameters.Length == 1); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "target". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + var typeInput = setMethod.DeclaringType; + var parameterType = parameters[0].ParameterType; + + // Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; } + var propertySetterAsAction = + setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType)); + var callPropertySetterClosedGenericMethod = + CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType); + var callPropertySetterDelegate = + callPropertySetterClosedGenericMethod.CreateDelegate( + typeof(Action), propertySetterAsAction); + + return (Action)callPropertySetterDelegate; + } + + /// + /// Given an object, adds each instance property with a public get method as a key and its + /// associated value to a dictionary. + /// + /// If the object is already an + /// IDictionary{string, object} + /// + /// instance, then a copy + /// is returned. + /// + /// + /// 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) + { + var dictionary = value as IDictionary; + if (dictionary != null) + { + return new Dictionary(dictionary, StringComparer.OrdinalIgnoreCase); + } + + keySelector = keySelector ?? new Func(key => key); + + dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (value != null) + { + foreach (var prop in GetProperties(value).Values) + { + dictionary[keySelector(prop.Name)] = prop.GetValue(value); + } + } + + return dictionary; + } + + private static FastProperty CreateInstance(PropertyInfo property) + { + return new FastProperty(property); + } + + // Called via reflection + private static object CallPropertyGetter( + Func getter, + object target) + { + return getter((TDeclaringType)target); + } + + // Called via reflection + private static object CallPropertyGetterByReference( + ByRefFunc getter, + object target) + { + var unboxed = (TDeclaringType)target; + return getter(ref unboxed); + } + + // Called via reflection + private static object CallNullSafePropertyGetter( + Func getter, + object target) + { + if (target == null) + { + return null; + } + + return getter((TDeclaringType)target); + } + + // Called via reflection + private static object CallNullSafePropertyGetterByReference( + ByRefFunc getter, + object target) + { + if (target == null) + { + return null; + } + + var unboxed = (TDeclaringType)target; + return getter(ref unboxed); + } + + private static void CallPropertySetter( + Action setter, + object target, + object value) + { + setter((TDeclaringType)target, (TValue)value); + } + + protected static IDictionary GetVisibleProperties( + Type type, + Func createPropertyHelper, + ConcurrentDictionary> allPropertiesCache, + ConcurrentDictionary> visiblePropertiesCache) + { + IDictionary result; + if (visiblePropertiesCache.TryGetValue(type, out 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); + foreach (var propertyHelper in allProperties.Values) + { + if (propertyHelper.Property.DeclaringType != type) + { + allPropertiesDefinedOnType = false; + break; + } + } + + if (allPropertiesDefinedOnType) + { + result = allProperties; + visiblePropertiesCache.TryAdd(type, result); + return result; + } + + // There's some inherited properties here, so we need to check for hiding via 'new'. + var filteredProperties = new List(allProperties.Count); + foreach (var propertyHelper in allProperties.Values) + { + var declaringType = propertyHelper.Property.DeclaringType; + if (declaringType == type) + { + filteredProperties.Add(propertyHelper); + continue; + } + + // If this property was declared on a base type then look for the definition closest to the + // the type to see if we should include it. + var ignoreProperty = false; + + // Walk up the hierarchy until we find the type that actally declares this + // PropertyInfo. + var currentTypeInfo = type.GetTypeInfo(); + var declaringTypeInfo = declaringType.GetTypeInfo(); + while (currentTypeInfo != null && currentTypeInfo != declaringTypeInfo) + { + // We've found a 'more proximal' public definition + var declaredProperty = currentTypeInfo.GetDeclaredProperty(propertyHelper.Name); + if (declaredProperty != null) + { + ignoreProperty = true; + break; + } + + if (currentTypeInfo.BaseType != null) + { + currentTypeInfo = currentTypeInfo.BaseType.GetTypeInfo(); + } + + } + + if (!ignoreProperty) + { + filteredProperties.Add(propertyHelper); + } + } + + result = filteredProperties.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); + visiblePropertiesCache.TryAdd(type, result); + return result; + } + + protected static IDictionary GetProperties( + Type type, + Func createPropertyHelper, + ConcurrentDictionary> cache) + { + // Unwrap nullable types. This means Nullable.Value and Nullable.HasValue will not be + // part of the sequence of properties returned by this method. + type = Nullable.GetUnderlyingType(type) ?? type; + + IDictionary fastProperties; + if (!cache.TryGetValue(type, out fastProperties)) + { + var candidates = GetCandidateProperties(type); + fastProperties = candidates.Select(p => createPropertyHelper(p)).ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); + cache.TryAdd(type, fastProperties); + } + + return fastProperties; + } + + private static IEnumerable GetCandidateProperties(Type type) + { + // We avoid loading indexed properties using the Where statement. + var properties = type.GetRuntimeProperties().Where(IsCandidateProperty); + + var typeInfo = type.GetTypeInfo(); + if (typeInfo.IsInterface) + { + // Reflection does not return information about inherited properties on the interface itself. + properties = properties.Concat(typeInfo.ImplementedInterfaces.SelectMany( + interfaceType => interfaceType.GetRuntimeProperties().Where(IsCandidateProperty))); + } + + return properties; + } + + // Indexed properties are not useful (or valid) for grabbing properties off an object. + private static bool IsCandidateProperty(PropertyInfo property) + { + return property.GetIndexParameters().Length == 0 && + property.GetMethod != null && + property.GetMethod.IsPublic && + !property.GetMethod.IsStatic; + } + + private static ConcurrentDictionary> CreateVolatileCache() + { + return new ConcurrentDictionary>(); + } + + class PropertyKey : Tuple + { + public PropertyKey(Type type, string propertyName) + : base(type, propertyName) + { + } + public Type Type { get { return base.Item1; } } + public string PropertyName { get { return base.Item2; } } + } + } +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/ComponentModel/GenericListTypeConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/GenericListTypeConverter.cs deleted file mode 100644 index f5253dd01b..0000000000 --- a/src/Libraries/SmartStore.Core/ComponentModel/GenericListTypeConverter.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Linq; - -namespace SmartStore.Core.ComponentModel -{ - public class GenericListTypeConverter : TypeConverter - { - protected readonly TypeConverter _typeConverter; - - public GenericListTypeConverter() - { - _typeConverter = TypeDescriptor.GetConverter(typeof(T)); - if (_typeConverter == null) - throw new InvalidOperationException("No type converter exists for type " + typeof(T).FullName); - } - - protected virtual string[] GetStringArray(string input) - { - if (!String.IsNullOrEmpty(input)) - { - string[] result = input.Split(','); - Array.ForEach(result, s => s.Trim()); - return result; - } - else - return new string[0]; - } - - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - - if (sourceType == typeof(string)) - { - string[] items = GetStringArray(sourceType.ToString()); - return (items.Count() > 0); - } - - return base.CanConvertFrom(context, sourceType); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - if (value is string) - { - string[] items = GetStringArray((string)value); - var result = new List(); - Array.ForEach(items, s => - { - object item = _typeConverter.ConvertFromInvariantString(s); - if (item != null) - { - result.Add((T)item); - } - }); - - return result; - } - return base.ConvertFrom(context, culture, value); - } - - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) - { - if (destinationType == typeof(string)) - { - string result = string.Empty; - if (((IList)value) != null) - { - //we don't use string.Join() because it doesn't support invariant culture - for (int i = 0; i < ((IList)value).Count; i++) - { - var str1 = Convert.ToString(((IList)value)[i], CultureInfo.InvariantCulture); - result += str1; - //don't add comma after the last element - if (i != ((IList)value).Count - 1) - result += ","; - } - } - return result; - } - - return base.ConvertTo(context, culture, value, destinationType); - } - } -} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs similarity index 51% rename from src/Libraries/SmartStore.Core/ComponentModel/Expando.cs rename to src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs index 81c2ecb4f7..1049481e35 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs @@ -36,7 +36,7 @@ using System.Linq; using System.Dynamic; using System.Reflection; -using Fasterflect; +using System.Collections; namespace SmartStore.ComponentModel { @@ -59,7 +59,7 @@ namespace SmartStore.ComponentModel /// Dictionary: Any of the extended properties are accessible via IDictionary interface /// [Serializable] - public class Expando : DynamicObject, IDynamicMetaObjectProvider + public class HybridExpando : DynamicObject, IDictionary { /// /// Instance of object passed in @@ -71,8 +71,6 @@ public class Expando : DynamicObject, IDynamicMetaObjectProvider /// private Type _instanceType; - private IList _instancePropertyInfo; - /// /// String Dictionary that contains the extra dynamic values /// stored on this object/instance @@ -86,7 +84,7 @@ public class Expando : DynamicObject, IDynamicMetaObjectProvider /// /// Note you can subclass Expando. /// - public Expando() + public HybridExpando() { Initialize(this); } @@ -99,30 +97,24 @@ public Expando() /// check native properties and only check the Dictionary! /// /// - public Expando(object instance) + public HybridExpando(object instance) { Initialize(instance); } - - protected virtual void Initialize(object instance) + protected void Initialize(object instance) { _instance = instance; if (instance != null) _instanceType = instance.GetType(); } - IList InstancePropertyInfo - { - get - { - if (_instancePropertyInfo == null && _instance != null) - _instancePropertyInfo = _instance.GetType().Properties(Flags.InstancePublicDeclaredOnly); - return _instancePropertyInfo; - } - } + protected object WrappedObject + { + get { return _instance; } + } - public override IEnumerable GetDynamicMemberNames() + public override IEnumerable GetDynamicMemberNames() { foreach (var prop in this.GetProperties(false)) yield return prop.Key; @@ -138,68 +130,76 @@ public override IEnumerable GetDynamicMemberNames() /// public override bool TryGetMember(GetMemberBinder binder, out object result) { - result = null; - - // first check the Properties collection for member - if (Properties.Keys.Contains(binder.Name)) - { - result = Properties[binder.Name]; - return true; - } - - - // Next check for Public properties via Reflection - if (_instance != null) - { - try - { - return GetProperty(_instance, binder.Name, out result); - } - catch { } - } - - // failed to retrieve a property - result = null; - return false; + return TryGetMemberCore(binder.Name, out result); } - - /// - /// Property setter implementation tries to retrieve value from instance - /// first then into this object - /// - /// - /// - /// - public override bool TrySetMember(SetMemberBinder binder, object value) + protected virtual bool TryGetMemberCore(string name, out object result) + { + result = null; + + // first check the Properties collection for member + if (Properties.Keys.Contains(name)) + { + result = Properties[name]; + return true; + } + + // Next check for public properties via Reflection + if (_instance != null) + { + try + { + return GetProperty(_instance, name, out result); + } + catch { } + } + + // failed to retrieve a property + result = null; + return false; + } + + + /// + /// Property setter implementation tries to retrieve value from instance + /// first then into this object + /// + /// + /// + /// + public override bool TrySetMember(SetMemberBinder binder, object value) { - - // first check to see if there's a native property to set - if (_instance != null) - { - try - { - bool result = SetProperty(_instance, binder.Name, value); - if (result) - return true; - } - catch { } - } - - // no match - set or add to dictionary - Properties[binder.Name] = value; - return true; + return TrySetMemberCore(binder.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) + protected virtual bool TrySetMemberCore(string name, object value) + { + // first check to see if there's a native property to set + if (_instance != null) + { + try + { + bool result = SetProperty(_instance, name, value); + if (result) + return true; + } + catch { } + } + + // no match - set or add to dictionary + Properties[name] = value; + return true; + } + + /// + /// 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) { if (_instance != null) { @@ -226,19 +226,16 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o /// protected bool GetProperty(object instance, string name, out object result) { - if (instance == null) - instance = this; - - var pi = _instanceType.Property(name, Flags.InstancePublic); - if (pi != null) - { - result = instance.GetPropertyValue(pi.Name); - return true; - } - - result = null; + var fastProp = _instanceType != null ? FastProperty.GetProperty(_instanceType, name, PropertyCachingStrategy.EagerCached) : null; + if (fastProp != null) + { + result = fastProp.GetValue(instance ?? this); + return true; + } + + result = null; return false; - } + } /// /// Reflection helper method to set a property value @@ -249,16 +246,14 @@ protected bool GetProperty(object instance, string name, out object result) /// protected bool SetProperty(object instance, string name, object value) { - if (instance == null) - instance = this; - - var pi = _instanceType.Property(name, Flags.InstancePublic); - if (pi != null) - { - instance.SetPropertyValue(pi.Name, value); - return true; + var fastProp = _instanceType != null ? FastProperty.GetProperty(_instanceType, name, PropertyCachingStrategy.EagerCached) : null; + if (fastProp != null) + { + fastProp.SetValue(instance ?? this, value); + return true; } - return false; + + return false; } /// @@ -271,16 +266,12 @@ protected bool SetProperty(object instance, string name, object value) /// protected bool InvokeMethod(object instance, string name, object[] args, out object result) { - if (instance == null) - instance = this; - // Look at the instanceType - var mi = _instanceType.Method(name, Flags.InstancePublic); - + var mi = _instanceType != null ? _instanceType.GetMethod(name, BindingFlags.Instance | BindingFlags.Public) : null; if (mi != null) { - result = _instance.CallMethod(mi.Name, args); - return true; + result = mi.Invoke(instance ?? this, args); + return true; } result = null; @@ -288,78 +279,66 @@ protected bool InvokeMethod(object instance, string name, object[] args, out obj } - /// - /// Convenience method that provides a string Indexer - /// to the Properties collection AND the strongly typed - /// properties of the object by name. - /// - /// // dynamic - /// exp["Address"] = "112 nowhere lane"; - /// // strong - /// var name = exp["StronglyTypedProperty"] as string; - /// - /// - /// The getter checks the Properties dictionary first - /// then looks in PropertyInfo for properties. - /// The setter checks the instance properties before - /// checking the Properties dictionary. - /// - /// - /// - /// - public object this[string key] + /// + /// Convenience method that provides a string Indexer + /// to the Properties collection AND the strongly typed + /// properties of the object by name. + /// + /// // dynamic + /// exp["Address"] = "112 nowhere lane"; + /// // strong + /// var name = exp["StronglyTypedProperty"] as string; + /// + /// + /// The getter checks the Properties dictionary first + /// then looks in PropertyInfo for properties. + /// The setter checks the instance properties before + /// checking the Properties dictionary. + /// + /// + /// + /// + public object this[string key] + { + get + { + object result = null; + if (!TryGetMemberCore(key, out result)) + { + throw new KeyNotFoundException(); + } + + return result; + } + set + { + TrySetMemberCore(key, value); + } + } + + + /// + /// Returns all properties + /// + /// + /// + public IEnumerable> GetProperties(bool includeInstanceProperties = false) { - get + foreach (var key in this.Properties.Keys) + { + yield return new KeyValuePair(key, this.Properties[key]); + } + + if (includeInstanceProperties && _instance != null) { - try - { - // try to get from properties collection first - return Properties[key]; - } - catch (KeyNotFoundException) - { - // try reflection on instanceType - object result = null; - if (GetProperty(_instance, key, out result)) - return result; - - // no doesn't exist - throw; - } + foreach (var prop in FastProperty.GetProperties(_instance).Values) + { + if (!this.Properties.ContainsKey(prop.Name)) + { + yield return new KeyValuePair(prop.Name, prop.GetValue(_instance)); + } + } } - set - { - if (Properties.ContainsKey(key)) - { - Properties[key] = value; - return; - } - - // check instance for existance of type first - var pi = _instanceType.Property(key, Flags.Public); - if (pi != null) - SetProperty(_instance, key, value); - else - Properties[key] = value; - } - } - - - /// - /// Returns and the properties of - /// - /// - /// - public IEnumerable> GetProperties(bool includeInstanceProperties = false) - { - if (includeInstanceProperties && _instance != null) - { - foreach (var prop in this.InstancePropertyInfo) - yield return new KeyValuePair(prop.Name, prop.GetValue(_instance, null)); - } - - foreach (var key in this.Properties.Keys) - yield return new KeyValuePair(key, this.Properties[key]); } @@ -383,34 +362,135 @@ public bool Contains(KeyValuePair item, bool includeInstanceProp /// public bool Contains(string propertyName, bool includeInstanceProperties = false) { - bool res = Properties.ContainsKey(propertyName); - if (res) - return true; + if (Properties.ContainsKey(propertyName)) + { + return true; + } if (includeInstanceProperties && _instance != null) { - foreach (var prop in this.InstancePropertyInfo) - { - if (prop.Name == propertyName) - return true; - } + return FastProperty.GetProperties(_instance).ContainsKey(propertyName); } return false; } - public bool TryGetValue(string propertyName, out object value) - { - value = null; - - if (this.Contains(propertyName, true)) - { - value = this[propertyName]; - return true; - } - - return false; + #region IDictionary + + ICollection IDictionary.Keys + { + get + { + return GetProperties(true).Select(x => x.Key).AsReadOnly(); + } + } + + ICollection IDictionary.Values + { + get + { + return GetProperties(true).Select(x => x.Value).AsReadOnly(); + } + } + + int ICollection>.Count + { + get + { + var count = Properties.Count; + if (_instanceType != null) + { + count += FastProperty.GetProperties(_instanceType).Count; + } + + return count; + } + } + + bool ICollection>.IsReadOnly + { + get + { + return false; + } + } + + object IDictionary.this[string key] + { + get + { + return this[key]; + } + + set + { + this[key] = value; + } + } + + bool IDictionary.ContainsKey(string key) + { + return Contains(key, true); + } + + void IDictionary.Add(string key, object value) + { + throw new NotImplementedException(); + } + + bool IDictionary.Remove(string key) + { + throw new NotImplementedException(); + } + + public bool TryGetValue(string key, out object value) + { + value = null; + + if (this.Contains(key, true)) + { + value = this[key]; + return true; + } + + return false; + } + + void ICollection>.Add(KeyValuePair item) + { + throw new NotImplementedException(); + } + + void ICollection>.Clear() + { + throw new NotImplementedException(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return Contains(item.Key, true); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + bool ICollection>.Remove(KeyValuePair item) + { + throw new NotImplementedException(); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetProperties(true).GetEnumerator(); } - } + IEnumerator IEnumerable.GetEnumerator() + { + return GetProperties(true).GetEnumerator(); + } + + #endregion + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs b/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs index fb2ab77767..bec72dcea5 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs @@ -181,7 +181,7 @@ public void WriteXml(System.Xml.XmlWriter writer) writer.WriteStartElement("item"); writer.WriteStartElement("key"); - writer.WriteString(key as string); + writer.WriteString(key); writer.WriteEndElement(); writer.WriteStartElement("value"); @@ -306,8 +306,7 @@ public bool FromXml(string xml) if (string.IsNullOrEmpty(xml)) return true; - var result = SerializationUtils.DeSerializeObject(xml, - this.GetType()) as PropertyBag; + var result = SerializationUtils.DeSerializeObject(xml, this.GetType()) as PropertyBag; if (result != null) { foreach (var item in result) diff --git a/src/Libraries/SmartStore.Core/ComponentModel/SerializationUtils.cs b/src/Libraries/SmartStore.Core/ComponentModel/SerializationUtils.cs index 263e516ef5..e36995facd 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/SerializationUtils.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/SerializationUtils.cs @@ -34,20 +34,13 @@ using System; using System.IO; using System.Text; -//using System.Reflection; -using Fasterflect; - using System.Xml; using System.Xml.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Diagnostics; -using System.Runtime.Serialization; namespace SmartStore.ComponentModel { - - // Serialization specific code - internal static class SerializationUtils { /// @@ -194,25 +187,25 @@ public static bool SerializeObject(object instance, out string xmlResultString, return true; } - - /// - /// Serializes an object instance to a file. - /// - /// the object instance to serialize - /// - /// determines whether XML serialization or binary serialization is used - /// - public static bool SerializeObject(object instance, out byte[] resultBuffer, bool throwExceptions = false) + /// + /// Serializes an object instance to a file. + /// + /// the object instance to serialize + /// + /// + /// + public static bool SerializeObject(object instance, out byte[] resultBuffer, bool throwExceptions = false) { bool retVal = true; + resultBuffer = null; - MemoryStream ms = null; + var ms = new MemoryStream(); try { - BinaryFormatter serializer = new BinaryFormatter(); - ms = new MemoryStream(); + var serializer = new BinaryFormatter(); serializer.Serialize(ms, instance); - } + resultBuffer = ms.ToArray(); + } catch (Exception ex) { Debug.Write("SerializeObject failed with : " + ex.GetBaseException().Message, "West Wind"); @@ -223,12 +216,9 @@ public static bool SerializeObject(object instance, out byte[] resultBuffer, boo } finally { - if (ms != null) - ms.Close(); + ms.Close(); } - resultBuffer = ms.ToArray(); - return retVal; } @@ -419,17 +409,17 @@ public static object DeSerializeObject(byte[] buffer, Type objectType, bool thro /// public static string ObjectToString(object instance, string separator, ObjectToStringTypes type) { - var fi = instance.GetType().Fields(); + var fi = instance.GetType().GetFields(); string output = string.Empty; if (type == ObjectToStringTypes.Properties || type == ObjectToStringTypes.PropertiesAndFields) { - foreach (var property in instance.GetType().Properties()) + foreach (var property in instance.GetType().GetProperties()) { try { - output += property.Name + ":" + instance.GetPropertyValue(property.Name).ToString() + separator; + output += property.Name + ":" + property.GetValue(instance, null).ToString() + separator; } catch { @@ -444,7 +434,7 @@ public static string ObjectToString(object instance, string separator, ObjectToS { try { - output = output + field.Name + ": " + instance.GetFieldValue(field.Name).ToString() + separator; + output = output + field.Name + ": " + field.GetValue(instance).ToString() + separator; } catch { diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/BooleanConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/BooleanConverter.cs new file mode 100644 index 0000000000..11e6c6c941 --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/BooleanConverter.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.ComponentModel +{ + [SuppressMessage("ReSharper", "CanBeReplacedWithTryCastAndCheckForNull")] + public class BooleanConverter : TypeConverterBase + { + private readonly HashSet _trueValues; + private readonly HashSet _falseValues; + + public BooleanConverter(string[] trueValues, string[] falseValues) + : base(typeof(bool)) + { + _trueValues = new HashSet(trueValues, StringComparer.OrdinalIgnoreCase); + _falseValues = new HashSet(falseValues, StringComparer.OrdinalIgnoreCase); + } + + public ICollection TrueValues + { + get { return _trueValues; } + } + + public ICollection FalseValues + { + get { return _falseValues; } + } + + public override object ConvertFrom(CultureInfo culture, object value) + { + if (value is short) + { + if ((short)value == 0) + { + return false; + } + if ((short)value == 1) + { + return true; + } + } + + if (value is string) + { + var str = (string)value; + + bool b; + if (bool.TryParse(str, out b)) + { + return b; + } + + short sh; + if (short.TryParse(str, out sh)) + { + if (sh == 0) + { + return false; + } + if (sh == 1) + { + return true; + } + } + + str = (str.NullEmpty() ?? string.Empty).Trim(); + if (_trueValues.Contains(str)) + { + return true; + } + + if (_falseValues.Contains(str)) + { + return false; + } + } + + return base.ConvertFrom(culture, value); + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/DateTimeConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/DateTimeConverter.cs new file mode 100644 index 0000000000..b357dcddb8 --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/DateTimeConverter.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.ComponentModel +{ + [SuppressMessage("ReSharper", "CanBeReplacedWithTryCastAndCheckForNull")] + public class DateTimeConverter : TypeConverterBase + { + public DateTimeConverter() + : base(typeof(DateTime)) + { + } + + public override bool CanConvertFrom(Type type) + { + return type == typeof(string) + || type == typeof(long) + || type == typeof(double) + || type == typeof(TimeSpan) + || base.CanConvertFrom(type); + } + + public override bool CanConvertTo(Type type) + { + return type == typeof(string) + || type == typeof(long) + || type == typeof(double) + || type == typeof(DateTimeOffset) + || type == typeof(TimeSpan) + || base.CanConvertTo(type); + } + + public override object ConvertFrom(CultureInfo culture, object value) + { + if (value is TimeSpan) + { + var span = (TimeSpan)value; + return new DateTime(span.Ticks); + } + + if (value is string) + { + var str = (string)value; + + DateTime time; + if (DateTime.TryParse(str, culture, DateTimeStyles.None, out time)) + { + return time; + } + + long lng; + if (long.TryParse(str, NumberStyles.None, culture, out lng)) + { + return lng.FromUnixTime(); + } + + double dbl; + if (double.TryParse(str, NumberStyles.AllowDecimalPoint, culture, out dbl)) + { + return DateTime.FromOADate(dbl); + } + } + + if (value is long) + { + return ((long)value).FromUnixTime(); + } + + if (value is double) + { + return DateTime.FromOADate((double)value); + } + + return base.ConvertFrom(culture, value); + } + + public override object ConvertTo(CultureInfo culture, string format, object value, Type to) + { + var time = (DateTime)value; + + if (to == typeof(DateTimeOffset)) + { + return new DateTimeOffset(time); + } + + if (to == typeof(TimeSpan)) + { + return new TimeSpan(time.Ticks); + } + + if (to == typeof(double)) + { + return time.ToOADate(); + } + + if (to == typeof(long)) + { + return time.ToUnixTime(); + } + + 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 new file mode 100644 index 0000000000..1980693205 --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/EnumerableConverter.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; + +namespace SmartStore.ComponentModel +{ + [SuppressMessage("ReSharper", "CanBeReplacedWithTryCastAndCheckForNull")] + public class EnumerableConverter : TypeConverterBase + { + private readonly Func, object> _activator; + private readonly ITypeConverter _elementTypeConverter; + + public EnumerableConverter(Type sequenceType) + : base(typeof(object)) + { + _elementTypeConverter = TypeConverterFactory.GetConverter(); + if (_elementTypeConverter == null) + throw new InvalidOperationException("No type converter exists for type " + typeof(T).FullName); + + _activator = CreateSequenceActivator(sequenceType); + } + + [SuppressMessage("ReSharper", "RedundantLambdaSignatureParentheses")] + private static Func, object> CreateSequenceActivator(Type sequenceType) + { + // Default is IEnumerable + Func, object> activator = null; + + var t = sequenceType; + + if (t == typeof(IEnumerable)) + { + activator = (x) => x; + } + else if (t == (typeof(IReadOnlyCollection)) || t == (typeof(IReadOnlyList))) + { + activator = (x) => x.AsReadOnly(); + } + else if (t.IsAssignableFrom(typeof(List))) + { + activator = (x) => x.ToList(); + } + else if (t.IsAssignableFrom(typeof(HashSet))) + { + activator = (x) => new HashSet(x); + } + else if (t.IsAssignableFrom(typeof(Queue))) + { + activator = (x) => new Queue(x); + } + else if (t.IsAssignableFrom(typeof(Stack))) + { + activator = (x) => new Stack(x); + } + else if (t.IsAssignableFrom(typeof(LinkedList))) + { + activator = (x) => new LinkedList(x); + } + else if (t.IsAssignableFrom(typeof(ConcurrentBag))) + { + activator = (x) => new ConcurrentBag(x); + } + else if (t.IsAssignableFrom(typeof(ArraySegment))) + { + activator = (x) => new ArraySegment(x.ToArray()); + } + + if (activator == null) + { + throw new InvalidOperationException("'{0}' is not a valid type for enumerable conversion.".FormatInvariant(sequenceType.FullName)); + } + + return activator; + } + + public override bool CanConvertFrom(Type type) + { + return type == typeof(string) || typeof(IConvertible).IsAssignableFrom(type); + } + + public override bool CanConvertTo(Type type) + { + return type == typeof(string); + } + + public override object ConvertFrom(CultureInfo culture, object value) + { + if (value == null) + { + return _activator(Enumerable.Empty()); + } + + if (value is string) + { + var items = GetStringArray((string)value); + + var result = items + .Select(x => _elementTypeConverter.ConvertFrom(culture, x)) + .Where(x => x != null) + .Cast(); + + return _activator(result); + } + + if (value is IConvertible) + { + var result2 = (new object[] { value }) + .Select(x => Convert.ChangeType(value, typeof(T))) + .Cast(); + + return _activator(result2); + } + + return base.ConvertFrom(culture, value); + } + + public override object ConvertTo(CultureInfo culture, string format, object value, Type to) + { + if (to == typeof(string)) + { + string result = string.Empty; + var enumerable = value as IEnumerable; + + if (enumerable != null) + { + // 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)); + result += str + ","; + } + + result = result.TrimEnd(','); + } + + return result; + } + + return base.ConvertTo(culture, format, value, to); + } + + protected virtual string[] GetStringArray(string input) + { + if (!String.IsNullOrEmpty(input)) + { + var splitChar = '|'; + + if (input.IndexOf(splitChar) < 0) + { + if (input.IndexOf(';') > -1) + { + splitChar = ';'; + } + else if (input.IndexOf(',') > -1) + { + splitChar = ','; + } + } + + var result = input.Split(new char[] { splitChar }, StringSplitOptions.RemoveEmptyEntries); + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + Array.ForEach(result, s => s.Trim()); + return result; + } + + return new string[0]; + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ITypeConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ITypeConverter.cs new file mode 100644 index 0000000000..c76ecba218 --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ITypeConverter.cs @@ -0,0 +1,83 @@ +using System; +using System.Globalization; + +namespace SmartStore.ComponentModel +{ + /// + /// Converts objects. + /// + public interface ITypeConverter + { + /// + /// Returns whether this converter can convert an object of the given type to the type of this converter. + /// + /// A Type that represents the type you want to convert from. + /// true if this converter can perform the conversion; otherwise, false. + bool CanConvertFrom(Type type); + + /// + /// Returns whether this converter can convert the object to the specified type. + /// + /// A Type that represents the type you want to convert to. + /// true if this converter can perform the conversion; otherwise, false. + bool CanConvertTo(Type type); + + /// + /// Converts the given value to the type of this converter. + /// + /// The to use as the current culture. If null is passed, the invariant culture is assumed. + /// The object to convert. + /// An object that represents the converted value. + object ConvertFrom(CultureInfo culture, object value); + + /// + /// Converts the given value object to the specified type, using the arguments. + /// + /// The to use as the current culture. If null is passed, the invariant culture is assumed. + /// A standard or custom format expression. + /// The object to convert. + /// The type to convert the value parameter to. + /// An Object that represents the converted value. + object ConvertTo(CultureInfo culture, string format, object value, Type to); + } + + public static class ITypeConverterExtensions + { + public static object ConvertFrom(this ITypeConverter converter, object value) + { + return converter.ConvertFrom(CultureInfo.InvariantCulture, value); + } + + public static object ConvertTo(this ITypeConverter converter, object value, Type to) + { + return converter.ConvertTo(CultureInfo.InvariantCulture, null, value, to); + } + + public static object SafeConvert(this ITypeConverter converter, string value) + { + try + { + if (converter != null && value.HasValue() && converter.CanConvertFrom(typeof(string))) + { + return converter.ConvertFrom(value); + } + } + catch (Exception exc) + { + exc.Dump(); + } + + return null; + } + + public static bool IsEqual(this ITypeConverter converter, string value, object compareWith) + { + object convertedObject = converter.SafeConvert(value); + + if (convertedObject != null && compareWith != null) + return convertedObject.Equals(compareWith); + + return false; + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/NullableConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/NullableConverter.cs new file mode 100644 index 0000000000..d11c53409f --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/NullableConverter.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.ComponentModel +{ + [SuppressMessage("ReSharper", "TryCastAlwaysSucceeds")] + public class NullableConverter : TypeConverterBase + { + private readonly bool _underlyingTypeIsConvertible; + + public NullableConverter(Type type) + : base(type) + { + NullableType = type; + UnderlyingType = Nullable.GetUnderlyingType(type); + + if (UnderlyingType == null) + { + throw Error.Argument("type", "Type is not a nullable type."); + } + + _underlyingTypeIsConvertible = typeof(IConvertible).IsAssignableFrom(UnderlyingType) && !UnderlyingType.IsEnum; + UnderlyingTypeConverter = TypeConverterFactory.GetConverter(UnderlyingType); + } + + public Type NullableType + { + get; + private set; + } + + public Type UnderlyingType + { + get; + private set; + } + + public ITypeConverter UnderlyingTypeConverter + { + get; + private set; + } + + public override bool CanConvertFrom(Type type) + { + if (type == this.UnderlyingType) + { + return true; + } + + if (UnderlyingTypeConverter.CanConvertFrom(type)) + { + return true; + } + + if (_underlyingTypeIsConvertible && type != typeof(string) && typeof(IConvertible).IsAssignableFrom(type)) + { + return true; + } + + return false; + } + + public override bool CanConvertTo(Type type) + { + Console.WriteLine("NullableConverter can convert to {0}: {1}".FormatInvariant(type.Name, UnderlyingTypeConverter.CanConvertTo(type))); + + if (type == this.UnderlyingType) + { + return true; + } + + return UnderlyingTypeConverter.CanConvertTo(type); + } + + public override object ConvertFrom(CultureInfo culture, object value) + { + if ((value == null) || (value.GetType() == this.UnderlyingType)) + { + return value; + } + + if ((value is string) && string.IsNullOrEmpty(value as string)) + { + return null; + } + + if (_underlyingTypeIsConvertible && !(value is string) && value is IConvertible) + { + // num > num? + return Convert.ChangeType(value, UnderlyingType, culture); + } + + return UnderlyingTypeConverter.ConvertFrom(culture, value); + } + + public override object ConvertTo(CultureInfo culture, string format, object value, Type to) + { + if ((to == this.UnderlyingType) && this.NullableType.IsInstanceOfType(value)) + { + return value; + } + + if (value == null) + { + if (to == typeof(string)) + { + return string.Empty; + } + } + + return UnderlyingTypeConverter.ConvertTo(culture, format, value, to); + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ProductBundleDataConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ProductBundleDataConverter.cs new file mode 100644 index 0000000000..599431116f --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ProductBundleDataConverter.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml.Serialization; +using SmartStore.Core.Domain.Catalog; + +namespace SmartStore.ComponentModel +{ + [SuppressMessage("ReSharper", "TryCastAlwaysSucceeds")] + public class ProductBundleDataConverter : TypeConverterBase + { + private readonly bool _forList; + + public ProductBundleDataConverter(bool forList) + : base(typeof(object)) + { + _forList = forList; + } + + 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) + { + object result = null; + string str = value as string; + if (!string.IsNullOrEmpty(str)) + { + try + { + using (var tr = new StringReader(str)) + { + var serializer = new XmlSerializer(_forList ? typeof(List) : typeof(ProductBundleItemOrderData)); + result = serializer.Deserialize(tr); + } + } + catch + { + // xml error + } + } + + return result; + } + + return base.ConvertFrom(culture, value); + } + + public override object ConvertTo(CultureInfo culture, string format, object value, Type to) + { + if (to == typeof(string)) + { + if (value != null && (value is ProductBundleItemOrderData || value is IList)) + { + var sb = new StringBuilder(); + using (var tw = new StringWriter(sb)) + { + var serializer = new XmlSerializer(_forList ? typeof(List) : typeof(ProductBundleItemOrderData)); + serializer.Serialize(tw, value); + return sb.ToString(); + } + } + else + { + return string.Empty; + } + } + + return base.ConvertTo(culture, format, value, to); + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ShippingOptionConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ShippingOptionConverter.cs new file mode 100644 index 0000000000..b79c9a5f2f --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ShippingOptionConverter.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Text; +using System.Xml.Serialization; +using SmartStore.Core.Domain.Shipping; + +namespace SmartStore.ComponentModel +{ + [SuppressMessage("ReSharper", "TryCastAlwaysSucceeds")] + public class ShippingOptionConverter : TypeConverterBase + { + private readonly bool _forList; + + public ShippingOptionConverter(bool forList) + : base(typeof(object)) + { + _forList = forList; + } + + 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) + { + object result = null; + string str = value as string; + if (!String.IsNullOrEmpty(str)) + { + try + { + using (var tr = new StringReader(str)) + { + var serializer = new XmlSerializer(_forList ? typeof(List) : typeof(ShippingOption)); + result = serializer.Deserialize(tr); + } + } + catch + { + // xml error + } + } + + return result; + } + + return base.ConvertFrom(culture, value); + } + + public override object ConvertTo(CultureInfo culture, string format, object value, Type to) + { + if (to == typeof(string)) + { + if (value != null && (value is ShippingOption || value is IList)) + { + var sb = new StringBuilder(); + using (var tw = new StringWriter(sb)) + { + var serializer = new XmlSerializer(_forList ? typeof(List) : typeof(ShippingOption)); + serializer.Serialize(tw, value); + return sb.ToString(); + } + } + else + { + return string.Empty; + } + } + + return base.ConvertTo(culture, format, value, to); + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TimeSpanConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TimeSpanConverter.cs new file mode 100644 index 0000000000..656355d8e8 --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TimeSpanConverter.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.ComponentModel +{ + [SuppressMessage("ReSharper", "CanBeReplacedWithTryCastAndCheckForNull")] + public class TimeSpanConverter : TypeConverterBase + { + public TimeSpanConverter() + : base(typeof(TimeSpan)) + { + } + + public override bool CanConvertFrom(Type type) + { + return type == typeof(string) + || type == typeof(DateTime) + || base.CanConvertFrom(type); + } + + public override object ConvertFrom(CultureInfo culture, object value) + { + if (value is DateTime) + { + var time = (DateTime)value; + return new TimeSpan(time.Ticks); + } + + if (value is string) + { + var str = (string)value; + + TimeSpan span; + if (TimeSpan.TryParse(str, culture, out span)) + { + return span; + } + + long lng; + if (long.TryParse(str, NumberStyles.None, culture, out lng)) + { + return new TimeSpan(lng.FromUnixTime().Ticks); + } + + double dbl; + if (double.TryParse(str, NumberStyles.None, culture, out dbl)) + { + return new TimeSpan(DateTime.FromOADate(dbl).Ticks); + } + } + + try + { + return (TimeSpan)System.Convert.ChangeType(value, typeof(TimeSpan), culture); + } + catch { } + + return base.ConvertFrom(culture, value); + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterAdapter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterAdapter.cs new file mode 100644 index 0000000000..b7ed897169 --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterAdapter.cs @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel; +using System.Globalization; + +namespace SmartStore.ComponentModel +{ + internal class TypeConverterAdapter : TypeConverterBase + { + private readonly TypeConverter _converter; + + public TypeConverterAdapter(TypeConverter converter) + : base(typeof(object)) + { + _converter = converter; + } + + public override bool CanConvertFrom(Type type) + { + return _converter != null && _converter.CanConvertFrom(type); + } + + public override bool CanConvertTo(Type type) + { + return _converter != null && _converter.CanConvertTo(type); + } + + public override object ConvertFrom(CultureInfo culture, object value) + { + return _converter.ConvertFrom(null, culture, value); + } + + public override object ConvertTo(CultureInfo culture, string format, object value, Type to) + { + return _converter.ConvertTo(null, culture, value, to); + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterBase.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterBase.cs new file mode 100644 index 0000000000..958cd9274c --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterBase.cs @@ -0,0 +1,68 @@ +using System; +using System.ComponentModel; +using System.Globalization; + +namespace SmartStore.ComponentModel +{ + public abstract class TypeConverterBase : ITypeConverter + { + private readonly Lazy _systemConverter; + private readonly Type _type; + + protected TypeConverterBase(Type type) + { + Guard.ArgumentNotNull(() => type); + + _type = type; + _systemConverter = new Lazy(() => TypeDescriptor.GetConverter(type), true); + } + + public TypeConverter SystemConverter + { + get + { + if (_type == typeof(object)) + { + return null; + } + + return _systemConverter.Value; + } + } + + public virtual bool CanConvertFrom(Type type) + { + return SystemConverter != null && SystemConverter.CanConvertFrom(type); + } + + public virtual bool CanConvertTo(Type type) + { + return type == typeof(string) || (SystemConverter != null && SystemConverter.CanConvertTo(type)); + } + + public virtual object ConvertFrom(CultureInfo culture, object value) + { + if (SystemConverter != null) + { + return SystemConverter.ConvertFrom(null, culture, value); + } + + throw Error.InvalidCast(value.GetType(), _type); + } + + public virtual object ConvertTo(CultureInfo culture, string format, object value, Type to) + { + if (SystemConverter != null) + { + return SystemConverter.ConvertTo(null, culture, value, to); + } + + if (value == null) + { + return string.Empty; + } + + return value.ToString(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterFactory.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterFactory.cs new file mode 100644 index 0000000000..4f25272dd8 --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterFactory.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Shipping; + +namespace SmartStore.ComponentModel +{ + public static class TypeConverterFactory + { + private static readonly ConcurrentDictionary _typeConverters = new ConcurrentDictionary(); + + static TypeConverterFactory() + { + CreateDefaultConverters(); + } + + private static void CreateDefaultConverters() + { + _typeConverters.TryAdd(typeof(DateTime), new DateTimeConverter()); + _typeConverters.TryAdd(typeof(TimeSpan), new TimeSpanConverter()); + _typeConverters.TryAdd(typeof(bool), new BooleanConverter( + new [] { "yes", "y", "on", "wahr" }, + new [] { "no", "n", "off", "falsch" })); + + ITypeConverter converter = new ShippingOptionConverter(true); + _typeConverters.TryAdd(typeof(IList), converter); + _typeConverters.TryAdd(typeof(List), converter); + _typeConverters.TryAdd(typeof(ShippingOption), new ShippingOptionConverter(false)); + + converter = new ProductBundleDataConverter(true); + _typeConverters.TryAdd(typeof(IList), converter); + _typeConverters.TryAdd(typeof(List), converter); + _typeConverters.TryAdd(typeof(ProductBundleItemOrderData), new ProductBundleDataConverter(false)); + } + + public static void RegisterConverter(ITypeConverter typeConverter) + { + RegisterConverter(typeof(T), typeConverter); + } + + public static void RegisterConverter(Type type, ITypeConverter typeConverter) + { + Guard.ArgumentNotNull(() => type); + Guard.ArgumentNotNull(() => typeConverter); + + _typeConverters.TryAdd(type, typeConverter); + } + + public static ITypeConverter RemoveConverter(ITypeConverter typeConverter) + { + return RemoveConverter(typeof(T)); + } + + public static ITypeConverter RemoveConverter(Type type) + { + Guard.ArgumentNotNull(() => type); + + ITypeConverter converter = null; + _typeConverters.TryRemove(type, out converter); + return converter; + } + + public static ITypeConverter GetConverter() + { + return GetConverter(typeof(T)); + } + + public static ITypeConverter GetConverter(object component) + { + Guard.ArgumentNotNull(() => component); + + return GetConverter(component.GetType()); + } + + public static ITypeConverter GetConverter(Type type) + { + Guard.ArgumentNotNull(() => type); + + ITypeConverter converter; + if (_typeConverters.TryGetValue(type, out converter)) + { + return converter; + } + + var isGenericType = type.IsGenericType; + if (isGenericType) + { + var definition = type.GetGenericTypeDefinition(); + + // Nullables + if (definition == typeof(Nullable<>)) + { + converter = new NullableConverter(type); + RegisterConverter(type, converter); + return converter; + } + + // Sequence types + var genericArgs = type.GetGenericArguments(); + var isEnumerable = genericArgs.Length == 1 && type.IsSubClass(typeof(IEnumerable<>)); + if (isEnumerable) + { + converter = (ITypeConverter)Activator.CreateInstance(typeof(EnumerableConverter<>).MakeGenericType(genericArgs[0]), type); + RegisterConverter(type, converter); + return converter; + } + } + + // default fallback + converter = new TypeConverterAdapter(TypeDescriptor.GetConverter(type)); + RegisterConverter(type, converter); + return converter; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Data/DbContextScope.cs b/src/Libraries/SmartStore.Core/Data/DbContextScope.cs index c778b7270a..93a7552c50 100644 --- a/src/Libraries/SmartStore.Core/Data/DbContextScope.cs +++ b/src/Libraries/SmartStore.Core/Data/DbContextScope.cs @@ -1,23 +1,40 @@ using System; using SmartStore.Core.Infrastructure; +using System.Linq.Expressions; +using System.Collections.Generic; +using System.Linq; namespace SmartStore.Core.Data { public class DbContextScope : IDisposable { - private readonly bool _autoDetectChangesEnabled; + private readonly IDbContext _ctx; + private readonly bool _autoDetectChangesEnabled; private readonly bool _proxyCreationEnabled; private readonly bool _validateOnSaveEnabled; private readonly bool _forceNoTracking; - private readonly IDbContext _ctx; + private readonly bool _hooksEnabled; + private readonly bool _autoCommit; + private readonly bool _lazyLoading; + - public DbContextScope(IDbContext ctx = null, bool? autoDetectChanges = null, bool? proxyCreation = null, bool? validateOnSave = null, bool? forceNoTracking = null) + public DbContextScope(IDbContext ctx = null, + bool? autoDetectChanges = null, + bool? proxyCreation = null, + bool? validateOnSave = null, + bool? forceNoTracking = null, + bool? hooksEnabled = null, + bool? autoCommit = null, + bool? lazyLoading = null) { _ctx = ctx ?? EngineContext.Current.Resolve(); _autoDetectChangesEnabled = _ctx.AutoDetectChangesEnabled; _proxyCreationEnabled = _ctx.ProxyCreationEnabled; _validateOnSaveEnabled = _ctx.ValidateOnSaveEnabled; _forceNoTracking = _ctx.ForceNoTracking; + _hooksEnabled = _ctx.HooksEnabled; + _autoCommit = _ctx.AutoCommitEnabled; + _lazyLoading = _ctx.LazyLoadingEnabled; if (autoDetectChanges.HasValue) _ctx.AutoDetectChangesEnabled = autoDetectChanges.Value; @@ -30,7 +47,42 @@ public DbContextScope(IDbContext ctx = null, bool? autoDetectChanges = null, boo if (forceNoTracking.HasValue) _ctx.ForceNoTracking = forceNoTracking.Value; - } + + if (hooksEnabled.HasValue) + _ctx.HooksEnabled = hooksEnabled.Value; + + if (autoCommit.HasValue) + _ctx.AutoCommitEnabled = autoCommit.Value; + + if (lazyLoading.HasValue) + _ctx.LazyLoadingEnabled = lazyLoading.Value; + } + + public IDbContext DbContext + { + get { return _ctx; } + } + + public void LoadCollection( + TEntity entity, + Expression>> navigationProperty, + bool force = false, + Func, IQueryable> queryAction = null) + where TEntity : BaseEntity + where TCollection : BaseEntity + { + _ctx.LoadCollection(entity, navigationProperty, force, queryAction); + } + + public void LoadReference( + TEntity entity, + Expression> navigationProperty, + bool force = false) + where TEntity : BaseEntity + where TProperty : BaseEntity + { + _ctx.LoadReference(entity, navigationProperty, force); + } public int Commit() { @@ -43,6 +95,9 @@ public void Dispose() _ctx.ProxyCreationEnabled = _proxyCreationEnabled; _ctx.ValidateOnSaveEnabled = _validateOnSaveEnabled; _ctx.ForceNoTracking = _forceNoTracking; + _ctx.HooksEnabled = _hooksEnabled; + _ctx.AutoCommitEnabled = _autoCommit; + _ctx.LazyLoadingEnabled = _lazyLoading; } } diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/HooksEventConsumer.cs b/src/Libraries/SmartStore.Core/Data/Hooks/HooksEventConsumer.cs index def515b630..2372dbba00 100644 --- a/src/Libraries/SmartStore.Core/Data/Hooks/HooksEventConsumer.cs +++ b/src/Libraries/SmartStore.Core/Data/Hooks/HooksEventConsumer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using Autofac.Features.Metadata; @@ -50,9 +51,9 @@ public HooksEventConsumer( public void HandleEvent(PreActionHookEvent eventMessage) { - var entries = eventMessage.ModifiedEntries; + var entries = eventMessage.ModifiedEntries.ToArray(); - if (!entries.Any() || !_preHooks.Any()) + if (entries.Length == 0 || !_preHooks.Any()) return; foreach (var entry in entries) @@ -64,7 +65,11 @@ public void HandleEvent(PreActionHookEvent eventMessage) if (hook.HookStates == e.PreSaveState && hook.RequiresValidation == eventMessage.RequiresValidation) { var metadata = new HookEntityMetadata(e.PreSaveState); - hook.HookObject(e.Entity, metadata); + using (var scope = new DbContextScope(hooksEnabled: false)) + { + // dead end: don't let hooks call hooks again + hook.HookObject(e.Entity, metadata); + } if (metadata.HasStateChanged) { @@ -75,6 +80,7 @@ public void HandleEvent(PreActionHookEvent eventMessage) } } + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] private IEnumerable GetPreHookInstancesFor(Type hookedType) { if (_preHooksCache.ContainsKey(hookedType)) @@ -89,9 +95,9 @@ private IEnumerable GetPreHookInstancesFor(Type hookedType) public void HandleEvent(PostActionHookEvent eventMessage) { - var entries = eventMessage.ModifiedEntries; + var entries = eventMessage.ModifiedEntries.ToArray(); - if (!entries.Any() || !_postHooks.Any()) + if (entries.Length == 0 || !_postHooks.Any()) return; foreach (var entry in entries) @@ -103,7 +109,11 @@ public void HandleEvent(PostActionHookEvent eventMessage) if (hook.HookStates == e.PreSaveState) { var metadata = new HookEntityMetadata(e.PreSaveState); - hook.HookObject(e.Entity, metadata); + using (var scope = new DbContextScope(hooksEnabled: false)) + { + // dead end: don't let hooks call hooks again + hook.HookObject(e.Entity, metadata); + } } } } diff --git a/src/Libraries/SmartStore.Core/Data/IDbContext.cs b/src/Libraries/SmartStore.Core/Data/IDbContext.cs index b0a2e1506d..cb276a03ac 100644 --- a/src/Libraries/SmartStore.Core/Data/IDbContext.cs +++ b/src/Libraries/SmartStore.Core/Data/IDbContext.cs @@ -1,9 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.Entity; +using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; -using SmartStore.Core; namespace SmartStore.Core.Data { @@ -39,12 +41,12 @@ IList ExecuteStoredProcedureList(string commandText, params ob /// Executes sql by using SQL-Server Management Objects which supports GO statements. int ExecuteSqlThroughSmo(string sql); - // codehint: sm-add (required for UoW implementation) string Alias { get; } // increasing performance on bulk operations bool ProxyCreationEnabled { get; set; } - bool AutoDetectChangesEnabled { get; set; } + bool LazyLoadingEnabled { get; set; } + bool AutoDetectChangesEnabled { get; set; } bool ValidateOnSaveEnabled { get; set; } bool HooksEnabled { get; set; } bool HasChanges { get; } @@ -59,6 +61,12 @@ IList ExecuteStoredProcedureList(string commandText, params ob /// bool ForceNoTracking { get; set; } + /// + /// Gets or sets a value indicating whether database write operations + /// originating from repositories should be committed immediately. + /// + bool AutoCommitEnabled { get; set; } + /// /// Gets a list of modified properties for the specified entity /// @@ -77,26 +85,29 @@ IList ExecuteStoredProcedureList(string commandText, params ob /// Type of entity /// The entity instance to attach /// true when the entity is attched already, false otherwise - bool IsAttached(TEntity entity) where TEntity : BaseEntity, new(); + bool IsAttached(TEntity entity) where TEntity : BaseEntity; - /// - /// Detaches an entity from the current object context if it's attached - /// - /// Type of entity - /// The entity instance to detach - void DetachEntity(TEntity entity) where TEntity : BaseEntity, new(); + /// + /// Attaches an entity to the context or returns an already attached entity (if it was already attached) + /// + /// Type of entity + /// Entity + /// Attached entity + TEntity Attach(TEntity entity) where TEntity : BaseEntity; /// - /// Detaches an entity from the current object context + /// Detaches an entity from the current object context if it's attached /// + /// Type of entity /// The entity instance to detach - void Detach(object entity); + void DetachEntity(TEntity entity) where TEntity : BaseEntity; /// - /// Detaches all entities from the current object context + /// Detaches all entities of type TEntity from the current object context /// + /// When true, only entities in unchanged state get detached. /// The count of detached entities - int DetachAll(); + int DetachEntities(bool unchangedEntitiesOnly = true) where TEntity : class; /// /// Change the state of an entity object @@ -104,15 +115,15 @@ IList ExecuteStoredProcedureList(string commandText, params ob /// Type of entity /// The entity instance /// The new state - void ChangeState(TEntity entity, System.Data.Entity.EntityState newState); + void ChangeState(TEntity entity, System.Data.Entity.EntityState newState) where TEntity : BaseEntity; /// - /// Changes the object state to unchanged + /// Reloads the entity from the database overwriting any property values with values from the database. + /// The entity will be in the Unchanged state after calling this method. /// /// Type of entity /// The entity instance - /// true on success, false on failure - bool SetToUnchanged(TEntity entity); + void ReloadEntity(TEntity entity) where TEntity : BaseEntity; /// /// Begins a transaction on the underlying store connection using the specified isolation level @@ -127,4 +138,5 @@ IList ExecuteStoredProcedureList(string commandText, params ob /// the external transaction void UseTransaction(DbTransaction transaction); } + } diff --git a/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs new file mode 100644 index 0000000000..f3afa01d07 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core; +using SmartStore.Core.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Data.Entity; + +namespace SmartStore +{ + public static class IDbContextExtensions + { + + /// + /// Detaches all entities from the current object context + /// + /// When true, only entities in unchanged state get detached. + /// The count of detached entities + public static int DetachAll(this IDbContext ctx, bool unchangedEntitiesOnly = true) + { + return ctx.DetachEntities(unchangedEntitiesOnly); + } + + public static void DetachEntities(this IDbContext ctx, IEnumerable entities) where TEntity : BaseEntity + { + Guard.ArgumentNotNull(() => ctx); + + entities.Each(x => ctx.DetachEntity(x)); + } + + /// + /// Changes the object state to unchanged + /// + /// Type of entity + /// + /// The entity instance + /// true on success, false on failure + public static bool SetToUnchanged(this IDbContext ctx, TEntity entity) where TEntity : BaseEntity + { + try + { + ctx.ChangeState(entity, System.Data.Entity.EntityState.Unchanged); + return true; + } + catch (Exception ex) + { + ex.Dump(); + return false; + } + } + + public static IQueryable QueryForCollection( + this IDbContext ctx, + TEntity entity, + Expression>> navigationProperty) + where TEntity : BaseEntity + where TCollection : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotNull(() => navigationProperty); + + var dbContext = ctx as DbContext; + if (dbContext == null) + { + throw new NotSupportedException("The IDbContext instance does not inherit from DbContext (EF)"); + } + + return dbContext.Entry(entity).Collection(navigationProperty).Query(); + } + + public static IQueryable QueryForReference( + this IDbContext ctx, + TEntity entity, + Expression> navigationProperty) + where TEntity : BaseEntity + where TProperty : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotNull(() => navigationProperty); + + var dbContext = ctx as DbContext; + if (dbContext == null) + { + throw new NotSupportedException("The IDbContext instance does not inherit from DbContext (EF)"); + } + + return dbContext.Entry(entity).Reference(navigationProperty).Query(); + } + + public static void LoadCollection( + this IDbContext ctx, + TEntity entity, + Expression>> navigationProperty, + bool force = false, + Func, IQueryable> queryAction = null) + where TEntity : BaseEntity + where TCollection : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotNull(() => navigationProperty); + + var dbContext = ctx as DbContext; + if (dbContext == null) + { + throw new NotSupportedException("The IDbContext instance does not inherit from DbContext (EF)"); + } + + var entry = dbContext.Entry(entity); + var collection = entry.Collection(navigationProperty); + + if (force) + { + collection.IsLoaded = false; + } + + if (!collection.IsLoaded) + { + if (queryAction != null || ctx.ForceNoTracking) + { + var query = !ctx.ForceNoTracking + ? collection.Query() + : collection.Query().AsNoTracking(); + + var myQuery = queryAction != null + ? queryAction(query) + : query; + + collection.CurrentValue = myQuery.ToList(); + } + else + { + collection.Load(); + } + + collection.IsLoaded = true; + } + } + + public static void LoadReference( + this IDbContext ctx, + TEntity entity, + Expression> navigationProperty, + bool force = false) + where TEntity : BaseEntity + where TProperty : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotNull(() => navigationProperty); + + var dbContext = ctx as DbContext; + if (dbContext == null) + { + throw new NotSupportedException("The IDbContext instance does not inherit from DbContext (EF)"); + } + + var entry = dbContext.Entry(entity); + var reference = entry.Reference(navigationProperty); + + if (force) + { + reference.IsLoaded = false; + } + + if (!reference.IsLoaded) + { + reference.Load(); + reference.IsLoaded = true; + } + } + + public static void AttachRange(this IDbContext ctx, IEnumerable entities) where TEntity : BaseEntity + { + entities.Each(x => ctx.Attach(x)); + } + + } +} diff --git a/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs b/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs new file mode 100644 index 0000000000..2289488b95 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Data.Entity; +using System.Linq.Expressions; +using SmartStore.Core; + +namespace SmartStore +{ + public static class IQueryableExtensions + { + + /// + /// Instructs the repository to eager load entities that may be in the type's association path. + /// + /// A previously created query object which the expansion should be applied to. + /// + /// The path of the child entities to eager load. + /// Deeper paths can be specified by separating the path with dots. + /// + /// 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"); + + return query.Include(path); + } + + /// + /// Instructs the repository to eager load entities that may be in the type's association path. + /// + /// A previously created query object which the expansion should be applied to. + /// The path of the child entities to eager load. + /// 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"); + + return query.Include(path); + } + + } +} diff --git a/src/Libraries/SmartStore.Core/Data/IRepository.cs b/src/Libraries/SmartStore.Core/Data/IRepository.cs index e82d1fb74e..cae856f654 100644 --- a/src/Libraries/SmartStore.Core/Data/IRepository.cs +++ b/src/Libraries/SmartStore.Core/Data/IRepository.cs @@ -39,6 +39,13 @@ public partial interface IRepository where T : BaseEntity /// The resolved entity T GetById(object id); + /// + /// Attaches an entity to the context + /// + /// The entity to attach + /// The entity + T Attach(T entity); + /// /// Marks the entity instance to be saved to the store. /// @@ -60,6 +67,13 @@ public partial interface IRepository where T : BaseEntity /// Implementors should delegate this to the current void Update(T entity); + /// + /// Marks the changes of existing entities to be saved to the store. + /// + /// A list of entity instances that should be updated in the database. + /// Implementors should delegate this to the current + void UpdateRange(IEnumerable entities); + /// /// Marks an existing entity to be deleted from the store. /// @@ -67,23 +81,17 @@ public partial interface IRepository where T : BaseEntity /// Implementors should delegate this to the current void Delete(T entity); - /// - /// Instructs the repository to eager load entities that may be in the type's association path. - /// - /// A previously created query object which the expansion should be applied to. - /// - /// The path of the child entities to eager load. - /// Deeper paths can be specified by separating the path with dots. - /// - /// A new query object to which the expansion was applied. + /// + /// Marks existing entities to be deleted from the store. + /// + /// A list of entity instances that should be deleted from the database. + /// Implementors should delegate this to the current + void DeleteRange(IEnumerable entities); + + [Obsolete("Use the extension method from 'SmartStore.Core, SmartStore.Core.Data' instead")] IQueryable Expand(IQueryable query, string path); - /// - /// Instructs the repository to eager load entities that may be in the type's association path. - /// - /// A previously created query object which the expansion should be applied to. - /// The path of the child entities to eager load. - /// A new query object to which the expansion was applied. + [Obsolete("Use the extension method from 'SmartStore.Core, SmartStore.Core.Data' instead")] IQueryable Expand(IQueryable query, Expression> path); /// @@ -118,6 +126,10 @@ public partial interface IRepository where T : BaseEntity /// Gets or sets a value indicating whether database write operations /// such as insert, delete or update should be committed immediately. /// - bool AutoCommitEnabled { get; set; } + /// + /// Set this to true or false to supersede the global AutoCommitEnabled + /// on level for this repository instance only. + /// + bool? AutoCommitEnabled { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Data/Impex/ImportModeFlags.cs b/src/Libraries/SmartStore.Core/Data/Impex/ImportModeFlags.cs deleted file mode 100644 index f7c40437d9..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Impex/ImportModeFlags.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace SmartStore.Core.Data -{ - - [Flags] - public enum ImportModeFlags - { - Insert = 1, - Update = 2 - } - -} diff --git a/src/Libraries/SmartStore.Core/Data/Impex/ImportProgressInfo.cs b/src/Libraries/SmartStore.Core/Data/Impex/ImportProgressInfo.cs deleted file mode 100644 index 9ddf010991..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Impex/ImportProgressInfo.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SmartStore.Core.Data -{ - - public class ImportProgressInfo - { - - public int TotalRecords - { - get; - set; - } - - public int TotalProcessed - { - get; - set; - } - - public double ProcessedPercent - { - get - { - if (TotalRecords == 0) - return 0; - - return ((double)TotalProcessed / (double)TotalRecords) * 100; - } - } - - public int NewRecords - { - get; - set; - } - - public int ModifiedRecords - { - get; - set; - } - - public TimeSpan ElapsedTime - { - get; - set; - } - - public int TotalWarnings - { - get; - set; - } - - public int TotalErrors - { - get; - set; - } - } - -} diff --git a/src/Libraries/SmartStore.Core/Data/Impex/ImportResult.cs b/src/Libraries/SmartStore.Core/Data/Impex/ImportResult.cs deleted file mode 100644 index 1856607135..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Impex/ImportResult.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace SmartStore.Core.Data -{ - - public class ImportResult - { - - public ImportResult() - { - this.Messages = new List(); - this.StartDateUtc = DateTime.UtcNow; - } - - public DateTime StartDateUtc - { - get; - set; - } - - public DateTime EndDateUtc - { - get; - set; - } - - public int TotalRecords - { - get; - set; - } - - public int NewRecords - { - get; - set; - } - - public int ModifiedRecords - { - get; - set; - } - - public int AffectedRecords - { - get { return NewRecords + ModifiedRecords; } - } - - public bool Cancelled - { - get; - set; - } - - public ImportMessage AddInfo(string message, ImportRowInfo affectedRow = null, string affectedField = null) - { - return this.AddMessage(message, ImportMessageType.Info, affectedRow, affectedField); - } - - public ImportMessage AddWarning(string message, ImportRowInfo affectedRow = null, string affectedField = null) - { - return this.AddMessage(message, ImportMessageType.Warning, affectedRow, affectedField); - } - - public ImportMessage AddError(string message, ImportRowInfo affectedRow = null, string affectedField = null) - { - return this.AddMessage(message, ImportMessageType.Error, affectedRow, affectedField); - } - - public ImportMessage AddError(Exception exception, int? affectedBatch = null, string stage = null) - { - var ex = exception; - while (true) - { - if (ex.InnerException == null) - break; - ex = ex.InnerException; - } - - var prefix = new List(); - if (affectedBatch.HasValue) - { - prefix.Add("Batch: " + affectedBatch.Value); - } - if (stage.HasValue()) - { - prefix.Add("Stage: " + stage); - } - - string msg = string.Empty; - if (prefix.Any()) - { - msg = "[{0}] ".FormatCurrent(String.Join(", ", prefix)); - } - - msg += ex.Message; - - return this.AddMessage(msg, ImportMessageType.Error); - } - - public ImportMessage AddMessage(string message, ImportMessageType severity, ImportRowInfo affectedRow = null, string affectedField = null) - { - var msg = new ImportMessage(message, severity); - msg.AffectedItem = affectedRow; - msg.AffectedField = affectedField; - this.Messages.Add(msg); - return msg; - } - - public IList Messages - { - get; - private set; - } - - public bool HasWarnings - { - get { return this.Messages.Any(x => x.MessageType == ImportMessageType.Warning); } - } - - public bool HasErrors - { - get { return this.Messages.Any(x => x.MessageType == ImportMessageType.Error); } - } - - } - -} diff --git a/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs b/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs index 43c1308998..ed034c1730 100644 --- a/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs +++ b/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Text; @@ -9,22 +10,6 @@ namespace SmartStore.Core.Data public static class RepositoryExtensions { - - public static IEnumerable LoadAll(this IRepository rs) where T : BaseEntity - { - return rs.Table.AsEnumerable(); - } - - public static IEnumerable Where(this IRepository rs, Func predicate) where T : BaseEntity - { - return rs.Table.Where(predicate); - } - - public static T GetSingle(this IRepository rs, Func predicate) where T : BaseEntity - { - return rs.Table.SingleOrDefault(predicate); - } - public static T GetFirst(this IRepository rs, Func predicate) where T : BaseEntity { return rs.Table.FirstOrDefault(predicate); @@ -42,15 +27,77 @@ public static IEnumerable GetMany(this IRepository rs, IEnumerable } } - public static void Delete(this IRepository rs, object id) where T : BaseEntity + public static void Delete(this IRepository rs, int id) where T : BaseEntity { - T entityToDelete = rs.GetById(id); - if (entityToDelete != null) - { - rs.Delete(entityToDelete); - } + Guard.ArgumentNotZero(id, "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); + + ids.Each(id => rs.Delete(id)); + } + + /// + /// Truncates the table + /// + /// 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 static int DeleteAll(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.Chunk(500)) + { + rs.DeleteRange(chunk.ToList()); + count += rs.Context.SaveChanges(); + } + } + else + { + var ids = query.Select(x => new { Id = x.Id }).ToList(); + foreach (var chunk in ids.Chunk(500)) + { + rs.DeleteRange(chunk.Select(x => x.Id)); + count += rs.Context.SaveChanges(); + } + } + } + + return count; + } + public static IQueryable Get( this IRepository rs, Expression> predicate = null, @@ -64,10 +111,9 @@ public static IQueryable Get( query = query.Where(predicate); } - foreach (var includeProperty in includeProperties.Split - (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { - query = rs.Expand(query, includeProperty.Trim()); + query = query.Expand(includeProperty.Trim()); } if (orderBy != null) diff --git a/src/Libraries/SmartStore.Core/Data/SqlFileTokenizer.cs b/src/Libraries/SmartStore.Core/Data/SqlFileTokenizer.cs index 343dad1287..e85c9bac4c 100644 --- a/src/Libraries/SmartStore.Core/Data/SqlFileTokenizer.cs +++ b/src/Libraries/SmartStore.Core/Data/SqlFileTokenizer.cs @@ -49,10 +49,10 @@ public IEnumerable Tokenize() using (var reader = ReadSqlFile()) { - var statement = string.Empty; + string statement; while ((statement = ReadNextSqlStatement(reader)) != null) { - yield return statement; + yield return statement.EmptyNull(); } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Blogs/BlogExtensions.cs b/src/Libraries/SmartStore.Core/Domain/Blogs/BlogExtensions.cs index d28f41b9c9..e5cbfc2f5c 100644 --- a/src/Libraries/SmartStore.Core/Domain/Blogs/BlogExtensions.cs +++ b/src/Libraries/SmartStore.Core/Domain/Blogs/BlogExtensions.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace SmartStore.Core.Domain.Blogs { public static class BlogExtensions { - public static string[] ParseTags(this BlogPost blogPost) + [SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")] + public static string[] ParseTags(this BlogPost blogPost) { if (blogPost == null) throw new ArgumentNullException("blogPost"); @@ -13,8 +15,8 @@ public static string[] ParseTags(this BlogPost blogPost) var parsedTags = new List(); if (!String.IsNullOrEmpty(blogPost.Tags)) { - string[] tags2 = blogPost.Tags.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - foreach (string tag2 in tags2) + var tags2 = blogPost.Tags.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var tag2 in tags2) { var tmp = tag2.Trim(); if (!String.IsNullOrEmpty(tmp)) diff --git a/src/Libraries/SmartStore.Core/Domain/Blogs/BlogSettings.cs b/src/Libraries/SmartStore.Core/Domain/Blogs/BlogSettings.cs index 8a1b5ca08e..e0c7492934 100644 --- a/src/Libraries/SmartStore.Core/Domain/Blogs/BlogSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Blogs/BlogSettings.cs @@ -11,6 +11,7 @@ public BlogSettings() PostsPageSize = 10; AllowNotRegisteredUsersToLeaveComments = true; NumberOfTags = 15; + MaxAgeInDays = 180; } /// @@ -38,6 +39,11 @@ public BlogSettings() /// public int NumberOfTags { get; set; } + /// + /// The maximum age of blog items (in days) for RSS feed + /// + public int MaxAgeInDays { get; set; } + /// /// Enable the blog RSS feed link in customers browser address bar /// diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs index 7aba261b34..12f9fa8587 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs @@ -3,6 +3,12 @@ namespace SmartStore.Core.Domain.Catalog { + public enum SubCategoryDisplayType + { + Hide = 0, + AboveProductList = 5, + Bottom = 10 + } public class CatalogSettings : ISettings { @@ -10,6 +16,7 @@ public CatalogSettings() { FileUploadAllowedExtensions = new List(); AllowProductSorting = true; + DefaultSortOrder = ProductSortingEnum.Position; AllowProductViewModeChanging = true; DefaultViewMode = "grid"; CategoryBreadcrumbEnabled = true; @@ -26,11 +33,16 @@ public CatalogSettings() CompareProductsEnabled = true; FilterEnabled = true; MaxFilterItemsToDisplay = 4; - ShowSubcategoriesAboveProductLists = true; + SortFilterResultsByMatches = true; + SubCategoryDisplayType = SubCategoryDisplayType.AboveProductList; ProductSearchAutoCompleteEnabled = true; + ShowProductImagesInSearchAutoComplete = true; ProductSearchAutoCompleteNumberOfProducts = 10; ProductSearchTermMinimumLength = 3; NumberOfBestsellersOnHomepage = 6; + ShowManufacturersOnHomepage = true; + ShowManufacturerPictures = false; + ShowManufacturerPicturesInProductDetail = true; SearchPageProductsPerPage = 6; ProductsAlsoPurchasedEnabled = true; ProductsAlsoPurchasedNumber = 6; @@ -53,6 +65,7 @@ public CatalogSettings() ShowProductReviewsInProductDetail = true; HtmlTextCollapsedHeight = 260; MostRecentlyUsedCategoriesMaxSize = 6; + MostRecentlyUsedManufacturersMaxSize = 4; } /// @@ -115,13 +128,18 @@ public CatalogSettings() /// public bool AllowProductSorting { get; set; } + /// + /// Gets or sets the default sort order in product lists + /// + public ProductSortingEnum DefaultSortOrder { get; set; } + /// /// Gets or sets a value indicating whether customers are allowed to change product view mode /// public bool AllowProductViewModeChanging { get; set; } /// - /// Gets or sets a value indicating whether customers are allowed to change product view mode + /// Gets or sets the default view mode for product lists /// public string DefaultViewMode { get; set; } @@ -159,11 +177,16 @@ public CatalogSettings() /// Gets or sets a value indicating whether all filter criterias should be expanded /// public bool ExpandAllFilterCriteria { get; set; } - - /// - /// Gets or sets a value indicating whether sub categories above product lists are enabled - /// - public bool ShowSubcategoriesAboveProductLists { get; set; } + + /// + /// Gets or sets a value indicating whether filter results should be sorted by matches + /// + public bool SortFilterResultsByMatches { 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 a 'Share button' is enabled @@ -276,9 +299,39 @@ public CatalogSettings() public int NumberOfBestsellersOnHomepage { get; set; } /// - /// Gets or sets a number of products per page on search products page + /// Gets or sets a value indicating whether to show manufacturers on home page + /// + public bool ShowManufacturersOnHomepage { get; set; } + + /// + /// Gets or sets a value indicating whether to show manufacturer pictures or names on home page /// - public int SearchPageProductsPerPage { get; set; } + public bool ShowManufacturerPictures { get; set; } + + /// + /// Gets or sets a value indicating whether to hide manufacturer pictures in product detail + /// + public bool ShowManufacturerPicturesInProductDetail { get; set; } + + /// + /// Gets or sets a value indicating whether to hide maufacturer default pictures + /// + public bool HideManufacturerDefaultPictures { 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 @@ -340,10 +393,20 @@ public CatalogSettings() public bool SuppressSkuSearch { get; set; } - /// - /// Gets or sets the available customer selectable default page size options - /// - public string DefaultPageSizeOptions { get; set; } + /// + /// Gets or sets a value indicating whether to search long description + /// + public bool SearchDescriptions { get; set; } + + /// + /// Gets or sets the available customer selectable default page size options + /// + public string DefaultPageSizeOptions { get; set; } + + /// + /// Gets or sets the price display type for prices in product lists + /// + public PriceDisplayType PriceDisplayType { get; set; } /// /// Gets or sets a value indicating whether to include "Short description" in compare products @@ -431,5 +494,10 @@ public CatalogSettings() /// Gets or sets how many items to display maximally in the most recently used category list /// public int MostRecentlyUsedCategoriesMaxSize { get; set; } + + /// + /// Gets or sets how many items to display maximally in the most recently used manufacturer list + /// + public int MostRecentlyUsedManufacturersMaxSize { 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 98755be4d1..83c5ef53c6 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics; using System.Runtime.Serialization; using SmartStore.Core.Domain.Discounts; @@ -26,17 +27,28 @@ public partial class Category : BaseEntity, ISoftDeletable, ILocalizedEntity, IS [DataMember] public string Name { get; set; } + /// + /// Gets or sets the full name (category page title) + /// + [DataMember] + public string FullName { get; set; } + /// /// Gets or sets the description /// [DataMember] public string Description { get; set; } + /// + /// Gets or sets a description displayed at the bottom of the category page + /// + [DataMember] + public string BottomDescription { get; set; } + /// /// Gets or sets the category alias /// (an optional key for advanced customization) /// - /// codehint: sm-add [DataMember] public string Alias { get; set; } @@ -143,6 +155,7 @@ public partial class Category : BaseEntity, ISoftDeletable, ILocalizedEntity, IS /// /// Gets or sets a value indicating whether the entity has been deleted /// + [Index] public bool Deleted { get; set; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs index 7aa0222761..88b366c773 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs @@ -4,6 +4,7 @@ using SmartStore.Core.Domain.Stores; using System.Runtime.Serialization; using SmartStore.Core.Domain.Media; +using System.ComponentModel.DataAnnotations.Schema; namespace SmartStore.Core.Domain.Catalog { @@ -100,6 +101,7 @@ public partial class Manufacturer : BaseEntity, ISoftDeletable, ILocalizedEntity /// /// Gets or sets a value indicating whether the entity has been deleted /// + [Index] public bool Deleted { get; set; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/PriceDisplayType.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/PriceDisplayType.cs new file mode 100644 index 0000000000..aeb4c24250 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/PriceDisplayType.cs @@ -0,0 +1,29 @@ + +namespace SmartStore.Core.Domain.Catalog +{ + /// + /// Represents types of product prices to display + /// + public enum PriceDisplayType + { + /// + /// The lowest possible price of a product (default) + /// + LowestPrice = 0, + + /// + /// The product price initially displayed on the product detail page + /// + PreSelectedPrice = 10, + + /// + /// The product price without associated data like discounts, tier prices, attributes or attribute combinations + /// + PriceWithoutDiscountsAndAttributes = 20, + + /// + /// Do not display a product price + /// + Hide = 30 + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs index f6f80f0415..999f66bad3 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics; using System.Runtime.Serialization; using SmartStore.Core.Domain.Directory; @@ -17,7 +18,7 @@ namespace SmartStore.Core.Domain.Catalog /// [DataContract] public partial class Product : BaseEntity, ISoftDeletable, ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported, IMergedData - { + { private ICollection _productCategories; private ICollection _productManufacturers; private ICollection _productPictures; @@ -103,6 +104,12 @@ public partial class Product : BaseEntity, ISoftDeletable, ILocalizedEntity, ISl [DataMember] public bool ShowOnHomePage { get; set; } + /// + /// Gets or sets the display order for homepage products + /// + [DataMember] + public int HomePageDisplayOrder { get; set; } + /// /// Gets or sets the meta keywords /// @@ -184,6 +191,7 @@ public string Sku /// Gets or sets the manufacturer part number /// [DataMember] + [Index] public string ManufacturerPartNumber { [DebuggerStepThrough] @@ -201,6 +209,7 @@ public string ManufacturerPartNumber /// Gets or sets the Global Trade Item Number (GTIN). These identifiers include UPC (in North America), EAN (in Europe), JAN (in Japan), and ISBN (for books). /// [DataMember] + [Index] public string Gtin { [DebuggerStepThrough] @@ -245,7 +254,7 @@ public string Gtin public bool AutomaticallyAddRequiredProducts { get; set; } /// - /// Gets or sets a value indicating whether the product is download + /// Gets or sets a value indicating whether the product is a download /// [DataMember] public bool IsDownload { get; set; } @@ -386,7 +395,7 @@ public int StockQuantity [DebuggerStepThrough] get { - return this.GetMergedDataValue("StockQuantity", _stockQuantity); + return this.GetMergedDataValue("StockQuantity", _stockQuantity); } set { @@ -558,7 +567,7 @@ public decimal Price /// Gets or sets a value indicating whether this product has tier prices configured /// The same as if we run this.TierPrices.Count > 0 /// We use this property for performance optimization: - /// if this property is set to false, then we do not need to load tier prices navifation property + /// if this property is set to false, then we do not need to load tier prices navigation property /// /// [DataMember] @@ -664,6 +673,7 @@ public decimal Height /// /// Gets or sets a value indicating whether the entity has been deleted /// + [Index] public bool Deleted { get; set; } /// @@ -1038,5 +1048,5 @@ public virtual ICollection ProductBundleItems get { return _productBundleItems ?? (_productBundleItems = new HashSet()); } protected set { _productBundleItems = value; } } - } + } } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductBundleData.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductBundleData.cs index 5582074c65..354720c7ea 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductBundleData.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductBundleData.cs @@ -34,58 +34,4 @@ public partial class ProductBundleItemOrderData public string AttributesInfo { get; set; } public bool PerItemShoppingCart { get; set; } } - - public class ProductBundleDataListTypeConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - return true; - - return base.CanConvertFrom(context, sourceType); - } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - if (value is string) - { - List bundleData = null; - string rawValue = value as string; - - if (rawValue.HasValue()) - { - try - { - using (var reader = new StringReader(rawValue)) - { - var xml = new XmlSerializer(typeof(List)); - bundleData = (List)xml.Deserialize(reader); - } - } - catch { } - } - return bundleData; - } - return base.ConvertFrom(context, culture, value); - } - - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) - { - if (destinationType == typeof(string)) - { - var bundleData = value as List; - - if (bundleData == null) - return ""; - - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb)) - { - var xml = new XmlSerializer(typeof(List)); - xml.Serialize(writer, value); - return sb.ToString(); - } - } - return base.ConvertTo(context, culture, value, destinationType); - } - } } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs index 17a29dbab3..8db56d81b4 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs @@ -5,10 +5,14 @@ /// public enum ProductSortingEnum { + /// + /// Initial state + /// + Initial = 0, /// /// Position (display order) /// - Position = 0, + Position = 1, /// /// Name: A to Z /// @@ -32,6 +36,6 @@ public enum ProductSortingEnum /// /// Product creation date /// - CreatedOnAsc = 16 // codehint: sm-add + CreatedOnAsc = 16 } } \ 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 d6af1d213a..84ceca5428 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs @@ -1,5 +1,4 @@ - -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; namespace SmartStore.Core.Domain.Common { @@ -13,6 +12,8 @@ public CommonSettings() SitemapIncludeManufacturers = true; SitemapIncludeTopics = true; FullTextMode = FulltextSearchMode.ExactMatch; + AutoUpdateEnabled = true; + EntityPickerPageSize = 48; } public bool UseSystemEmailForContactUsForm { get; set; } @@ -28,19 +29,25 @@ public CommonSettings() public bool SitemapIncludeTopics { get; set; } /// - /// Gets a sets a value indicating whether to display a warning if java-script is disabled + /// Gets or sets a value indicating whether to display a warning if java-script is disabled /// public bool DisplayJavaScriptDisabledWarning { get; set; } /// - /// Gets a sets a value indicating whether to full-text search is supported + /// Gets or sets a value indicating whether to full-text search is supported /// public bool UseFullTextSearch { get; set; } /// - /// Gets a sets a Full-Text search mode + /// 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; } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Common/PdfSettings.cs b/src/Libraries/SmartStore.Core/Domain/Common/PdfSettings.cs index 45a7c50906..bddfb93963 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/PdfSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/PdfSettings.cs @@ -30,5 +30,15 @@ public PdfSettings() /// Gets or sets a value indicating whether to render order notes in PDf reports /// public bool RenderOrderNotes { get; set; } + + /// + /// Gets or sets a value indicating whether to attach the order PDF to 'Order Placed (customer)' email + /// + public bool AttachOrderPdfToOrderPlacedEmail { get; set; } + + /// + /// Gets or sets a value indicating whether to attach the order PDF to 'Order Completed (customer)' email + /// + public bool AttachOrderPdfToOrderCompletedEmail { 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 578057866b..e55f57bd4f 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/Customer.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/Customer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Runtime.Serialization; using SmartStore.Core.Domain.Common; @@ -103,6 +104,7 @@ public PasswordFormat PasswordFormat /// /// Gets or sets a value indicating whether the customer has been deleted /// + [Index] public bool Deleted { get; set; } /// @@ -115,6 +117,7 @@ public PasswordFormat PasswordFormat /// Gets or sets the customer system name /// [DataMember] + [Index] public string SystemName { get; set; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNameFormat.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNameFormat.cs index 227eba65cf..c10aa440f4 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNameFormat.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNameFormat.cs @@ -3,7 +3,7 @@ namespace SmartStore.Core.Domain.Customers /// /// Represents the customer name fortatting enumeration /// - public enum CustomerNameFormat : int + public enum CustomerNameFormat { /// /// Show emails diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNumberMethod.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNumberMethod.cs new file mode 100644 index 0000000000..f333486399 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNumberMethod.cs @@ -0,0 +1,24 @@ +namespace SmartStore.Core.Domain.Customers +{ + /// + /// Represents the customer number method + /// + public enum CustomerNumberMethod + { + /// + /// no customer number will be saved + /// + Disabled = 10, + + /// + /// customer numbers can be saved + /// + Enabled = 20, + + /// + /// customer numbers will automatically be set when new customers are created + /// + AutomaticallySet = 30, + + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNumberVisibility.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNumberVisibility.cs new file mode 100644 index 0000000000..d05e588e97 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNumberVisibility.cs @@ -0,0 +1,28 @@ +namespace SmartStore.Core.Domain.Customers +{ + /// + /// Represents the customer visibility in the frontend + /// + public enum CustomerNumberVisibility + { + /// + /// Customer number won't be displayed in the frontend + /// + None = 10, + + /// + /// Customer number will be displayed in the frontend + /// + Display = 20, + + /// + /// A customer can enter his own number if customer number wasn't saved yet + /// + EditableIfEmpty = 30, + + /// + /// A customer can enter his own number and alter it + /// + Editable = 40, + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerRole.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerRole.cs index 7c6bddf03c..6d3b2003e8 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerRole.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerRole.cs @@ -26,7 +26,6 @@ public partial class CustomerRole : BaseEntity /// public bool TaxExempt { get; set; } - /// codehint: sm-add /// /// Gets or sets the tax display type /// diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs index f37d102711..e6d7f56f56 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs @@ -8,6 +8,8 @@ public class CustomerSettings : ISettings public CustomerSettings() { UsernamesEnabled = true; + CustomerNumberMethod = Customers.CustomerNumberMethod.Disabled; + CustomerNumberVisibility = Customers.CustomerNumberVisibility.None; DefaultPasswordFormat = PasswordFormat.Hashed; HashedPasswordFormat = "SHA1"; PasswordMinLength = 6; @@ -22,6 +24,7 @@ public CustomerSettings() NewsletterEnabled = true; OnlineCustomerMinutes = 20; StoreLastVisitedPage = true; + DisplayPrivacyAgreementOnContactUs = false; } /// @@ -29,6 +32,16 @@ public CustomerSettings() /// public bool UsernamesEnabled { get; set; } + /// + /// Gets or sets the customer number method + /// + public CustomerNumberMethod CustomerNumberMethod { get; set; } + + /// + /// Gets or sets the customer number visibility + /// + public CustomerNumberVisibility CustomerNumberVisibility { get; set; } + /// /// Gets or sets a value indicating whether users can check the availability of usernames (when registering or changing in 'My Account') /// @@ -139,7 +152,11 @@ public CustomerSettings() /// 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 /// @@ -234,8 +251,12 @@ public CustomerSettings() #endregion - // codehint: sm-add (no ui, only db edit) public string PrefillLoginUsername { get; set; } public string PrefillLoginPwd { get; set; } - } + + /// + /// Identifier of a customer role that new registered customers will be assigned to + /// + public int RegisterCustomerRoleId { get; set; } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/PasswordFormat.cs b/src/Libraries/SmartStore.Core/Domain/Customers/PasswordFormat.cs index b84ab3a661..4b35797ec7 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/PasswordFormat.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/PasswordFormat.cs @@ -1,7 +1,7 @@  namespace SmartStore.Core.Domain.Customers { - public enum PasswordFormat : int + public enum PasswordFormat { Clear = 0, Hashed = 1, diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/RewardPointsSettings.cs b/src/Libraries/SmartStore.Core/Domain/Customers/RewardPointsSettings.cs index 1d6ec48df5..f0e99e9502 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/RewardPointsSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/RewardPointsSettings.cs @@ -24,10 +24,15 @@ public RewardPointsSettings() /// public decimal ExchangeRate { get; set; } - /// - /// Gets or sets a number of points awarded for registration - /// - public int PointsForRegistration { get; set; } + /// + /// Gets or sets a value whether to round down reward points + /// + public bool RoundDownRewardPoints { get; set; } + + /// + /// Gets or sets a number of points awarded for registration + /// + public int PointsForRegistration { get; set; } /// /// Gets or sets a number of points awarded for a product review diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs index f830bf8631..33569b4855 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs @@ -20,6 +20,7 @@ public static partial class SystemCustomerAttributeNames 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"; } } //Other attributes public static string DiscountCouponCode { get { return "DiscountCouponCode"; } } @@ -31,9 +32,11 @@ public static partial class SystemCustomerAttributeNames public static string PasswordRecoveryToken { get { return "PasswordRecoveryToken"; } } public static string AccountActivationToken { get { return "AccountActivationToken"; } } public static string LastVisitedPage { get { return "LastVisitedPage"; } } - public static string ImpersonatedCustomerId { get { return "ImpersonatedCustomerId"; } } + public static string LastUserAgent { get { return "LastUserAgent"; } } + public static string ImpersonatedCustomerId { get { return "ImpersonatedCustomerId"; } } public static string AdminAreaStoreScopeConfiguration { get { return "AdminAreaStoreScopeConfiguration"; } } public static string MostRecentlyUsedCategories { get { return "MostRecentlyUsedCategories"; } } + public static string MostRecentlyUsedManufacturers { get { return "MostRecentlyUsedManufacturers"; } } //depends on store public static string CurrencyId { get { return "CurrencyId"; } } diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerNames.cs b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerNames.cs index 6922b88f2b..48ed1b0dfd 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerNames.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerNames.cs @@ -5,5 +5,6 @@ public static partial class SystemCustomerNames { public static string SearchEngine { get { return "SearchEngine"; } } public static string BackgroundTask { get { return "BackgroundTask"; } } + public static string PdfConverter { get { return "PdfConverter"; } } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerRoleNames.cs b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerRoleNames.cs index 760d14d9be..214fd18191 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerRoleNames.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerRoleNames.cs @@ -3,7 +3,6 @@ namespace SmartStore.Core.Domain.Customers { public static partial class SystemCustomerRoleNames { - /// codehint: sm-add public static string SuperAdministrators { get { return "SuperAdmins"; } } public static string Administrators { get { return "Administrators"; } } diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/UserRegistrationType.cs b/src/Libraries/SmartStore.Core/Domain/Customers/UserRegistrationType.cs index dc89589020..1f950092db 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/UserRegistrationType.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/UserRegistrationType.cs @@ -3,7 +3,7 @@ namespace SmartStore.Core.Domain.Customers /// /// Represents the customer registration type fortatting enumeration /// - public enum UserRegistrationType : int + public enum UserRegistrationType { /// /// Standard account creation diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeEnums.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeEnums.cs new file mode 100644 index 0000000000..7a3ab1f8ae --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeEnums.cs @@ -0,0 +1,26 @@ +namespace SmartStore.Core.Domain.DataExchange +{ + public delegate void ProgressValueSetter(int value, int maximum, string message); + + + /// + /// Data exchange abortion types + /// + public enum DataExchangeAbortion + { + /// + /// No abortion. Go on with processing. + /// + None = 0, + + /// + /// Break item processing but not the rest of the execution. Typically used for demo limitations. + /// + Soft, + + /// + /// Break processing immediately + /// + Hard + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs new file mode 100644 index 0000000000..4b0bc6d497 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs @@ -0,0 +1,28 @@ +using SmartStore.Core.Configuration; + +namespace SmartStore.Core.Domain.DataExchange +{ + public class DataExchangeSettings : ISettings + { + public DataExchangeSettings() + { + MaxFileNameLength = 50; + ImageDownloadTimeout = 10; + } + + /// + /// The maximum length of file names (in characters) of files created by the export framework + /// + public int MaxFileNameLength { get; set; } + + /// + /// Relative path to a folder with images to be imported + /// + public string ImageImportFolder { get; set; } + + /// + /// The timeout for image download per entity in minutes + /// + public int ImageDownloadTimeout { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs new file mode 100644 index 0000000000..b9e323c670 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs @@ -0,0 +1,141 @@ +using System; +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Core.Domain +{ + public class ExportDeployment : BaseEntity, ICloneable + { + /// + /// The profile identifier + /// + public int ProfileId { get; set; } + + /// + /// Name of the deployment + /// + public string Name { get; set; } + + /// + /// Whether the deployment is enabled + /// + public bool Enabled { get; set; } + + /// + /// XML with information about the last deployment result + /// + public string ResultInfo { get; set; } + + /// + /// The deployment type identifier + /// + public int DeploymentTypeId { get; set; } + + /// + /// The deployment type + /// + public ExportDeploymentType DeploymentType + { + get + { + return (ExportDeploymentType)DeploymentTypeId; + } + set + { + DeploymentTypeId = (int)value; + } + } + + public string Username { get; set; } + + public string Password { get; set; } + + /// + /// Deployment URL + /// + public string Url { get; set; } + + /// + /// The type identifier of how to transmit via HTTP + /// + public int HttpTransmissionTypeId { get; set; } + + /// + /// The type of how to transmit via HTTP + /// + public ExportHttpTransmissionType HttpTransmissionType + { + get + { + return (ExportHttpTransmissionType)HttpTransmissionTypeId; + } + set + { + HttpTransmissionTypeId = (int)value; + } + } + + /// + /// The file system path + /// + public string FileSystemPath { get; set; } + + /// + /// Path of a subfolder + /// + public string SubFolder { get; set; } + + /// + /// Multiple email addresses can be separated by commas + /// + public string EmailAddresses { get; set; } + + /// + /// Subject of the email + /// + public string EmailSubject { get; set; } + + /// + /// Identifier of the email account + /// + public int EmailAccountId { get; set; } + + /// + /// Whether to use FTP active or passive mode + /// + public bool PassiveMode { get; set; } + + /// + /// Whether to use SSL + /// + public bool UseSsl { get; set; } + + public virtual ExportProfile Profile { get; set; } + + public ExportDeployment Clone() + { + var deployment = new ExportDeployment + { + Name = this.Name, + Enabled = this.Enabled, + DeploymentTypeId = this.DeploymentTypeId, + Username = this.Username, + Password = this.Password, + Url = this.Url, + HttpTransmissionTypeId = this.HttpTransmissionTypeId, + FileSystemPath = this.FileSystemPath, + SubFolder = this.SubFolder, + EmailAddresses = this.EmailAddresses, + EmailSubject = this.EmailSubject, + EmailAccountId = this.EmailAccountId, + PassiveMode = this.PassiveMode, + UseSsl = this.UseSsl + }; + return deployment; + } + + object ICloneable.Clone() + { + return this.Clone(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs new file mode 100644 index 0000000000..6ea68ec73c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs @@ -0,0 +1,146 @@ +using System; + +namespace SmartStore.Core.Domain.DataExchange +{ + /// + /// Supported entity types + /// + public enum ExportEntityType + { + Product = 0, + Category, + Manufacturer, + Customer, + Order, + NewsLetterSubscription + } + + /// + /// Supported deployment types + /// + public enum ExportDeploymentType + { + FileSystem = 0, + Email, + Http, + Ftp, + PublicFolder + } + + /// + /// Supported HTTP transmission types + /// + public enum ExportHttpTransmissionType + { + SimplePost = 0, + MultipartFormDataPost + } + + /// + /// Controls the merging of various data as product description + /// + public enum ExportDescriptionMerging + { + None = 0, + ShortDescriptionOrNameIfEmpty, + ShortDescription, + Description, + NameAndShortDescription, + NameAndDescription, + ManufacturerAndNameAndShortDescription, + ManufacturerAndNameAndDescription + } + + /// + /// Controls the merging of various data while exporting attribute combinations as products + /// + public enum ExportAttributeValueMerging + { + None = 0, + AppendAllValuesToName + } + + /// + /// Controls data processing and projection items supported by an export provider + /// + [Flags] + public enum ExportFeatures + { + None = 0, + + /// + /// Whether to automatically create a file based public deployment when an export profile is created + /// + CreatesInitialPublicDeployment = 1, + + /// + /// Whether to offer option to include\exclude grouped products + /// + CanOmitGroupedProducts = 1 << 2, + + /// + /// Whether to offer option to export attribute combinations as products + /// + CanProjectAttributeCombinations = 1 << 3, + + /// + /// Whether to offer further options to manipulate the product description + /// + CanProjectDescription = 1 << 4, + + /// + /// Whether to offer option to enter a brand fallback + /// + OffersBrandFallback = 1 << 5, + + /// + /// Whether to offer option to set a picture size and to get the URL of the main image + /// + CanIncludeMainPicture = 1 << 6, + + /// + /// Whether to use SKU as manufacturer part number if MPN is empty + /// + UsesSkuAsMpnFallback = 1 << 7, + + /// + /// Whether to offer option to enter a shipping time fallback + /// + OffersShippingTimeFallback = 1 << 8, + + /// + /// Whether to offer option to enter a shipping costs fallback and a free shipping threshold + /// + OffersShippingCostsFallback = 1 << 9, + + /// + /// Whether to get the calculated old product price + /// + UsesOldPrice = 1 << 10, + + /// + /// Whether to get the calculated special and regular price (ignoring special offers) + /// + UsesSpecialPrice = 1 << 11, + + /// + /// Whether to not automatically send a completion email + /// + CanOmitCompletionMail = 1 << 12, + + /// + /// Whether to provide additional data of attribute combinations + /// + UsesAttributeCombination = 1 << 13 + } + + /// + /// Possible order status change after order exporting + /// + public enum ExportOrderStatusChange + { + None = 0, + Processing, + Complete + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs new file mode 100644 index 0000000000..23d08fd03c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -0,0 +1,175 @@ +using System; +using SmartStore.Core.Domain.Catalog; + +namespace SmartStore.Core.Domain.DataExchange +{ + [Serializable] + public class ExportFilter + { + /// + /// Store identifier; 0 to load all records + /// + public int StoreId { get; set; } + + /// + /// Entity created from + /// + public DateTime? CreatedFrom { get; set; } + + /// + /// Entity created to + /// + public DateTime? CreatedTo { get; set; } + + #region Product + + /// + /// Minimum product identifier + /// + public int? IdMinimum { get; set; } + + /// + /// Maximum product identifier + /// + public int? IdMaximum { get; set; } + + /// + /// Minimum price + /// + public decimal? PriceMinimum { get; set; } + + /// + /// Maximum price + /// + public decimal? PriceMaximum { get; set; } + + /// + /// Minimum product availability + /// + public int? AvailabilityMinimum { get; set; } + + /// + /// Maximum product availability + /// + public int? AvailabilityMaximum { get; set; } + + /// + /// A value indicating whether to load only published or non published products + /// + public bool? IsPublished { get; set; } + + /// + /// Category identifiers + /// + public int[] CategoryIds { get; set; } + + /// + /// A value indicating whether to load products without any catgory mapping + /// + public bool? WithoutCategories { get; set; } + + /// + /// Manufacturer identifier + /// + public int? ManufacturerId { get; set; } + + /// + /// A value indicating whether to load products without any manufacturer mapping + /// + public bool? WithoutManufacturers { get; set; } + + /// + /// Identifiers of product tag + /// + public int? ProductTagId { get; set; } + + /// + /// A value indicating whether to load products that are marked as featured (relates only to categories and manufacturers) + /// + public bool? FeaturedProducts { get; set; } + + /// + /// Filter by product type + /// + public ProductType? ProductType { get; set; } + + #endregion + + #region Order + + /// + /// Filter by order status + /// + public int[] OrderStatusIds { get; set; } + + /// + /// Filter by payment status + /// + public int[] PaymentStatusIds { get; set; } + + /// + /// Filter by shipping status + /// + public int[] ShippingStatusIds { get; set; } + + #endregion + + #region Customer + + /// + /// Filter by active or inactive customers + /// + public bool? IsActiveCustomer { get; set; } + + /// + /// Filter by tax exempt customers + /// + public bool? IsTaxExempt { get; set; } + + /// + /// Identifiers of customer roles + /// + public int[] CustomerRoleIds { get; set; } + + /// + /// Filter by billing country identifiers + /// + public int[] BillingCountryIds { get; set; } + + /// + /// Filter by shipping country identifiers + /// + public int[] ShippingCountryIds { get; set; } + + /// + /// Filter by last activity date from + /// + public DateTime? LastActivityFrom { get; set; } + + /// + /// Filter by last activity date to + /// + public DateTime? LastActivityTo { get; set; } + + /// + /// Filter by at least spent amount + /// + public decimal? HasSpentAtLeastAmount { get; set; } + + /// + /// Filter by at least placed orders + /// + public int? HasPlacedAtLeastOrders { get; set; } + + #endregion + + #region Newsletter Subscription + + /// + /// Filter by active or inactive subscriber + /// + public bool? IsActiveSubscriber { get; set; } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs new file mode 100644 index 0000000000..3161d1ea94 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -0,0 +1,166 @@ +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 + { + private ICollection _deployments; + + public ExportProfile() + { + Enabled = true; + PerStore = true; + Cleanup = true; + EmailAccountId = 0; + } + + /// + /// The name of the profile + /// + public string Name { get; set; } + + /// + /// The root path of the export folder + /// + public string FolderName { get; set; } + + /// + /// The pattern for file names + /// + public string FileNamePattern { get; set; } + + /// + /// The system name of the profile + /// + public string SystemName { get; set; } + + /// + /// The system name of the export provider + /// + public string ProviderSystemName { get; set; } + + /// + /// Whether the profile is an unremovable system profile + /// + public bool IsSystemProfile { get; set; } + + /// + /// Whether the export profile is enabled + /// + public bool Enabled { get; set; } + + /// + /// The scheduling task identifier + /// + public int SchedulingTaskId { get; set; } + + /// + /// XML with filtering information + /// + public string Filtering { get; set; } + + /// + /// XML with projection information + /// + public string Projection { get; set; } + + /// + /// XML with provider specific configuration data + /// + public string ProviderConfigData { get; set; } + + /// + /// XML with information about the last export + /// + public string ResultInfo { get; set; } + + /// + /// The number of records to be skipped + /// + public int Offset { get; set; } + + /// + /// How many records to be loaded per database round-trip + /// + public int Limit { get; set; } + + /// + /// The maximum number of records of one processed batch + /// + public int BatchSize { get; set; } + + /// + /// Whether to start a separate run-through for each store + /// + public bool PerStore { get; set; } + + /// + /// Email Account identifier used to send a notification message an export completes + /// + public int EmailAccountId { get; set; } + + /// + /// Email addresses where to send the notification message + /// + public string CompletedEmailAddresses { get; set; } + + /// + /// Whether to combine and compress the export files in a ZIP archive + /// + public bool CreateZipArchive { get; set; } + + /// + /// Whether to delete unneeded files after deployment + /// + public bool Cleanup { get; set; } + + + /// + /// The scheduling task + /// + public virtual ScheduleTask ScheduleTask { get; set; } + + /// + /// Gets or sets export deployments + /// + public virtual ICollection Deployments + { + get { return _deployments ?? (_deployments = new HashSet()); } + set { _deployments = value; } + } + + public ExportProfile Clone() + { + var profile = new ExportProfile + { + Name = this.Name, + FolderName = null, + FileNamePattern = this.FileNamePattern, + ProviderSystemName = this.ProviderSystemName, + Enabled = this.Enabled, + SchedulingTaskId = 0, + Filtering = this.Filtering, + Projection = this.Projection, + ProviderConfigData = this.ProviderConfigData, + ResultInfo = null, + Offset = this.Offset, + Limit = this.Limit, + BatchSize = this.BatchSize, + PerStore = this.PerStore, + EmailAccountId = this.EmailAccountId, + CompletedEmailAddresses = this.CompletedEmailAddresses, + CreateZipArchive = this.CreateZipArchive, + Cleanup = this.Cleanup + }; + return profile; + } + + object ICloneable.Clone() + { + return this.Clone(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs new file mode 100644 index 0000000000..493acda473 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs @@ -0,0 +1,181 @@ +using System; +using System.Xml.Serialization; +using SmartStore.Core.Domain.Catalog; + +namespace SmartStore.Core.Domain.DataExchange +{ + /// + /// Settings projected onto an export + /// + /// + /// Note possible projection controlling: a) developer controls, b) merchant controls, c) developer controls what the merchant can control + /// + [Serializable] + public class ExportProjection + { + #region All entity types + + /// + /// Store identifier + /// + public int? StoreId { get; set; } + + /// + /// The language to be applied to the export + /// + public int? LanguageId { get; set; } + + /// + /// The currency to be applied to the export + /// + public int? CurrencyId { get; set; } + + /// + /// Customer identifier + /// + public int? CustomerId { get; set; } + + #endregion + + #region Product + + /// + /// Description merging identifier + /// + public int DescriptionMergingId { get; set; } + + /// + /// Decription merging + /// + [XmlIgnore] + public ExportDescriptionMerging DescriptionMerging + { + get + { + return (ExportDescriptionMerging)DescriptionMergingId; + } + set + { + DescriptionMergingId = (int)value; + } + } + + /// + /// Convert HTML decription to plain text + /// + public bool DescriptionToPlainText { get; set; } + + /// + /// Comma separated text to append to the decription + /// + public string AppendDescriptionText { get; set; } + + /// + /// Remove critical characters from the description + /// + public bool RemoveCriticalCharacters { get; set; } + + /// + /// Comma separated list of critical characters + /// + public string CriticalCharacters { get; set; } + + /// + /// The price type for calculating the product price + /// + public PriceDisplayType? PriceType { get; set; } + + /// + /// Convert net to gross prices + /// + public bool ConvertNetToGrossPrices { get; set; } + + /// + /// Fallback for product brand + /// + public string Brand { get; set; } + + /// + /// Number of images per object to be exported + /// + public int? NumberOfPictures { get; set; } + + /// + /// Picture size + /// + public int PictureSize { get; set; } + + /// + /// Fallback for shipping time + /// + public string ShippingTime { get; set; } + + /// + /// Fallback for shipping costs + /// + public decimal? ShippingCosts { get; set; } + + /// + /// Free shipping threshold + /// + public decimal? FreeShippingThreshold { get; set; } + + /// + /// Whether to export attribute combinations as products + /// + public bool AttributeCombinationAsProduct { get; set; } + + /// + /// Identifier for merging attribute values of attribute combinations + /// + public int AttributeCombinationValueMergingId { get; set; } + + /// + /// Merging attribute values of attribute combinations + /// + [XmlIgnore] + public ExportAttributeValueMerging AttributeCombinationValueMerging + { + get + { + return (ExportAttributeValueMerging)AttributeCombinationValueMergingId; + } + set + { + AttributeCombinationValueMergingId = (int)value; + } + } + + /// + /// Whether to export grouped products + /// + public bool NoGroupedProducts { get; set; } + + #endregion + + #region Order + + /// + /// Identifier of the new state for orders + /// + public int OrderStatusChangeId { get; set; } + + /// + /// New state for orders + /// + [XmlIgnore] + public ExportOrderStatusChange OrderStatusChange + { + get + { + return (ExportOrderStatusChange)OrderStatusChangeId; + } + set + { + OrderStatusChangeId = (int)value; + } + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportEnums.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportEnums.cs new file mode 100644 index 0000000000..94bbd69246 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportEnums.cs @@ -0,0 +1,28 @@ +using System; + +namespace SmartStore.Core.Domain.DataExchange +{ + /// + /// Supported entity types + /// + public enum ImportEntityType + { + Product = 0, + Category, + Customer, + NewsLetterSubscription + } + + public enum ImportFileType + { + CSV = 0, + XLSX + } + + [Flags] + public enum ImportModeFlags + { + Insert = 1, + Update = 2 + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs new file mode 100644 index 0000000000..1139496b4a --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs @@ -0,0 +1,113 @@ +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Tasks; + +namespace SmartStore.Core.Domain +{ + public class ImportProfile : BaseEntity + { + /// + /// The name of the profile + /// + public string Name { get; set; } + + /// + /// The name of the folder (file system) + /// + public string FolderName { get; set; } + + /// + /// The identifier of the file type + /// + public int FileTypeId { get; set; } + + /// + /// The file type + /// + public ImportFileType FileType + { + get + { + return (ImportFileType)FileTypeId; + } + set + { + FileTypeId = (int)value; + } + } + + /// + /// The identifier of the entity type + /// + public int EntityTypeId { get; set; } + + /// + /// The entity type + /// + public ImportEntityType EntityType + { + get + { + return (ImportEntityType)EntityTypeId; + } + set + { + EntityTypeId = (int)value; + } + } + + /// + /// Whether the profile is enabled + /// + public bool Enabled { get; set; } + + /// + /// Number of records to bypass + /// + public int Skip { get; set; } + + /// + /// Maximum number of records to return + /// + public int Take { get; set; } + + /// + /// Whether to only update existing data + /// + public bool UpdateOnly { get; set; } + + /// + /// Name of key fields to identify existing records during import + /// + public string KeyFieldNames { get; set; } + + /// + /// File type specific configuration + /// + public string FileTypeConfiguration { get; set; } + + /// + /// XML with extra data + /// + public string ExtraData { get; set; } + + /// + /// Mapping of import columns + /// + public string ColumnMapping { get; set; } + + /// + /// XML with information about the last import + /// + public string ResultInfo { get; set; } + + /// + /// The scheduling task identifier + /// + public int SchedulingTaskId { get; set; } + + /// + /// The scheduling task + /// + public virtual ScheduleTask ScheduleTask { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/SyncMapping.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/SyncMapping.cs new file mode 100644 index 0000000000..a297bc8f85 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/SyncMapping.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Core.Domain.DataExchange +{ + /// + /// Holds info about a synchronization operation with an external system + /// + [DataContract] + public partial class SyncMapping : BaseEntity + { + public SyncMapping() + { + this.SyncedOnUtc = DateTime.UtcNow; + } + + /// + /// Gets or sets the entity identifier in SmartStore + /// + [Index("IX_SyncMapping_ByEntity", 0, IsUnique = true)] + [DataMember] + public int EntityId { get; set; } + + /// + /// Gets or sets the entity's key in the external application + /// + [Index("IX_SyncMapping_BySource", 0, IsUnique = true)] + [DataMember] + public string SourceKey { get; set; } + + /// + /// Gets or sets a name representing the entity type + /// + [Index("IX_SyncMapping_ByEntity", 1, IsUnique = true)] + [Index("IX_SyncMapping_BySource", 1, IsUnique = true)] + [DataMember] + public string EntityName { get; set; } + + /// + /// Gets or sets a name for the external application + /// + [Index("IX_SyncMapping_ByEntity", 2, IsUnique = true)] + [Index("IX_SyncMapping_BySource", 2, IsUnique = true)] + [DataMember] + public string ContextName { get; set; } + + /// + /// Gets or sets an optional content hash reflecting the source model at the time of last sync + /// + [DataMember] + public string SourceHash { get; set; } + + /// + /// Gets or sets a custom integer value + /// + [DataMember] + public int? CustomInt { get; set; } + + /// + /// Gets or sets a custom string value + /// + [DataMember] + public string CustomString { get; set; } + + /// + /// Gets or sets a custom bool value + /// + [DataMember] + public bool? CustomBool { get; set; } + + /// + /// Gets or sets the date of the last sync operation + /// + [DataMember] + public DateTime SyncedOnUtc { get; set; } + + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Directory/Country.cs b/src/Libraries/SmartStore.Core/Domain/Directory/Country.cs index 39bb1ba887..117a487a07 100644 --- a/src/Libraries/SmartStore.Core/Domain/Directory/Country.cs +++ b/src/Libraries/SmartStore.Core/Domain/Directory/Country.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; +using System.Runtime.Serialization; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Shipping; -using System.Runtime.Serialization; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Directory { @@ -9,7 +10,7 @@ namespace SmartStore.Core.Domain.Directory /// Represents a country /// [DataContract] - public partial class Country : BaseEntity, ILocalizedEntity + public partial class Country : BaseEntity, ILocalizedEntity, IStoreMappingSupported { private ICollection _stateProvinces; private ICollection _restrictedShippingMethods; @@ -68,6 +69,12 @@ public partial class Country : BaseEntity, ILocalizedEntity /// [DataMember] public int DisplayOrder { 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 state/provinces diff --git a/src/Libraries/SmartStore.Core/Domain/Directory/CurrencySettings.cs b/src/Libraries/SmartStore.Core/Domain/Directory/CurrencySettings.cs index d1c807b4a6..371d202799 100644 --- a/src/Libraries/SmartStore.Core/Domain/Directory/CurrencySettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Directory/CurrencySettings.cs @@ -1,12 +1,9 @@ - -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; namespace SmartStore.Core.Domain.Directory { public class CurrencySettings : ISettings { - public int PrimaryStoreCurrencyId { get; set; } - public int PrimaryExchangeRateCurrencyId { get; set; } public string ActiveExchangeRateProviderSystemName { get; set; } public bool AutoUpdateEnabled { get; set; } public long LastUpdateTime { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs b/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs index 4823f78825..1af950f759 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs @@ -1,11 +1,13 @@ using System; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Seo; namespace SmartStore.Core.Domain.Forums { /// /// Represents a forum /// - public partial class Forum : BaseEntity + public partial class Forum : BaseEntity, ILocalizedEntity, ISlugSupported { /// /// Gets or sets the forum group identifier diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs index 4e4cc99eba..fc84ae983e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Forums { /// /// Represents a forum group /// - public partial class ForumGroup : BaseEntity + public partial class ForumGroup : BaseEntity, IStoreMappingSupported, ILocalizedEntity, ISlugSupported { private ICollection _forums; @@ -35,6 +38,11 @@ public partial class ForumGroup : BaseEntity /// 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 the collection of Forums /// diff --git a/src/Libraries/SmartStore.Core/Domain/Localization/LocalizationSettings.cs b/src/Libraries/SmartStore.Core/Domain/Localization/LocalizationSettings.cs index 4cd7889fee..b02a4ad2a2 100644 --- a/src/Libraries/SmartStore.Core/Domain/Localization/LocalizationSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Localization/LocalizationSettings.cs @@ -9,6 +9,7 @@ public LocalizationSettings() UseImagesForLanguageSelection = true; DefaultLanguageRedirectBehaviour = DefaultLanguageRedirectBehaviour.StripSeoCode; InvalidLanguageRedirectBehaviour = InvalidLanguageRedirectBehaviour.ReturnHttp404; + LoadAllLocalizedPropertiesOnStartup = true; } /// @@ -26,6 +27,11 @@ public LocalizationSettings() /// 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 ffd8d2fc22..f0d0406fb4 100644 --- a/src/Libraries/SmartStore.Core/Domain/Localization/LocalizedProperty.cs +++ b/src/Libraries/SmartStore.Core/Domain/Localization/LocalizedProperty.cs @@ -13,28 +13,28 @@ public partial class LocalizedProperty : BaseEntity /// Gets or sets the entity identifier /// [DataMember] - [Index("IX_LocalizedProperty_Compound", 1)] + [Index("IX_LocalizedProperty_Compound", Order = 1)] public int EntityId { get; set; } /// /// Gets or sets the language identifier /// [DataMember] - [Index("IX_LocalizedProperty_Compound", 4)] + [Index("IX_LocalizedProperty_Compound", Order = 4)] public int LanguageId { get; set; } /// /// Gets or sets the locale key group /// [DataMember] - [Index("IX_LocalizedProperty_Compound", 3)] + [Index("IX_LocalizedProperty_Compound", Order = 3)] public string LocaleKeyGroup { get; set; } /// /// Gets or sets the locale key /// [DataMember] - [Index("IX_LocalizedProperty_Compound", 2)] + [Index("IX_LocalizedProperty_Compound", Order = 2)] public string LocaleKey { get; set; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/Media/Download.cs b/src/Libraries/SmartStore.Core/Domain/Media/Download.cs index 92ebeba670..0082cbfc67 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/Download.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/Download.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.Serialization; namespace SmartStore.Core.Domain.Media @@ -7,22 +8,28 @@ namespace SmartStore.Core.Domain.Media /// Represents a download /// [DataContract] - public partial class Download : BaseEntity + public partial class Download : BaseEntity, ITransient { - /// - /// Gets a sets a GUID + public Download() + { + this.UpdatedOnUtc = DateTime.UtcNow; + } + + /// + /// Gets or sets a GUID /// [DataMember] + [Index] public Guid DownloadGuid { get; set; } /// - /// Gets a sets a value indicating whether DownloadUrl property should be used + /// Gets or sets a value indicating whether DownloadUrl property should be used /// [DataMember] public bool UseDownloadUrl { get; set; } /// - /// Gets a sets a download URL + /// Gets or sets a download URL /// [DataMember] public string DownloadUrl { get; set; } @@ -55,5 +62,19 @@ public partial class Download : BaseEntity /// [DataMember] public bool IsNew { get; set; } + + /// + /// Gets or sets a value indicating whether the entity transient/preliminary + /// + [DataMember] + [Index("IX_UpdatedOn_IsTransient", 1)] + public bool IsTransient { get; set; } + + /// + /// Gets or sets the date and time of instance update + /// + [DataMember] + [Index("IX_UpdatedOn_IsTransient", 0)] + public DateTime UpdatedOnUtc { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs b/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs index 6d1deff20f..b3072c0912 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs @@ -1,5 +1,4 @@ - -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; namespace SmartStore.Core.Domain.Media { @@ -11,6 +10,7 @@ public MediaSettings() ProductThumbPictureSize = 100; ProductDetailsPictureSize = 300; ProductThumbPictureSizeOnProductDetailsPage = 70; + MessageProductThumbPictureSize = 70; AssociatedProductPictureSize = 125; BundledProductPictureSize = 70; CategoryThumbPictureSize = 125; @@ -18,7 +18,7 @@ public MediaSettings() CartThumbPictureSize = 80; CartThumbBundleItemPictureSize = 32; MiniCartThumbPictureSize = 32; - AutoCompleteSearchThumbPictureSize = 20; + VariantValueThumbPictureSize = 20; MaximumImageSize = 1280; DefaultPictureZoomEnabled = true; PictureZoomType = "window"; @@ -30,16 +30,17 @@ public MediaSettings() public int ProductThumbPictureSize { get; set; } public int ProductDetailsPictureSize { get; set; } public int ProductThumbPictureSizeOnProductDetailsPage { get; set; } - public int AssociatedProductPictureSize { get; set; } + public int MessageProductThumbPictureSize { get; set; } + public int AssociatedProductPictureSize { get; set; } public int BundledProductPictureSize { get; set; } - public int CategoryThumbPictureSize { get; set; } + public int CategoryThumbPictureSize { get; set; } public int ManufacturerThumbPictureSize { get; set; } public int CartThumbPictureSize { get; set; } public int CartThumbBundleItemPictureSize { get; set; } public int MiniCartThumbPictureSize { get; set; } - public int AutoCompleteSearchThumbPictureSize { get; set; } + public int VariantValueThumbPictureSize { get; set; } - public bool DefaultPictureZoomEnabled { get; set; } + public bool DefaultPictureZoomEnabled { get; set; } public string PictureZoomType { get; set; } public int MaximumImageSize { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/Media/Picture.cs b/src/Libraries/SmartStore.Core/Domain/Media/Picture.cs index 45d8e80069..f527b7d693 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/Picture.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/Picture.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using SmartStore.Core.Domain.Catalog; using System.Runtime.Serialization; +using System; +using System.ComponentModel.DataAnnotations.Schema; namespace SmartStore.Core.Domain.Media { @@ -8,9 +10,14 @@ namespace SmartStore.Core.Domain.Media /// Represents a picture /// [DataContract] - public partial class Picture : BaseEntity + public partial class Picture : BaseEntity, ITransient { - private ICollection _productPictures; + public Picture() + { + this.UpdatedOnUtc = DateTime.UtcNow; + } + + private ICollection _productPictures; /// /// Gets or sets the picture binary /// @@ -34,6 +41,20 @@ public partial class Picture : BaseEntity [DataMember] public bool IsNew { get; set; } + /// + /// Gets or sets a value indicating whether the entity transient/preliminary + /// + [DataMember] + [Index("IX_UpdatedOn_IsTransient", 1)] + public bool IsTransient { get; set; } + + /// + /// Gets or sets the date and time of instance update + /// + [DataMember] + [Index("IX_UpdatedOn_IsTransient", 0)] + public DateTime UpdatedOnUtc { get; set; } + /// /// Gets or sets the product pictures /// diff --git a/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs b/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs index 618b4f5052..3da89ff17a 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs @@ -3,7 +3,7 @@ namespace SmartStore.Core.Domain.Media /// /// Represents a picture item type /// - public enum PictureType : int + public enum PictureType { /// /// Entities (products, categories, manufacturers) @@ -15,7 +15,6 @@ public enum PictureType : int Avatar = 10, } - // codehint: sm-add public enum ThumbnailScaleMode { Auto, diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs b/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs index decde80336..859a7bd74f 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs @@ -54,9 +54,10 @@ public string FriendlyName { get { - if (!String.IsNullOrWhiteSpace(this.DisplayName)) - return this.Email + " (" + this.DisplayName + ")"; - return this.Email; + if (DisplayName.IsEmpty()) + return Email; + + return "{0} ({1})".FormatInvariant(DisplayName, Email); } } diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/EmailAttachmentStorageLocation.cs b/src/Libraries/SmartStore.Core/Domain/Messages/EmailAttachmentStorageLocation.cs new file mode 100644 index 0000000000..3dada46a59 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Messages/EmailAttachmentStorageLocation.cs @@ -0,0 +1,22 @@ +using SmartStore.Core.Domain.Media; + +namespace SmartStore.Core.Domain.Messages +{ + public enum EmailAttachmentStorageLocation + { + /// + /// Attachment is embedded as Blob + /// + Blob, + + /// + /// Attachment is a reference to + /// + FileReference, + + /// + /// Attachment is located on disk (physical or virtual path) + /// + Path + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplate.cs b/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplate.cs index 79ca39edc1..75f66564ae 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplate.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplate.cs @@ -1,4 +1,5 @@ using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Messages @@ -42,5 +43,25 @@ public partial class MessageTemplate : BaseEntity, ILocalizedEntity, IStoreMappi /// 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 emails derived from the template are only send manually + /// + public bool SendManually { get; set; } + + /// + /// Gets or sets the attachment 1 file identifier + /// + public int? Attachment1FileId { get; set; } + + /// + /// Gets or sets the attachment 2 file identifier + /// + public int? Attachment2FileId { get; set; } + + /// + /// Gets or sets the attachment 3 file identifier + /// + public int? Attachment3FileId { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmail.cs b/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmail.cs index d0d88d8db4..d3f7e63b93 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmail.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmail.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace SmartStore.Core.Domain.Messages { @@ -7,6 +8,8 @@ namespace SmartStore.Core.Domain.Messages /// public partial class QueuedEmail : BaseEntity { + private ICollection _attachments; + /// /// Gets or sets the priority /// @@ -82,9 +85,23 @@ public partial class QueuedEmail : BaseEntity /// public int EmailAccountId { get; set; } + /// + /// Gets or sets a value indicating whether emails are only send manually + /// + public bool SendManually { get; set; } + /// /// Gets the email account /// public virtual EmailAccount EmailAccount { get; set; } + + /// + /// Gets or sets the collection of attachments + /// + public virtual ICollection Attachments + { + get { return _attachments ?? (_attachments = new HashSet()); } + protected set { _attachments = value; } + } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmailAttachment.cs b/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmailAttachment.cs new file mode 100644 index 0000000000..cd6d436139 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmailAttachment.cs @@ -0,0 +1,61 @@ +using SmartStore.Core.Domain.Media; + +namespace SmartStore.Core.Domain.Messages +{ + /// + /// Reperesents an e-mail attachment + /// + public partial class QueuedEmailAttachment : BaseEntity + { + + /// + /// Gets or sets the queued email identifier + /// + public int QueuedEmailId { get; set; } + + /// + /// Gets or sets the queued email entity instance + /// + public virtual QueuedEmail QueuedEmail { get; set; } + + /// + /// Gets or sets the storage location + /// + public EmailAttachmentStorageLocation StorageLocation { get; set; } + + /// + /// A physical or virtual path to the file (only applicable if location is Path) + /// + public string Path { get; set; } + + /// + /// The id of a record (only applicable if location is FileReference) + /// + public int? FileId { get; set; } + + /// + /// Gets the file object + /// + /// + /// This property is not named Download on purpose, because we're going to rename Download to File in a future release. + /// + public virtual Download File { get; set; } + + /// + /// The attachment's binary data (only applicable if location is Blob) + /// + public byte[] Data { get; set; } + + /// + /// The attachment file name (without path) + /// + public string Name { get; set; } + + /// + /// The attachment file's mime type, e.g. application/pdf + /// + public string MimeType { get; set; } + + } + +} diff --git a/src/Libraries/SmartStore.Core/Domain/News/NewsSettings.cs b/src/Libraries/SmartStore.Core/Domain/News/NewsSettings.cs index 479b3c5c9b..ffceb3edab 100644 --- a/src/Libraries/SmartStore.Core/Domain/News/NewsSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/News/NewsSettings.cs @@ -1,5 +1,4 @@ - -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; namespace SmartStore.Core.Domain.News { @@ -12,6 +11,7 @@ public NewsSettings() ShowNewsOnMainPage = true; MainPageNewsCount = 3; NewsArchivePageSize = 10; + MaxAgeInDays = 180; } /// @@ -44,6 +44,11 @@ public NewsSettings() /// public int NewsArchivePageSize { get; set; } + /// + /// The maximum age of news (in days) for RSS feed + /// + public int MaxAgeInDays { get; set; } + /// /// Enable the news RSS feed link in customers browser address bar /// diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs b/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs index e1685f9937..63cbf58ea8 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs @@ -1,16 +1,22 @@ using System.Collections.Generic; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Orders { - /// - /// Represents a checkout attribute - /// - public partial class CheckoutAttribute : BaseEntity, ILocalizedEntity - { + /// + /// Represents a checkout attribute + /// + public partial class CheckoutAttribute : BaseEntity, ILocalizedEntity, IStoreMappingSupported + { private ICollection _checkoutAttributeValues; + public CheckoutAttribute() + { + this.IsActive = true; + } + /// /// Gets or sets the name /// @@ -50,11 +56,21 @@ public partial class CheckoutAttribute : BaseEntity, ILocalizedEntity /// Gets or sets the display order /// public int DisplayOrder { get; set; } - - /// - /// Gets the attribute control type - /// - public AttributeControlType AttributeControlType + + /// + /// Gets or sets whether the checkout attribute is active + /// + public bool IsActive { get; set; } + + /// + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores + /// + public bool LimitedToStores { get; set; } + + /// + /// Gets the attribute control type + /// + public AttributeControlType AttributeControlType { get { @@ -65,6 +81,7 @@ public AttributeControlType AttributeControlType this.AttributeControlTypeId = (int)value; } } + /// /// Gets the checkout attribute values /// diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutEnums.cs b/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutEnums.cs new file mode 100644 index 0000000000..4ce792b336 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutEnums.cs @@ -0,0 +1,45 @@ +namespace SmartStore.Core.Domain.Orders +{ + /// + /// Setting for newsletter subscription in checkout + /// + public enum CheckoutNewsLetterSubscription + { + /// + /// No newsletter subscription checkbox + /// + None = 0, + + /// + /// Deactivated newsletter subscription checkbox + /// + Deactivated, + + /// + /// Activated newsletter subscription checkbox + /// + Activated + } + + + /// + /// Setting to hand over customer email to third party + /// + public enum CheckoutThirdPartyEmailHandOver + { + /// + /// No third party email hand over checkbox + /// + None = 0, + + /// + /// Deactivated third party email hand over checkbox + /// + Deactivated, + + /// + /// Activated third party email hand over checkbox + /// + Activated + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs b/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs index edbf5ddf1f..9db5f14733 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics; using System.Globalization; using System.Runtime.Serialization; @@ -319,7 +320,6 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat /// public string CardExpirationYear { get; set; } - /// codehint:sm-add begin /// /// Gets or sets a value indicating whether storing of credit card number is allowed /// @@ -429,6 +429,7 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat /// /// Gets or sets a value indicating whether the entity has been deleted /// + [Index] public bool Deleted { get; set; } /// @@ -449,14 +450,26 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat [DataMember] public int? RewardPointsRemaining { get; set; } - #endregion + /// + /// Gets or sets a value indicating whether a new payment notification arrived (IPN, webhook, callback etc.) + /// + [DataMember] + public bool HasNewPaymentNotification { get; set; } - #region Navigation properties + /// + /// Gets or sets a value indicating whether the customer accepted to hand over email address to third party + /// + [DataMember] + public bool AcceptThirdPartyEmailHandOver { get; set; } - /// - /// Gets or sets the customer - /// - [DataMember] + #endregion + + #region Navigation properties + + /// + /// Gets or sets the customer + /// + [DataMember] public virtual Customer Customer { get; set; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs b/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs index 7a55c36f70..05e9a49fd4 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; using SmartStore.Core.Domain.Localization; namespace SmartStore.Core.Domain.Orders @@ -16,6 +15,7 @@ public OrderSettings() ReturnRequestReasons = "Received Wrong Product,Wrong Product Ordered,There Was A Problem With The Product"; NumberOfDaysReturnRequestAvailable = 365; MinimumOrderPlacementInterval = 30; + OrderListPageSize = 10; } /// @@ -83,5 +83,14 @@ public OrderSettings() /// public int MinimumOrderPlacementInterval { get; set; } - } + /// + /// Gets or sets a value indicating whether to display all orders of all stores to a customer + /// + public bool DisplayOrdersOfAllStores { get; set; } + + /// + /// Page size of the order list + /// + public int OrderListPageSize { get; set; } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/OrderStatus.cs b/src/Libraries/SmartStore.Core/Domain/Orders/OrderStatus.cs index cbff14a755..bf41213405 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/OrderStatus.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/OrderStatus.cs @@ -3,7 +3,7 @@ namespace SmartStore.Core.Domain.Orders /// /// Represents an order status enumeration /// - public enum OrderStatus : int + public enum OrderStatus { /// /// Pending diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/RecurringPayment.cs b/src/Libraries/SmartStore.Core/Domain/Orders/RecurringPayment.cs index dd4896bb5b..9861dc9302 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/RecurringPayment.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/RecurringPayment.cs @@ -62,12 +62,12 @@ public DateTime? NextPaymentDate DateTime? result = null; if (!this.IsActive) - return result; + return null; var historyCollection = this.RecurringPaymentHistory; if (historyCollection.Count >= this.TotalCycles) { - return result; + return null; } //set another value to change calculation method diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs b/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs index 4135b6b042..ba269bdf4c 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs @@ -46,6 +46,12 @@ public partial class ReturnRequest : BaseEntity [DataMember] public string RequestedAction { get; set; } + /// + /// Gets or sets the date and time when requested action was last updated + /// + [DataMember] + public DateTime? RequestedActionUpdatedOnUtc { get; set; } + /// /// Gets or sets the customer comments /// @@ -58,6 +64,12 @@ public partial class ReturnRequest : BaseEntity [DataMember] public string StaffNotes { get; set; } + /// + /// Gets or sets the admin comment + /// + [DataMember] + public string AdminComment { get; set; } + /// /// Gets or sets the return status identifier /// diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequestStatus.cs b/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequestStatus.cs index 8cbb36ee80..02fea117d8 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequestStatus.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequestStatus.cs @@ -4,7 +4,7 @@ namespace SmartStore.Core.Domain.Orders /// /// Represents a return status /// - public enum ReturnRequestStatus : int + public enum ReturnRequestStatus { /// /// Pending diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs index 878b461e43..e403d7cdb1 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs @@ -1,14 +1,16 @@ using SmartStore.Core.Configuration; +using SmartStore.Core.Domain.Localization; namespace SmartStore.Core.Domain.Orders { - public class ShoppingCartSettings : ISettings - { + public class ShoppingCartSettings : BaseEntity, ISettings, ILocalizedEntity + { public ShoppingCartSettings() { MaximumShoppingCartItems = 1000; MaximumWishlistItems = 1000; MoveItemsFromWishlistToCart = true; + ShowItemsFromWishlistToCartButton = true; ShowProductImagesOnShoppingCart = true; ShowProductBundleImagesOnShoppingCart = true; ShowProductImagesOnWishList = true; @@ -16,6 +18,7 @@ public ShoppingCartSettings() ShowDiscountBox = true; ShowGiftCardBox = true; ShowCommentBox = true; + ShowEsdRevocationWaiverBox = true; CrossSellsNumber = 8; EmailWishlistEnabled = true; MiniShoppingCartEnabled = true; @@ -25,7 +28,6 @@ public ShoppingCartSettings() ShowBasePrice = true; ShowDeliveryTimes = true; ShowShortDesc = true; - } /// @@ -58,6 +60,11 @@ public ShoppingCartSettings() /// public bool MoveItemsFromWishlistToCart { get; set; } + /// + /// Gets or sets a value indicating whether to show the items from wishlist to ahopping cart button. + /// + public bool ShowItemsFromWishlistToCartButton { get; set; } + /// /// Gets or sets a value indicating whether to show product image on shopping cart page /// @@ -92,11 +99,31 @@ public ShoppingCartSettings() /// Gets or sets a value indicating whether to show a comment box on shopping cart page /// public bool ShowCommentBox { get; set; } - - /// - /// Gets or sets a number of "Cross-sells" on shopping cart page - /// - public int CrossSellsNumber { get; set; } + + /// + /// Gets or sets a value indicating whether to show a revocation waiver checkbox box for ESD products + /// + public bool ShowEsdRevocationWaiverBox { get; set; } + + /// + /// Gets or sets a value indicating whether to show a checkbox to subscribe to newsletters + /// + public CheckoutNewsLetterSubscription NewsLetterSubscription { get; set; } + + /// + /// Gets or sets a value indicating whether to show a checkbox to let the customer accept to hand over email address to third party + /// + public CheckoutThirdPartyEmailHandOver ThirdPartyEmailHandOver { get; set; } + + /// + /// Gets or sets the label to accept to hand over the email to third party + /// + public string ThirdPartyEmailHandOverLabel { get; set; } + + /// + /// Gets or sets a number of "Cross-sells" on shopping cart page + /// + public int CrossSellsNumber { get; set; } /// /// Gets or sets a value indicating whether "email a wishlist" feature is enabled @@ -127,7 +154,6 @@ public ShoppingCartSettings() /// public bool RoundPricesDuringCalculation { get; set; } - // codehint: sm-add /// /// 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/PaymentMethod.cs b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentMethod.cs new file mode 100644 index 0000000000..e078434cc6 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentMethod.cs @@ -0,0 +1,24 @@ +using System.Runtime.Serialization; +using SmartStore.Core.Domain.Localization; + +namespace SmartStore.Core.Domain.Payments +{ + /// + /// Represents a payment method + /// + [DataContract] + public partial class PaymentMethod : BaseEntity, ILocalizedEntity + { + /// + /// Gets or sets the payment method system name + /// + [DataMember] + public string PaymentMethodSystemName { get; set; } + + /// + /// Gets or sets the full description + /// + [DataMember] + public string FullDescription { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Payments/PaymentStatus.cs b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentStatus.cs index ddd4e0bcaa..44f3dc262e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Payments/PaymentStatus.cs +++ b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentStatus.cs @@ -4,7 +4,7 @@ namespace SmartStore.Core.Domain.Payments /// /// Represents a payment status enumeration /// - public enum PaymentStatus : int + public enum PaymentStatus { /// /// Pending diff --git a/src/Libraries/SmartStore.Core/Domain/Security/AclRecord.cs b/src/Libraries/SmartStore.Core/Domain/Security/AclRecord.cs index cd0349e586..7bd0cf40e3 100644 --- a/src/Libraries/SmartStore.Core/Domain/Security/AclRecord.cs +++ b/src/Libraries/SmartStore.Core/Domain/Security/AclRecord.cs @@ -1,3 +1,4 @@ +using SmartStore.Core.Domain.Customers; namespace SmartStore.Core.Domain.Security { /// @@ -20,6 +21,11 @@ public partial class AclRecord : BaseEntity /// public int CustomerRoleId { get; set; } + /// + /// Gets or sets the customer role + /// + public virtual CustomerRole CustomerRole { get; set; } + /// /// Gets or sets a value indicating whether the entry is idle /// diff --git a/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs b/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs index 6dacb605a6..1f06b5aba6 100644 --- a/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs @@ -1,34 +1,80 @@ - +using System; using System.Collections.Generic; using SmartStore.Core.Configuration; namespace SmartStore.Core.Domain.Seo { - public class SeoSettings : ISettings + public class SeoSettings : ISettings { public SeoSettings() { PageTitleSeparator = ". "; PageTitleSeoAdjustment = PageTitleSeoAdjustment.PagenameAfterStorename; - DefaultTitle = "Your store"; + DefaultTitle = "Shop"; DefaultMetaKeywords = ""; DefaultMetaDescription = ""; - AllowUnicodeCharsInUrls = true; - CanonicalHostNameRule = Seo.CanonicalHostNameRule.NoRule; - ReservedUrlRecordSlugs = new List() { "admin", "install", "recentlyviewedproducts", "newproducts", "compareproducts", "clearcomparelist", "setproductreviewhelpfulness", "login", "register", "logout", "cart", "wishlist", "emailwishlist", "checkout", "contactus", "passwordrecovery", "subscribenewsletter", "blog", "boards", "inboxupdate", "sentupdate", "news", "sitemap", "sitemapseo", "search", "config", "api", "odata" }; - ExtraRobotsDisallows = new List(); + AllowUnicodeCharsInUrls = false; + CanonicalHostNameRule = CanonicalHostNameRule.NoRule; + LoadAllUrlAliasesOnStartup = true; + + ExtraRobotsDisallows = new List { "/blog/tag/", "/blog/month/", "/producttags/" }; + + ReservedUrlRecordSlugs = new List + { + "admin", + "install", + "recentlyviewedproducts", + "newproducts", + "compareproducts", + "clearcomparelist", + "setproductreviewhelpfulness", + "login", + "register", + "logout", + "cart", + "wishlist", + "emailwishlist", + "checkout", + "contactus", + "passwordrecovery", + "subscribenewsletter", + "blog", + "boards", + "inboxupdate", + "sentupdate", + "news", + "sitemap", + "sitemapseo", + "search", + "config", + "api", + "odata" + }; + + SeoNameCharConversion = string.Join(Environment.NewLine, new List + { + "ä;ae", + "ö;oe", + "ü;ue", + "Ä;Ae", + "Ö;Oe", + "Ü;Ue", + "ß;ss" + }); } - + public string PageTitleSeparator { get; set; } public PageTitleSeoAdjustment PageTitleSeoAdjustment { get; set; } public string DefaultTitle { get; set; } public string DefaultMetaKeywords { get; set; } public string DefaultMetaDescription { get; set; } + public string MetaRobotsContent { get; set; } - public bool ConvertNonWesternChars { get; set; } + public bool ConvertNonWesternChars { get; set; } public bool AllowUnicodeCharsInUrls { get; set; } + public string SeoNameCharConversion { get; set; } - public bool CanonicalUrlsEnabled { get; set; } + public bool CanonicalUrlsEnabled { get; set; } public CanonicalHostNameRule CanonicalHostNameRule { get; set; } /// @@ -37,5 +83,10 @@ public SeoSettings() public List ReservedUrlRecordSlugs { get; set; } public List ExtraRobotsDisallows { get; set; } + + /// + /// A value indicating whether to load all URL records and active slugs on application startup + /// + public bool LoadAllUrlAliasesOnStartup { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Shipping/Shipment.cs b/src/Libraries/SmartStore.Core/Domain/Shipping/Shipment.cs index d0841d1dd6..e189e22b12 100644 --- a/src/Libraries/SmartStore.Core/Domain/Shipping/Shipment.cs +++ b/src/Libraries/SmartStore.Core/Domain/Shipping/Shipment.cs @@ -59,6 +59,7 @@ public partial class Shipment : BaseEntity /// /// Gets or sets the shipment items /// + [DataMember] public virtual ICollection ShipmentItems { get { return _shipmentItems ?? (_shipmentItems = new HashSet()); } diff --git a/src/Libraries/SmartStore.Core/Domain/Shipping/ShipmentItem.cs b/src/Libraries/SmartStore.Core/Domain/Shipping/ShipmentItem.cs index 48db08b9e1..fc77fb18b2 100644 --- a/src/Libraries/SmartStore.Core/Domain/Shipping/ShipmentItem.cs +++ b/src/Libraries/SmartStore.Core/Domain/Shipping/ShipmentItem.cs @@ -1,29 +1,35 @@ +using System.Runtime.Serialization; namespace SmartStore.Core.Domain.Shipping { /// /// Represents a shipment order product variant /// + [DataContract] public partial class ShipmentItem : BaseEntity { /// /// Gets or sets the shipment identifier /// + [DataMember] public int ShipmentId { get; set; } /// /// Gets or sets the order item identifier /// + [DataMember] public int OrderItemId { get; set; } /// /// Gets or sets the quantity /// + [DataMember] public int Quantity { get; set; } /// /// Gets the shipment /// + [DataMember] public virtual Shipment Shipment { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingMethod.cs b/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingMethod.cs index 527dbbb655..078b12a20c 100644 --- a/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingMethod.cs +++ b/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingMethod.cs @@ -5,10 +5,10 @@ namespace SmartStore.Core.Domain.Shipping { - /// - /// Represents a shipping method (used for offline shipping rate computation methods) - /// - [DataContract] + /// + /// Represents a shipping method (used for offline shipping rate computation methods) + /// + [DataContract] public partial class ShippingMethod : BaseEntity, ILocalizedEntity { private ICollection _restrictedCountries; diff --git a/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingOption.cs b/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingOption.cs index d059f630b7..5edb06f415 100644 --- a/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingOption.cs +++ b/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingOption.cs @@ -13,6 +13,11 @@ namespace SmartStore.Core.Domain.Shipping /// public partial class ShippingOption { + /// + /// Shipping method identifier + /// + public int ShippingMethodId { get; set; } + /// /// Gets or sets the system name of shipping rate computation method /// @@ -33,134 +38,4 @@ public partial class ShippingOption /// public string Description { get; set; } } - - - public class ShippingOptionTypeConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - - return base.CanConvertFrom(context, sourceType); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - if (value is string) - { - ShippingOption shippingOption = null; - string valueStr = value as string; - if (!String.IsNullOrEmpty(valueStr)) - { - try - { - using (var tr = new StringReader(valueStr)) - { - var xmlS = new XmlSerializer(typeof(ShippingOption)); - shippingOption = (ShippingOption)xmlS.Deserialize(tr); - } - } - catch - { - //xml error - } - } - return shippingOption; - } - return base.ConvertFrom(context, culture, value); - } - - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) - { - if (destinationType == typeof(string)) - { - var shippingOption = value as ShippingOption; - if (shippingOption != null) - { - var sb = new StringBuilder(); - using (var tw = new StringWriter(sb)) - { - var xmlS = new XmlSerializer(typeof(ShippingOption)); - xmlS.Serialize(tw, value); - string serialized = sb.ToString(); - return serialized; - } - } - else - { - return ""; - } - } - - return base.ConvertTo(context, culture, value, destinationType); - } - } - - - public class ShippingOptionListTypeConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - - return base.CanConvertFrom(context, sourceType); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - if (value is string) - { - List shippingOptions = null; - string valueStr = value as string; - if (!String.IsNullOrEmpty(valueStr)) - { - try - { - using (var tr = new StringReader(valueStr)) - { - var xmlS = new XmlSerializer(typeof(List)); - shippingOptions = (List)xmlS.Deserialize(tr); - } - } - catch - { - //xml error - } - } - return shippingOptions; - } - return base.ConvertFrom(context, culture, value); - } - - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) - { - if (destinationType == typeof(string)) - { - var shippingOptions = value as List; - if (shippingOptions != null) - { - var sb = new StringBuilder(); - using (var tw = new StringWriter(sb)) - { - var xmlS = new XmlSerializer(typeof(List)); - xmlS.Serialize(tw, value); - string serialized = sb.ToString(); - return serialized; - } - } - else - { - return ""; - } - } - - return base.ConvertTo(context, culture, value, destinationType); - } - } } diff --git a/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingStatus.cs b/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingStatus.cs index 64c1cde1e2..18bf8246d6 100644 --- a/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingStatus.cs +++ b/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingStatus.cs @@ -3,7 +3,7 @@ namespace SmartStore.Core.Domain.Shipping /// /// Represents the shipping status enumeration /// - public enum ShippingStatus : int + public enum ShippingStatus { /// /// Shipping not required diff --git a/src/Libraries/SmartStore.Core/Domain/StoreInformationSettings.cs b/src/Libraries/SmartStore.Core/Domain/StoreInformationSettings.cs index 079e3831a1..b486b2acc3 100644 --- a/src/Libraries/SmartStore.Core/Domain/StoreInformationSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/StoreInformationSettings.cs @@ -19,8 +19,6 @@ public StoreInformationSettings() /// public bool StoreClosedAllowForAdmins { get; set; } - // codehint: sm-delete - /// /// Gets or sets a value indicating whether mini profiler should be displayed in public store (used for debugging) /// diff --git a/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs b/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs index 0b5c7e30b2..0d9fe6be96 100644 --- a/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs +++ b/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs @@ -1,4 +1,5 @@ using System.Runtime.Serialization; +using SmartStore.Core.Domain.Directory; namespace SmartStore.Core.Domain.Stores { @@ -59,8 +60,34 @@ public partial class Store : BaseEntity /// /// Gets or sets the CDN host name, if static media content should be served through a CDN. /// + [DataMember] public string ContentDeliveryNetwork { get; set; } + /// + /// Gets or sets the primary store currency identifier + /// + [DataMember] + public int PrimaryStoreCurrencyId { get; set; } + + /// + /// Gets or sets the primary exchange rate currency identifier + /// + [DataMember] + public int PrimaryExchangeRateCurrencyId { get; set; } + + /// + /// Gets or sets the primary store currency + /// + [DataMember] + public virtual Currency PrimaryStoreCurrency { get; set; } + + /// + /// Gets or sets the primary exchange rate currency + /// + [DataMember] + public virtual Currency PrimaryExchangeRateCurrency { get; set; } + + /// /// Gets the security mode for the store /// diff --git a/src/Libraries/SmartStore.Core/Domain/Stores/StoreExtensions.cs b/src/Libraries/SmartStore.Core/Domain/Stores/StoreExtensions.cs index fe7812464c..3273ab21da 100644 --- a/src/Libraries/SmartStore.Core/Domain/Stores/StoreExtensions.cs +++ b/src/Libraries/SmartStore.Core/Domain/Stores/StoreExtensions.cs @@ -17,16 +17,12 @@ public static string[] ParseHostValues(this Store store) throw new ArgumentNullException("store"); var parsedValues = new List(); - if (!String.IsNullOrEmpty(store.Hosts)) + if (!string.IsNullOrEmpty(store.Hosts)) { - string[] hosts = store.Hosts.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - foreach (string host in hosts) - { - var tmp = host.Trim(); - if (!String.IsNullOrEmpty(tmp)) - parsedValues.Add(tmp); - } + var hosts = store.Hosts.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + parsedValues.AddRange(hosts.Select(host => host.Trim()).Where(tmp => !string.IsNullOrEmpty(tmp))); } + return parsedValues.ToArray(); } diff --git a/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs b/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs index c04458e8de..5806d83c4f 100644 --- a/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs +++ b/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs @@ -1,9 +1,13 @@  using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Diagnostics; namespace SmartStore.Core.Domain.Tasks { - public class ScheduleTask : BaseEntity + [DebuggerDisplay("{Name} (Type: {Type})")] + public class ScheduleTask : BaseEntity, ICloneable { /// /// Gets or sets the name @@ -11,18 +15,25 @@ public class ScheduleTask : BaseEntity public string Name { get; set; } /// - /// Gets or sets the run period (in seconds) + /// Gets or sets the task alias (an optional key for advanced customization) /// - public int Seconds { get; set; } + public string Alias { get; set; } + + /// + /// Gets or sets the CRON expression used to calculate future schedules + /// + public string CronExpression { get; set; } /// /// Gets or sets the type of appropriate ITask class /// + [Index("IX_Type")] public string Type { get; set; } /// /// Gets or sets the value indicating whether a task is enabled /// + [Index("IX_NextRun_Enabled", 1)] public bool Enabled { get; set; } /// @@ -30,25 +41,69 @@ public class ScheduleTask : BaseEntity /// public bool StopOnError { get; set; } + [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; } + 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 the value indicating whether a task is running + /// Gets a value indicating whether a task is running /// public bool IsRunning { get { - var result = (LastStartUtc.HasValue && LastStartUtc.Value > LastEndUtc.GetValueOrDefault()); + var result = LastStartUtc.HasValue && LastStartUtc.Value > LastEndUtc.GetValueOrDefault(); + return result; + } + } + /// + /// Gets a value indicating whether a task is scheduled for execution (Enabled = true and NextRunUtc <= UtcNow ) + /// + public bool IsPending + { + get + { + var result = Enabled && NextRunUtc.HasValue && NextRunUtc <= DateTime.UtcNow; return result; } } - } + + public ScheduleTask Clone() + { + return (ScheduleTask)this.MemberwiseClone(); + } + + object ICloneable.Clone() + { + return this.MemberwiseClone(); + } + } } diff --git a/src/Libraries/SmartStore.Core/Domain/Tax/TaxSettings.cs b/src/Libraries/SmartStore.Core/Domain/Tax/TaxSettings.cs index 7a342abf17..5a69937cac 100644 --- a/src/Libraries/SmartStore.Core/Domain/Tax/TaxSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Tax/TaxSettings.cs @@ -137,5 +137,10 @@ public TaxSettings() /// Gets or sets a value indicating whether we should notify a store owner when a new VAT number is submitted /// public bool EuVatEmailAdminWhenNewVatSubmitted { get; set; } + + /// + /// Gets or sets a value indicating whether a VAT-ID is required + /// + public bool VatRequired { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Themes/ThemeSettings.cs b/src/Libraries/SmartStore.Core/Domain/Themes/ThemeSettings.cs index 83fd09bf3b..43517c8410 100644 --- a/src/Libraries/SmartStore.Core/Domain/Themes/ThemeSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Themes/ThemeSettings.cs @@ -38,6 +38,15 @@ public ThemeSettings() /// public bool AllowCustomerToSelectTheme { get; set; } + /// + /// Gets or sets a value indicating whether user's theme choice should be saved in a cookie + /// + /// + /// If false, user's theme choice is associated to the account, + /// which may be undesirable when, for example, multiple users share a guest account. + /// + public bool SaveThemeChoiceInCookie { get; set; } + /// /// Gets or sets a value indicating whether mobile devices supported /// diff --git a/src/Libraries/SmartStore.Core/Email/Attachment.cs b/src/Libraries/SmartStore.Core/Email/Attachment.cs deleted file mode 100644 index 437848f0ac..0000000000 --- a/src/Libraries/SmartStore.Core/Email/Attachment.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Text; -using System.IO; -using System.Net.Mime; -using net = System.Net.Mail; - -namespace SmartStore.Core.Email -{ - /// - /// attachment-multipart - /// - public class Attachment - { - //private readonly net.Attachment _attachment; - private net.Attachment _attachment; - - public Attachment() - { - MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes("")); - this.Stream = stream; - - _attachment = new net.Attachment(this.Stream, ""); - } - - public Attachment(string filePath) - { - _attachment = new net.Attachment(filePath); - this.GetContentFromFile(filePath); - } - - public Attachment(string filePath, string mediaType) - { - _attachment = new net.Attachment(filePath, mediaType); - } - - public ContentType ContentType - { - get - { - return _attachment.ContentType; - } - set - { - _attachment.ContentType = value; - } - } - public string Name - { - get - { - return _attachment.Name; - } - set - { - _attachment.Name = value; - } - } - public string MediaType - { - get - { - return _attachment.ContentType.MediaType; - } - set - { - _attachment.ContentType.MediaType = value; - } - } - public ContentDisposition ContentDisposition - { - get - { - return _attachment.ContentDisposition; - } - } - public TransferEncoding ContentTransferEncoding - { - get - { - return _attachment.TransferEncoding; - } - set - { - _attachment.TransferEncoding = value; - } - } - public string ContentDescription { get; set; } - public Stream Stream { get; set; } - //public string FileName { get; set; } - - public void GetContentFromFile(string location) - { - byte[] buffer; - FileStream fileStream = new FileStream(location, FileMode.Open, FileAccess.Read); - - int length = (int)fileStream.Length; - buffer = new byte[length]; - int count; - int sum = 0; - - while ((count = fileStream.Read(buffer, sum, length - sum)) > 0) - sum += count; - - this.Stream = new MemoryStream(buffer); - - string extension = Path.GetExtension(location); - string filename = Path.GetFileName(location); - - switch(extension) - { - case ".txt": - this.MediaType = "text/plain"; - break; - case ".jpg": - this.MediaType = "image/jpeg"; - break; - default: - this.MediaType = "application/octet-stream"; - break; - } - - this.Name = filename; - this.ContentTransferEncoding = TransferEncoding.Base64; - this.ContentDisposition.FileName = filename; - this.ContentDisposition.DispositionType = "attachment"; - } - - public void GetContentFromString(string content) - { - this.Stream = new MemoryStream(Encoding.UTF8.GetBytes(content)); - //this.Stream = new MemoryStream(Convert.FromBase64String(content)); - } - - public void GetContentFromBase64String(string content) - { - this.Stream = new MemoryStream(Convert.FromBase64String(content)); - } - - //NEW METHODS TO WRAP ATTACHMENT - //TODO: Testen - public net.Attachment Instance - { - get - { - return _attachment; - } - set - { - _attachment = value; - } - } - - public Attachment CreateAttachmentFromString(string content, ContentType contentType) - { - this.Instance = net.Attachment.CreateAttachmentFromString(content, contentType); - return InitAttachment(_attachment); - } - - public Attachment CreateAttachmentFromString(string content, string name) - { - this.Instance = net.Attachment.CreateAttachmentFromString(content, name); - return InitAttachment(_attachment); - } - - public Attachment CreateAttachmentFromString(string content, string name, Encoding contentEncoding, string mediaType) - { - this.Instance = net.Attachment.CreateAttachmentFromString(content, name, contentEncoding, mediaType); - return InitAttachment(_attachment); - } - - public Attachment InitAttachment(net.Attachment tempAttachment) - { - this.Stream = tempAttachment.ContentStream; - this.Name = tempAttachment.Name; - this.ContentTransferEncoding = tempAttachment.TransferEncoding; - this.ContentType = tempAttachment.ContentType; - this.MediaType = tempAttachment.ContentType.MediaType; - - return this; - } - - } -} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Email/DefaultEmailSender.cs b/src/Libraries/SmartStore.Core/Email/DefaultEmailSender.cs index 9e5ebd2543..b9862e0c52 100644 --- a/src/Libraries/SmartStore.Core/Email/DefaultEmailSender.cs +++ b/src/Libraries/SmartStore.Core/Email/DefaultEmailSender.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using net = System.Net.Mail; +using System.Net.Mail; using System.Net.Mime; using System.Net; using System.IO; @@ -14,18 +14,14 @@ namespace SmartStore.Core.Email public class DefaultEmailSender : IEmailSender { - public DefaultEmailSender() - { - } - /// /// Builds System.Net.Mail.Message /// /// SmartStore.Email.Message /// System.Net.Mail.Message - protected virtual net.MailMessage BuildMailMessage(EmailMessage original) + protected virtual MailMessage BuildMailMessage(EmailMessage original) { - net.MailMessage msg = new net.MailMessage(); + MailMessage msg = new MailMessage(); if (String.IsNullOrEmpty(original.Subject)) { @@ -37,15 +33,15 @@ protected virtual net.MailMessage BuildMailMessage(EmailMessage original) if (original.AltText.HasValue()) { - msg.AlternateViews.Add(net.AlternateView.CreateAlternateViewFromString(original.AltText, new ContentType("text/html"))); - msg.AlternateViews.Add(net.AlternateView.CreateAlternateViewFromString(original.Body, new ContentType("text/plain"))); + msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(original.AltText, new ContentType("text/html"))); + msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(original.Body, new ContentType("text/plain"))); } else { msg.Body = original.Body; } - msg.DeliveryNotificationOptions = net.DeliveryNotificationOptions.None; + msg.DeliveryNotificationOptions = DeliveryNotificationOptions.None; msg.From = original.From.ToMailAddress(); @@ -54,31 +50,7 @@ protected virtual net.MailMessage BuildMailMessage(EmailMessage original) msg.Bcc.AddRange(original.Bcc.Where(x => x.Address.HasValue()).Select(x => x.ToMailAddress())); msg.ReplyToList.AddRange(original.ReplyTo.Where(x => x.Address.HasValue()).Select(x => x.ToMailAddress())); - foreach (Attachment attachment in original.Attachments) - { - byte[] byteData; - - if (attachment.ContentTransferEncoding == TransferEncoding.Base64) - { - using (var sr = new StreamReader(attachment.Stream)) - { - byteData = Convert.FromBase64String(sr.ReadToEnd()); - } - } - else - { - byteData = attachment.Stream.ToByteArray(); - } - - MemoryStream s = new MemoryStream(byteData); - net.Attachment att = new net.Attachment(s, attachment.Name, attachment.ContentType.MediaType); - - att.ContentType.MediaType = attachment.MediaType; - att.TransferEncoding = attachment.ContentTransferEncoding; - att.ContentDisposition.DispositionType = attachment.ContentDisposition.DispositionType; - - msg.Attachments.Add(att); - } + msg.Attachments.AddRange(original.Attachments); if (original.Headers != null) msg.Headers.AddRange(original.Headers); @@ -96,11 +68,12 @@ public void SendEmail(SmtpContext context, EmailMessage message) Guard.ArgumentNotNull(() => context); Guard.ArgumentNotNull(() => message); - var msg = this.BuildMailMessage(message); - - using (var client = context.ToSmtpClient()) + using (var msg = this.BuildMailMessage(message)) { - client.Send(msg); + using (var client = context.ToSmtpClient()) + { + client.Send(msg); + } } } @@ -109,12 +82,14 @@ public Task SendEmailAsync(SmtpContext context, EmailMessage message) Guard.ArgumentNotNull(() => context); Guard.ArgumentNotNull(() => message); + var client = context.ToSmtpClient(); var msg = this.BuildMailMessage(message); - using (var client = context.ToSmtpClient()) + return client.SendMailAsync(msg).ContinueWith(t => { - return client.SendMailAsync(msg); - } + client.Dispose(); + msg.Dispose(); + }); } #endregion diff --git a/src/Libraries/SmartStore.Core/Email/EmailMessage.cs b/src/Libraries/SmartStore.Core/Email/EmailMessage.cs index 58a8508c07..683071e498 100644 --- a/src/Libraries/SmartStore.Core/Email/EmailMessage.cs +++ b/src/Libraries/SmartStore.Core/Email/EmailMessage.cs @@ -2,15 +2,9 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Net.Mail; -using System.Net.Mime; -using System.Xml.Serialization; -using System.Linq; using System.IO; using System.Text; -using System.Xml; -using System.Web; using System.Net; -using System.Threading.Tasks; namespace SmartStore.Core.Email { @@ -78,18 +72,13 @@ public EmailMessage(EmailAddress to, string subject, string body, EmailAddress f public NameValueCollection Headers { get; private set; } - public void AddAttachment(Attachment attachment) - { - this.Attachments.Add(attachment); - } - public async void BodyFromFile(string filePathOrUrl) { - StreamReader sr = null; + StreamReader sr; if (filePathOrUrl.ToLower().StartsWith("http")) { - WebClient wc = new WebClient(); + var wc = new WebClient(); sr = new StreamReader(await wc.OpenReadTaskAsync(filePathOrUrl)); } else diff --git a/src/Libraries/SmartStore.Core/Email/SmtpContext.cs b/src/Libraries/SmartStore.Core/Email/SmtpContext.cs index 796e642109..054487610f 100644 --- a/src/Libraries/SmartStore.Core/Email/SmtpContext.cs +++ b/src/Libraries/SmartStore.Core/Email/SmtpContext.cs @@ -73,6 +73,8 @@ public SmtpClient ToSmtpClient() smtpClient.UseDefaultCredentials = this.UseDefaultCredentials; smtpClient.EnableSsl = this.EnableSsl; + smtpClient.Timeout = 10000; + if (this.UseDefaultCredentials) { smtpClient.Credentials = CredentialCache.DefaultNetworkCredentials; diff --git a/src/Libraries/SmartStore.Core/Events/CommonMessages/AppInitScheduledTasksEvent.cs b/src/Libraries/SmartStore.Core/Events/CommonMessages/AppInitScheduledTasksEvent.cs index 9f10cd94dc..348b5fac89 100644 --- a/src/Libraries/SmartStore.Core/Events/CommonMessages/AppInitScheduledTasksEvent.cs +++ b/src/Libraries/SmartStore.Core/Events/CommonMessages/AppInitScheduledTasksEvent.cs @@ -9,9 +9,8 @@ namespace SmartStore.Core.Events /// /// to initialize scheduled tasks in Application_Start /// - /// codehint: sm-add public class AppInitScheduledTasksEvent { - public List ScheduledTasks { get; set; } + public IList ScheduledTasks { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Events/CommonMessages/AppRegisterGlobalFiltersEvent.cs b/src/Libraries/SmartStore.Core/Events/CommonMessages/AppRegisterGlobalFiltersEvent.cs index f7c0c63e4e..d825cc079d 100644 --- a/src/Libraries/SmartStore.Core/Events/CommonMessages/AppRegisterGlobalFiltersEvent.cs +++ b/src/Libraries/SmartStore.Core/Events/CommonMessages/AppRegisterGlobalFiltersEvent.cs @@ -7,7 +7,6 @@ namespace SmartStore.Core.Events /// /// to register global filters in Application_Start /// - /// codehint: sm-add public class AppRegisterGlobalFiltersEvent { public GlobalFilterCollection Filters { get; set; } diff --git a/src/Libraries/SmartStore.Core/Events/EventConsumerMetadata.cs b/src/Libraries/SmartStore.Core/Events/EventConsumerMetadata.cs index dcc676e784..1ae4f063fb 100644 --- a/src/Libraries/SmartStore.Core/Events/EventConsumerMetadata.cs +++ b/src/Libraries/SmartStore.Core/Events/EventConsumerMetadata.cs @@ -1,4 +1,5 @@ using System; +using SmartStore.Core.Plugins; namespace SmartStore.Core.Events { @@ -6,5 +7,6 @@ public class EventConsumerMetadata { public bool ExecuteAsync { get; set; } public bool IsActive { get; set; } + public PluginDescriptor PluginDescriptor { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs index a4a602abc5..b6fec3780a 100644 --- a/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs @@ -7,10 +7,8 @@ namespace SmartStore { - public static class CollectionExtensions { - public static void AddRange(this ICollection initial, IEnumerable other) { if (other == null) @@ -31,31 +29,5 @@ public static bool IsNullOrEmpty(this ICollection source) { return (source == null || source.Count == 0); } - - //public static bool HasItems(this IEnumerable source) - //{ - // return source != null && source.GetEnumerator().MoveNext(); - //} - - public static bool EqualsAll(this IList a, IList b) - { - if (a == null || b == null) - return (a == null && b == null); - - if (a.Count != b.Count) - return false; - - EqualityComparer comparer = EqualityComparer.Default; - - for (int i = 0; i < a.Count; i++) - { - if (!comparer.Equals(a[i], b[i])) - return false; - } - - return true; - } - } - } diff --git a/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs index 2ee0fe32fc..a97d4862bf 100644 --- a/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs @@ -1,209 +1,83 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.IO; -using System.Drawing; -using System.ComponentModel; -using System.Drawing.Imaging; +using System.Diagnostics; using System.Globalization; -using System.Collections; -using System.Reflection; +using System.IO; using System.Runtime.Serialization.Formatters.Binary; -using SmartStore.Utilities; -using System.Diagnostics; using System.Security.Cryptography; -using SmartStore.Core.ComponentModel; -using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Domain.Catalog; +using System.Text; +using System.Text.RegularExpressions; +using SmartStore.ComponentModel; +using SmartStore.Utilities; namespace SmartStore { - - public static class ConversionExtensions + public static class ConversionExtensions { - private readonly static IDictionary s_customTypeConverters; - - static ConversionExtensions() - { - var intConverter = new GenericListTypeConverter(); - var decConverter = new GenericListTypeConverter(); - var stringConverter = new GenericListTypeConverter(); - var soListConverter = new ShippingOptionListTypeConverter(); - var bundleDataListConverter = new ProductBundleDataListTypeConverter(); - - s_customTypeConverters = new Dictionary(); - s_customTypeConverters.Add(typeof(List), intConverter); - s_customTypeConverters.Add(typeof(IList), intConverter); - s_customTypeConverters.Add(typeof(List), decConverter); - s_customTypeConverters.Add(typeof(IList), decConverter); - s_customTypeConverters.Add(typeof(List), stringConverter); - s_customTypeConverters.Add(typeof(IList), stringConverter); - s_customTypeConverters.Add(typeof(ShippingOption), new ShippingOptionTypeConverter()); - s_customTypeConverters.Add(typeof(List), soListConverter); - s_customTypeConverters.Add(typeof(IList), soListConverter); - s_customTypeConverters.Add(typeof(List), bundleDataListConverter); - s_customTypeConverters.Add(typeof(IList), bundleDataListConverter); - } - #region Object public static T Convert(this object value) { - return (T)Convert(value, typeof(T)); - } - - public static T Convert(this object value, CultureInfo culture) - { - return (T)Convert(value, typeof(T), culture); - } + return (T)(Convert(value, typeof(T)) ?? default(T)); + } - public static object Convert(this object value, Type to) - { - return value.Convert(to, CultureInfo.InvariantCulture); - } + public static T Convert(this object value, T defaultValue) + { + return (T)(Convert(value, typeof(T)) ?? defaultValue); + } - public static object Convert(this object value, Type to, CultureInfo culture) + public static T Convert(this object value, CultureInfo culture) { - Guard.ArgumentNotNull(to, "to"); - - if (value == null || to.IsInstanceOfType(value)) - { - return value; - } - - // array conversion results in four cases, as below - Array valueAsArray = value as Array; - if (to.IsArray) - { - Type destinationElementType = to.GetElementType(); - if (valueAsArray != null) - { - // case 1: both destination + source type are arrays, so convert each element - IList valueAsList = (IList)valueAsArray; - IList converted = Array.CreateInstance(destinationElementType, valueAsList.Count); - for (int i = 0; i < valueAsList.Count; i++) - { - converted[i] = valueAsList[i].Convert(destinationElementType, culture); - } - return converted; - } - else - { - // case 2: destination type is array but source is single element, so wrap element in array + convert - object element = value.Convert(destinationElementType, culture); - IList converted = Array.CreateInstance(destinationElementType, 1); - converted[0] = element; - return converted; - } - } - else if (valueAsArray != null) - { - // case 3: destination type is single element but source is array, so extract first element + convert - IList valueAsList = (IList)valueAsArray; - if (valueAsList.Count > 0) - { - value = valueAsList[0]; - } - // .. fallthrough to case 4 - } - // case 4: both destination + source type are single elements, so convert - - Type fromType = value.GetType(); - - //if (to.IsInterface || to.IsGenericTypeDefinition || to.IsAbstract) - // throw Error.Argument("to", "Target type '{0}' is not a value type or a non-abstract class.", to.FullName); - - // use Convert.ChangeType if both types are IConvertible - if (value is IConvertible && typeof(IConvertible).IsAssignableFrom(to)) - { - if (to.IsEnum) - { - if (value is string) - return Enum.Parse(to, value.ToString(), true); - else if (fromType.IsInteger()) - return Enum.ToObject(to, value); - } + return (T)(Convert(value, typeof(T), culture) ?? default(T)); + } - return System.Convert.ChangeType(value, to, culture); - } + public static T Convert(this object value, T defaultValue, CultureInfo culture) + { + return (T)(Convert(value, typeof(T), culture) ?? defaultValue); + } - if (value is DateTime && to == typeof(DateTimeOffset)) - return new DateTimeOffset((DateTime)value); + public static object Convert(this object value, Type to) + { + return value.Convert(to, CultureInfo.InvariantCulture); + } - if (value is string && to == typeof(Guid)) - return new Guid((string)value); + public static object Convert(this object value, Type to, CultureInfo culture) + { + Guard.ArgumentNotNull(to, "to"); - // see if source or target types have a TypeConverter that converts between the two - TypeConverter toConverter = GetTypeConverter(fromType); + if (value == null || value == DBNull.Value || to.IsInstanceOfType(value)) + { + return value == DBNull.Value ? null : value; + } - Type nonNullableTo = to.GetNonNullableType(); - bool isNullableTo = to != nonNullableTo; + Type from = value.GetType(); - if (toConverter != null && toConverter.CanConvertTo(nonNullableTo)) - { - object result = toConverter.ConvertTo(null, culture, value, nonNullableTo); - return isNullableTo ? Activator.CreateInstance(typeof(Nullable<>).MakeGenericType(nonNullableTo), result) : result; + if (culture == null) + { + culture = CultureInfo.InvariantCulture; } - TypeConverter fromConverter = GetTypeConverter(nonNullableTo); - - if (fromConverter != null && fromConverter.CanConvertFrom(fromType)) - { - object result = fromConverter.ConvertFrom(null, culture, value); - return isNullableTo ? Activator.CreateInstance(typeof(Nullable<>).MakeGenericType(nonNullableTo), result) : result; - } - - // TypeConverter doesn't like Double to Decimal - if (fromType == typeof(double) && nonNullableTo == typeof(decimal)) + // get a converter for 'to' (value -> to) + var converter = TypeConverterFactory.GetConverter(to); + if (converter != null && converter.CanConvertFrom(from)) { - decimal result = new Decimal((double)value); - return isNullableTo ? Activator.CreateInstance(typeof(Nullable<>).MakeGenericType(nonNullableTo), result) : result; + return converter.ConvertFrom(culture, value); } - throw Error.InvalidCast(fromType, to); - - #region OBSOLETE - // TypeConverter converter = TypeDescriptor.GetConverter(to); - // bool canConvertFrom = converter.CanConvertFrom(value.GetType()); - // if (!canConvertFrom) - // { - // converter = TypeDescriptor.GetConverter(value.GetType()); - // } - // if (!(canConvertFrom || converter.CanConvertTo(to))) - // { - // throw Error.InvalidOperation(@"The parameter conversion from type '{0}' to type '{1}' failed - // because no TypeConverter can convert between these types.", - // value.GetType().FullName, - // to.FullName); - // } - - // try - // { - // CultureInfo cultureToUse = culture ?? CultureInfo.CurrentCulture; - // object convertedValue = (canConvertFrom) ? - // converter.ConvertFrom(null /* context */, cultureToUse, value) : - // converter.ConvertTo(null /* context */, cultureToUse, value, to); - // return convertedValue; - // } - // catch (Exception ex) - // { - // throw Error.InvalidOperation(@"The parameter conversion from type '{0}' to type '{1}' failed. - // See the inner exception for more information.", ex, - // value.GetType().FullName, - // to.FullName); - // } - #endregion - } + // try the other way round with a 'from' converter (to <- from) + converter = TypeConverterFactory.GetConverter(from); + if (converter != null && converter.CanConvertTo(to)) + { + return converter.ConvertTo(culture, null, value, to); + } - internal static TypeConverter GetTypeConverter(Type type) - { - TypeConverter converter; - if (s_customTypeConverters.TryGetValue(type, out converter)) + // use Convert.ChangeType if both types are IConvertible + if (value is IConvertible && typeof(IConvertible).IsAssignableFrom(to)) { - return converter; + return System.Convert.ChangeType(value, to, culture); } - return TypeDescriptor.GetConverter(type); + + throw Error.InvalidCast(from, to); } #endregion @@ -219,187 +93,64 @@ public static char ToHex(this int value) return (char)((value - 10) + 97); } - /// - /// Returns kilobytes - /// - /// - /// - public static int ToKb(this int value) - { - return value * 1024; - } - - /// - /// Returns megabytes - /// - /// - /// - public static int ToMb(this int value) - { - return value * 1024 * 1024; - } - - /// Returns a that represents a specified number of minutes. - /// number of minutes - /// A that represents a value. - /// 3.Minutes() - public static TimeSpan ToMinutes(this int minutes) - { - return TimeSpan.FromMinutes(minutes); - } - - /// - /// Returns a that represents a specified number of seconds. - /// - /// number of seconds - /// A that represents a value. - /// 2.Seconds() - public static TimeSpan ToSeconds(this int seconds) - { - return TimeSpan.FromSeconds(seconds); - } - - /// - /// Returns a that represents a specified number of milliseconds. - /// - /// milliseconds for this timespan - /// A that represents a value. - public static TimeSpan ToMilliseconds(this int milliseconds) - { - return TimeSpan.FromMilliseconds(milliseconds); - } - - /// - /// Returns a that represents a specified number of days. - /// - /// Number of days. - /// A that represents a value. - public static TimeSpan ToDays(this int days) - { - return TimeSpan.FromDays(days); - } - - - /// - /// Returns a that represents a specified number of hours. - /// - /// Number of hours. - /// A that represents a value. - public static TimeSpan ToHours(this int hours) - { - return TimeSpan.FromHours(hours); - } - - #endregion - - #region double - - /// Returns a that represents a specified number of minutes. - /// number of minutes - /// A that represents a value. - /// 3D.Minutes() - public static TimeSpan ToMinutes(this double minutes) - { - return TimeSpan.FromMinutes(minutes); - } - - - /// Returns a that represents a specified number of hours. - /// number of hours - /// A that represents a value. - /// 3D.Hours() - public static TimeSpan ToHours(this double hours) - { - return TimeSpan.FromHours(hours); - } - - /// Returns a that represents a specified number of seconds. - /// number of seconds - /// A that represents a value. - /// 2D.Seconds() - public static TimeSpan ToSeconds(this double seconds) - { - return TimeSpan.FromSeconds(seconds); - } - - /// Returns a that represents a specified number of milliseconds. - /// milliseconds for this timespan - /// A that represents a value. - public static TimeSpan ToMilliseconds(this double milliseconds) - { - return TimeSpan.FromMilliseconds(milliseconds); - } - - /// - /// Returns a that represents a specified number of days. - /// - /// Number of days, accurate to the milliseconds. - /// A that represents a value. - public static TimeSpan ToDays(this double days) - { - return TimeSpan.FromDays(days); - } - #endregion #region String public static T ToEnum(this string value, T defaultValue) where T : IComparable, IFormattable { - T convertedValue = defaultValue; + Guard.ArgumentIsEnumType(typeof(T), "T"); - if (!string.IsNullOrEmpty(value)) - { - try - { - convertedValue = (T)Enum.Parse(typeof(T), value.Trim(), true); - } - catch (ArgumentException) - { - } - } - - return convertedValue; - } - - public static string[] ToArray(this string value) - { - return value.ToArray(new char[] { ',' }); - } + T result; + if (CommonHelper.TryConvert(value, out result)) + { + return result; + } - public static string[] ToArray(this string value, params char[] separator) - { - return value.Trim().Split(separator, StringSplitOptions.RemoveEmptyEntries); + return defaultValue; } public static int ToInt(this string value, int defaultValue = 0) { int result; - if (int.TryParse(value, out result)) - { - return result; - } - return defaultValue; + if (CommonHelper.TryConvert(value, out result)) + { + return result; + } + + return defaultValue; } - public static float ToFloat(this string value, float 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)) + { + return result; + } + return defaultValue; + } + + public static float ToFloat(this string value, float defaultValue = 0) { float result; - if (float.TryParse(value, out result)) - { - return result; - } - return defaultValue; + if (CommonHelper.TryConvert(value, out result)) + { + return result; + } + + return defaultValue; } public static bool ToBool(this string value, bool defaultValue = false) { bool result; - if (bool.TryParse(value, out result)) - { - return result; - } - return defaultValue; + if (CommonHelper.TryConvert(value, out result)) + { + return result; + } + + return defaultValue; } public static DateTime? ToDateTime(this string value, DateTime? defaultValue) @@ -432,24 +183,25 @@ public static bool ToBool(this string value, bool defaultValue = false) return defaultValue; } - public static Guid ToGuid(this string value) - { - if ((!String.IsNullOrEmpty(value)) && (value.Trim().Length == 22)) - { - string encoded = string.Concat(value.Trim().Replace("-", "+").Replace("_", "/"), "=="); - - byte[] base64 = System.Convert.FromBase64String(encoded); - - return new Guid(base64); - } - - return Guid.Empty; - } + /// + /// Parse ISO-8601 UTC timestamp including milliseconds. + /// + /// + /// Dublicate can be found in HmacAuthentication class. + /// + public static DateTime? ToDateTimeIso8601(this string value) + { + if (value.HasValue()) + { + DateTime dt; + if (DateTime.TryParseExact(value, "o", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt)) + return dt; - public static byte[] ToByteArray(this string value) - { - return Encoding.Default.GetBytes(value); - } + if (DateTime.TryParseExact(value, "yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt)) + return dt; + } + return null; + } [DebuggerStepThrough] public static Version ToVersion(this string value, Version defaultVersion = null) @@ -466,67 +218,49 @@ public static Version ToVersion(this string value, Version defaultVersion = null #endregion - #region DateTime - - // [...] - - #endregion - #region Stream public static byte[] ToByteArray(this Stream stream) { Guard.ArgumentNotNull(stream, "stream"); - byte[] buffer; - - if (stream is MemoryStream && stream.CanRead && stream.CanSeek) - { - int len = System.Convert.ToInt32(stream.Length); - buffer = new byte[len]; - stream.Read(buffer, 0, len); - return buffer; - } - - MemoryStream memStream = null; - try - { - buffer = new byte[1024]; - memStream = new MemoryStream(); - int bytesRead = stream.Read(buffer, 0, buffer.Length); - if (bytesRead > 0) - { - memStream.Write(buffer, 0, bytesRead); - bytesRead = stream.Read(buffer, 0, buffer.Length); - } - } - finally - { - if (memStream != null) - memStream.Close(); - } - - if (memStream != null) - { - return memStream.ToArray(); - } - - return null; + if (stream is MemoryStream) + { + return ((MemoryStream)stream).ToArray(); + } + else + { + using (var ms = new MemoryStream()) + { + stream.CopyTo(ms); + return ms.ToArray(); + } + } } - public static string AsString(this Stream stream) + public static string AsString(this Stream stream) + { + return stream.AsString(Encoding.UTF8); + } + + public static string AsString(this Stream stream, Encoding encoding) { - // convert memory stream to string + Guard.ArgumentNotNull(() => encoding); + + // convert stream to string string result; - stream.Position = 0; - using (StreamReader sr = new StreamReader(stream)) + if (stream.CanSeek) + { + stream.Position = 0; + } + + using (StreamReader sr = new StreamReader(stream, encoding)) { result = sr.ReadToEnd(); } return result; - } #endregion @@ -549,32 +283,18 @@ public static object ToObject(this byte[] bytes) } } - public static Image ToImage(this byte[] bytes) - { - using (var stream = new MemoryStream(bytes)) - { - return Image.FromStream(stream); - } - } - public static Stream ToStream(this byte[] bytes) { return new MemoryStream(bytes); } - public static string AsString(this byte[] bytes) - { - return Encoding.Default.GetString(bytes); - } - - - /// - /// Computes the MD5 hash of a byte array - /// - /// The byte array to compute the hash for - /// The hash value - //[DebuggerStepThrough] - public static string Hash(this byte[] value, bool toBase64 = false) + /// + /// Computes the MD5 hash of a byte array + /// + /// The byte array to compute the hash for + /// The hash value + [DebuggerStepThrough] + public static string Hash(this byte[] value, bool toBase64 = false) { Guard.ArgumentNotNull(value, "value"); @@ -603,45 +323,6 @@ public static string Hash(this byte[] value, bool toBase64 = false) #endregion - #region Image/Bitmap - - public static byte[] ToByteArray(this Image image) - { - Guard.ArgumentNotNull(() => image); - - byte[] bytes; - - ImageConverter converter = new ImageConverter(); - bytes = (byte[])converter.ConvertTo(image, typeof(byte[])); - return bytes; - } - - internal static byte[] ToByteArray(this Image image, ImageFormat format) - { - Guard.ArgumentNotNull(() => image); - Guard.ArgumentNotNull(() => format); - - using (var stream = new MemoryStream()) - { - image.Save(stream, format); - return stream.ToByteArray(); - } - } - - internal static Image ConvertTo(this Image image, ImageFormat format) - { - Guard.ArgumentNotNull(() => image); - Guard.ArgumentNotNull(() => format); - - using (var stream = new MemoryStream()) - { - image.Save(stream, format); - return Image.FromStream(stream); - } - } - - #endregion - #region Enumerable: Collections/List/Dictionary... public static T ToObject(this IDictionary values) where T : class diff --git a/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs index 0ae64d902a..3070172cdc 100644 --- a/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs @@ -5,24 +5,10 @@ namespace SmartStore { - public static class DateTimeExtensions { - private static readonly DateTime MinDate = new DateTime(1900, 1, 1); - private static readonly DateTime MaxDate = new DateTime(9999, 12, 31, 23, 59, 59, 999); public static readonly DateTime BeginOfEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - public static bool IsValid(this DateTime value) - { - return (value >= MinDate) && (value <= MaxDate); - } - - public static string GetLocalOffset(this DateTime value) - { - TimeSpan utcOffset = TimeZoneInfo.Local.GetUtcOffset(value); - return utcOffset.Hours.ToString("+00;-00", CultureInfo.InvariantCulture) + ":" + utcOffset.Minutes.ToString("00;00", CultureInfo.InvariantCulture); - } - /// /// Converts a nullable date/time value to UTC. /// @@ -33,38 +19,6 @@ public static string GetLocalOffset(this DateTime value) return dateTime.HasValue ? dateTime.Value.ToUniversalTime() : (DateTime?)null; } - /// - /// Returns a copy of a date/time value with its kind - /// set to but does not perform - /// any time-zone adjustment. - /// - /// - /// This method is useful when obtaining date/time values from sources - /// that might not correctly set the UTC flag. - /// - /// The date/time - /// The same date/time with the UTC flag set - public static DateTime AssumeUniversalTime(this DateTime dateTime) - { - return new DateTime(dateTime.Ticks, DateTimeKind.Utc); - } - - /// - /// Returns a copy of a nullable date/time value with its kind - /// set to but does not perform - /// any time-zone adjustment. - /// - /// - /// This method is useful when obtaining date/time values from sources - /// that might not correctly set the UTC flag. - /// - /// The nullable date/time - /// The same nullable date/time with the UTC flag set - public static DateTime? AssumeUniversalTime(this DateTime? dateTime) - { - return dateTime.HasValue ? AssumeUniversalTime(dateTime.Value) : (DateTime?)null; - } - /// /// Converts a nullable UTC date/time value to local time. /// @@ -143,260 +97,6 @@ public static DateTime GetEvenMinuteDateBefore(this DateTime? dateTime) return new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, 0); } - /// - /// Returns a date that is rounded to the next even second above the given - /// date. - /// - /// the Date to round, if the current time will - /// be used - /// the new rounded date - public static DateTime GetEvenSecondDate(this DateTime? dateTime) - { - if (!dateTime.HasValue) - { - dateTime = DateTime.UtcNow; - } - DateTime d = dateTime.Value; - d = d.AddSeconds(1); - return new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, d.Second); - } - - /// - /// Returns a date that is rounded to the previous even second below the - /// given date. - ///

- /// For example an input date with a time of 08:13:54.341 would result in a - /// date with the time of 08:13:00.000. - ///

- ///
- /// the Date to round, if the current time will - /// be used - /// the new rounded date - public static DateTime GetEvenSecondDateBefore(this DateTime? dateTime) - { - if (!dateTime.HasValue) - { - dateTime = DateTime.UtcNow; - } - DateTime d = dateTime.Value; - return new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, d.Second); - } - - /// - /// Returns a date that is rounded to the next even multiple of the given - /// minute. - ///

- /// For example an input date with a time of 08:13:54, and an input - /// minute-base of 5 would result in a date with the time of 08:15:00. The - /// same input date with an input minute-base of 10 would result in a date - /// with the time of 08:20:00. But a date with the time 08:53:31 and an - /// input minute-base of 45 would result in 09:00:00, because the even-hour - /// is the next 'base' for 45-minute intervals. - ///

- /// - ///

- /// More examples: - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - ///
Input TimeMinute-BaseResult Time
11:16:412011:20:00
11:36:412011:40:00
11:46:412012:00:00
11:26:413011:30:00
11:36:413012:00:00
11:16:411711:17:00
11:17:411711:34:00
11:52:411712:00:00
11:52:41511:55:00
11:57:41512:00:00
11:17:41012:00:00
11:17:41111:08:00
- ///

- /// - ///
- /// the Date to round, if the current time willbe used - /// the base-minute to set the time on - /// The new rounded date - public static DateTime GetNextGivenMinuteDate(this DateTime? dateTime, int minuteBase) - { - if (minuteBase < 0 || minuteBase > 59) - { - throw new ArgumentException("minuteBase must be >=0 and <= 59"); - } - - if (!dateTime.HasValue) - { - dateTime = DateTime.UtcNow; - } - DateTime d = dateTime.Value; - - if (minuteBase == 0) - { - d = d.AddHours(1); - return new DateTime(d.Year, d.Month, d.Day, d.Hour, 0, 0); - } - - int minute = d.Minute; - int arItr = minute / minuteBase; - int nextMinuteOccurance = minuteBase * (arItr + 1); - - if (nextMinuteOccurance < 60) - { - return new DateTime(d.Year, d.Month, d.Day, d.Hour, nextMinuteOccurance, 0); - } - else - { - d = d.AddHours(1); - return new DateTime(d.Year, d.Month, d.Day, d.Hour, 0, 0); - } - } - - /// - /// Returns a date that is rounded to the next even multiple of the given - /// minute. - ///

- /// The rules for calculating the second are the same as those for - /// calculating the minute in the method - /// . - ///

- ///
- /// The date. - /// The second base. - /// - public static DateTime GetNextGivenSecondDate(this DateTime? dateTime, int secondBase) - { - if (secondBase < 0 || secondBase > 59) - { - throw new ArgumentException("secondBase must be >=0 and <= 59"); - } - - if (!dateTime.HasValue) - { - dateTime = DateTime.UtcNow; - } - - DateTime d = dateTime.Value; - - if (secondBase == 0) - { - d = d.AddMinutes(1); - return new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, 0); - } - - int second = d.Second; - int arItr = second / secondBase; - int nextSecondOccurance = secondBase * (arItr + 1); - - if (nextSecondOccurance < 60) - { - return new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, nextSecondOccurance); - } - else - { - d = d.AddMinutes(1); - return new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, 0); - } - } - - - /// - /// Translate a date and time from a users timezone to the another - /// (probably server) timezone to assist in creating a simple trigger with - /// the right date and time. - /// - /// the date to translate - /// the original time-zone - /// the destination time-zone - /// the translated UTC date - public static DateTime TranslateTime(this DateTime date, TimeZone src, TimeZone dest) - { - DateTime newDate = DateTime.UtcNow; - double offset = (GetOffset(date, dest) - GetOffset(date, src)); - - newDate = newDate.AddMilliseconds(-1 * offset); - - return newDate; - } - - /// - /// Gets the offset from UT for the given date in the given timezone, - /// taking into account daylight savings. - /// - /// the date that is the base for the offset - /// the time-zone to calculate to offset to - /// the offset - public static double GetOffset(this DateTime date, TimeZone tz) - { - if (tz.IsDaylightSavingTime(date)) - { - // TODO - return tz.BaseUtcOffset.TotalMilliseconds + 0; - } - - return tz.BaseUtcOffset.TotalMilliseconds; - } - - /// - /// This functions determines if the TimeZone uses daylight saving time - /// - /// TimeZone instance to validate - /// True or false depending if daylight savings time is used - public static bool UseDaylightTime(this TimeZone timezone) - { - return timezone.SupportsDaylightSavingTime; - } - public static long ToJavaScriptTicks(this DateTime dateTime) { DateTimeOffset utcDateTime = dateTime.ToUniversalTime(); @@ -404,28 +104,6 @@ public static long ToJavaScriptTicks(this DateTime dateTime) return javaScriptTicks; } - //public static long ToJavaScriptTicks(this DateTimeOffset offset) - //{ - // DateTimeOffset utcDateTime = offset.ToUniversalTime(); - // long javaScriptTicks = (utcDateTime.Ticks - InitialJavaScriptDateTicks) / (long)10000; - // return javaScriptTicks; - //} - - public static XmlDateTimeSerializationMode ToSerializationMode(DateTimeKind kind) - { - switch (kind) - { - case DateTimeKind.Local: - return XmlDateTimeSerializationMode.Local; - case DateTimeKind.Unspecified: - return XmlDateTimeSerializationMode.Unspecified; - case DateTimeKind.Utc: - return XmlDateTimeSerializationMode.Utc; - default: - throw new ArgumentOutOfRangeException("kind", kind, "Unexpected DateTimeKind value."); - } - } - /// /// Get the first day of the month for /// any full date submitted diff --git a/src/Libraries/SmartStore.Core/Extensions/DecimalExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/DecimalExtensions.cs index 9d2540f9f5..aa6dd811e3 100644 --- a/src/Libraries/SmartStore.Core/Extensions/DecimalExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/DecimalExtensions.cs @@ -17,7 +17,7 @@ public static string FormatInvariant(this decimal value, int decimals = 2) } /// - /// Calculates the tax (percental) from a gross and a net value. + /// Calculates the tax (percentage) from a gross and a net value. /// /// Gross value /// Net value diff --git a/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs index 6b91605920..6e4c8976e8 100644 --- a/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs @@ -5,14 +5,13 @@ using System.Web.Routing; using System.Globalization; using System.Dynamic; +using SmartStore.Utilities; namespace SmartStore { - public static class DictionaryExtensions { - - public static void AddRange(this IDictionary values, IEnumerable> other) + public static void AddRange(this IDictionary values, IEnumerable> other) { foreach (var kvp in other) { @@ -34,16 +33,16 @@ public static void Merge(this IDictionary instance, string key, public static void Merge(this IDictionary instance, object values, bool replaceExisting = true) { - instance.Merge(new RouteValueDictionary(values), replaceExisting); + instance.Merge(CommonHelper.ObjectToDictionary(values), replaceExisting); } - public static void Merge(this IDictionary instance, IDictionary from, bool replaceExisting = true) + public static void Merge(this IDictionary instance, IDictionary from, bool replaceExisting = true) { - foreach (KeyValuePair keyValuePair in from) + foreach (var kvp in from) { - if (replaceExisting || !instance.ContainsKey(keyValuePair.Key)) + if (replaceExisting || !instance.ContainsKey(kvp.Key)) { - instance[keyValuePair.Key] = keyValuePair.Value; + instance[kvp.Key] = kvp.Value; } } } @@ -58,30 +57,22 @@ public static void PrependInValue(this IDictionary instance, str instance[key] = !instance.ContainsKey(key) ? value.ToString() : (value + separator + instance[key]); } - public static string ToAttributeString(this IDictionary instance) - { - StringBuilder builder = new StringBuilder(); - foreach (KeyValuePair pair in instance) - { - object[] args = new object[] { HttpUtility.HtmlAttributeEncode(pair.Key), HttpUtility.HtmlAttributeEncode(pair.Value.ToString()) }; - builder.Append(" {0}=\"{1}\"".FormatWith(args)); - } - return builder.ToString(); - } - - public static T GetValue(this IDictionary instance, K key) + public static TValue GetValue(this IDictionary instance, TKey key) { try { object val; if (instance != null && instance.TryGetValue(key, out val) && val != null) - return (T)Convert.ChangeType(val, typeof(T), CultureInfo.InvariantCulture); + { + return (TValue)Convert.ChangeType(val, typeof(TValue), CultureInfo.InvariantCulture); + } } - catch (Exception exc) + catch (Exception ex) { - exc.Dump(); + ex.Dump(); } - return default(T); + + return default(TValue); } public static ExpandoObject ToExpandoObject(this IDictionary source, bool castIfPossible = false) diff --git a/src/Libraries/SmartStore.Core/Extensions/EnumExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/EnumExtensions.cs deleted file mode 100644 index 6097509595..0000000000 --- a/src/Libraries/SmartStore.Core/Extensions/EnumExtensions.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; - -namespace SmartStore -{ - - public static class EnumExtensions - { - - /// - /// Gets the of an - /// type value. - /// - /// The type value. - /// A string containing the text of the - /// . - public static string GetFriendlyName(this Enum value) - { - Guard.ArgumentNotNull(value, "value"); - - string friendlyName = value.ToString(); - FieldInfo fieldInfo = value.GetType().GetField(friendlyName); - - var attributes = (EnumFriendlyNameAttribute[])fieldInfo.GetCustomAttributes(typeof(EnumFriendlyNameAttribute), false); - - if (attributes != null && attributes.Length == 1) - { - friendlyName = attributes[0].FriendlyName; - } - return friendlyName; - } - - /// - /// Gets the of an - /// type value. - /// - /// The type value. - /// A string containing the text of the - /// . - public static string GetDescription(this Enum value) - { - Guard.ArgumentNotNull(value, "value"); - - string description = value.ToString(); - FieldInfo fieldInfo = value.GetType().GetField(description); - - EnumDescriptionAttribute[] attributes = - (EnumDescriptionAttribute[]) - fieldInfo.GetCustomAttributes(typeof(EnumDescriptionAttribute), false); - - if (attributes != null && attributes.Length == 1) - { - description = attributes[0].Description; - } - return description; - } - - /// - /// Checks if the specified enum flag is set on a flagged enumeration type. - /// - /// - /// - /// - /// - public static bool IsSet(this T value, T flags) where T : struct - { - Type type = typeof(T); - - // only works with enums - if (!type.IsEnum) - throw Error.Argument("T", "The type parameter T must be an enum type."); - - // handle each underlying type - Type numberType = Enum.GetUnderlyingType(type); - - if (numberType.Equals(typeof(int))) - { - return BoxUnbox(value, flags, (a, b) => (a & b) == b); - } - else if (numberType.Equals(typeof(sbyte))) - { - return BoxUnbox(value, flags, (a, b) => (a & b) == b); - } - else if (numberType.Equals(typeof(byte))) - { - return BoxUnbox(value, flags, (a, b) => (a & b) == b); - } - else if (numberType.Equals(typeof(short))) - { - return BoxUnbox(value, flags, (a, b) => (a & b) == b); - } - else if (numberType.Equals(typeof(ushort))) - { - return BoxUnbox(value, flags, (a, b) => (a & b) == b); - } - else if (numberType.Equals(typeof(uint))) - { - return BoxUnbox(value, flags, (a, b) => (a & b) == b); - } - else if (numberType.Equals(typeof(long))) - { - return BoxUnbox(value, flags, (a, b) => (a & b) == b); - } - else if (numberType.Equals(typeof(ulong))) - { - return BoxUnbox(value, flags, (a, b) => (a & b) == b); - } - else if (numberType.Equals(typeof(char))) - { - return BoxUnbox(value, flags, (a, b) => (a & b) == b); - } - else - { - throw new ArgumentException("Unknown enum underlying type " + - numberType.Name + "."); - } - } - - /// - /// Helper function for handling the value types. Boxes the params to - /// object so that the cast can be called on them. - /// - private static bool BoxUnbox(object value, object flags, Func op) - { - return op((T)value, (T)flags); - } - - } - -} diff --git a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs index cd3fe2ffd1..948cfddc6a 100644 --- a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs @@ -1,33 +1,35 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Collections; -using System.Linq; -using System.Linq.Expressions; using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Web; using SmartStore.Collections; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace SmartStore { - - public static class EnumerableExtensions + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + public static class EnumerableExtensions { - #region Nested classes private static class DefaultReadOnlyCollection { private static ReadOnlyCollection defaultCollection; + [SuppressMessage("ReSharper", "ConvertIfStatementToNullCoalescingExpression")] internal static ReadOnlyCollection Empty { get { - if (EnumerableExtensions.DefaultReadOnlyCollection.defaultCollection == null) + if (defaultCollection == null) { - EnumerableExtensions.DefaultReadOnlyCollection.defaultCollection = new ReadOnlyCollection(new T[0]); + defaultCollection = new ReadOnlyCollection(new T[0]); } - return EnumerableExtensions.DefaultReadOnlyCollection.defaultCollection; + return defaultCollection; } } } @@ -74,15 +76,15 @@ public static IEnumerable> Chunk(this IEnumerable items, in } - /// - /// Performs an action on each item while iterating through a list. - /// This is a handy shortcut for foreach(item in list) { ... } - /// - /// The type of the items. - /// The list, which holds the objects. - /// The action delegate which is called on each item while iterating. - //[DebuggerStepThrough] - public static void Each(this IEnumerable source, Action action) + /// + /// Performs an action on each item while iterating through a list. + /// This is a handy shortcut for foreach(item in list) { ... } + /// + /// The type of the items. + /// The list, which holds the objects. + /// The action delegate which is called on each item while iterating. + [DebuggerStepThrough] + public static void Each(this IEnumerable source, Action action) { foreach (T t in source) { @@ -97,7 +99,7 @@ public static void Each(this IEnumerable source, Action action) /// The type of the items. /// The list, which holds the objects. /// The action delegate which is called on each item while iterating. - //[DebuggerStepThrough] + [DebuggerStepThrough] public static void Each(this IEnumerable source, Action action) { int i = 0; @@ -107,11 +109,6 @@ public static void Each(this IEnumerable source, Action action) } } - public static IEnumerable CastValid(this IEnumerable source) - { - return source.Cast().Where(o => o is T).Cast(); - } - public static ReadOnlyCollection AsReadOnly(this IEnumerable source) { if (source == null || !source.Any()) @@ -132,20 +129,76 @@ public static ReadOnlyCollection AsReadOnly(this IEnumerable source) return new ReadOnlyCollection(source.ToArray()); } - public static IEnumerable OrderByOrdinal(this IEnumerable source) - where T : IOrdered - { - return source.OrderByOrdinal(false); - } + /// + /// Converts an enumerable to a dictionary while tolerating duplicate entries (last wins) + /// + /// source + /// keySelector + /// Result as dictionary + public static Dictionary ToDictionarySafe( + this IEnumerable source, + Func keySelector) + { + return source.ToDictionarySafe(keySelector, new Func(src => src), null); + } - public static IEnumerable OrderByOrdinal(this IEnumerable source, bool descending) - where T : IOrdered - { - if (!descending) - return source.OrderBy(x => x.Ordinal); - else - return source.OrderByDescending(x => x.Ordinal); - } + /// + /// Converts an enumerable to a dictionary while tolerating duplicate entries (last wins) + /// + /// source + /// keySelector + /// comparer + /// Result as dictionary + public static Dictionary ToDictionarySafe( + this IEnumerable source, + Func keySelector, + IEqualityComparer comparer) + { + return source.ToDictionarySafe(keySelector, new Func(src => src), comparer); + } + + /// + /// Converts an enumerable to a dictionary while tolerating duplicate entries (last wins) + /// + /// source + /// keySelector + /// elementSelector + /// Result as dictionary + public static Dictionary ToDictionarySafe( + this IEnumerable source, + Func keySelector, + Func elementSelector) + { + return source.ToDictionarySafe(keySelector, elementSelector, null); + } + + /// + /// Converts an enumerable to a dictionary while tolerating duplicate entries (last wins) + /// + /// source + /// keySelector + /// elementSelector + /// comparer + /// Result as dictionary + public static Dictionary ToDictionarySafe( + this IEnumerable source, + Func keySelector, + Func elementSelector, + IEqualityComparer comparer) + { + Guard.ArgumentNotNull(() => source); + Guard.ArgumentNotNull(() => keySelector); + Guard.ArgumentNotNull(() => elementSelector); + + var dictionary = new Dictionary(comparer); + + foreach (var local in source) + { + dictionary[keySelector(local)] = elementSelector(local); + } + + return dictionary; + } #endregion @@ -177,6 +230,7 @@ public static Multimap ToMultimap( public static void AddRange(this NameValueCollection initial, NameValueCollection other) { Guard.ArgumentNotNull(initial, "initial"); + if (other == null) return; @@ -186,63 +240,45 @@ public static void AddRange(this NameValueCollection initial, NameValueCollectio } } - #endregion - - #region AsSerializable - - /// - /// Convenience API to allow an IEnumerable[T] (such as returned by Linq2Sql, NHibernate, EF etc.) - /// to be serialized by DataContractSerializer. - /// - /// The type of item. - /// The original collection. - /// A serializable enumerable wrapper. - public static IEnumerable AsSerializable(this IEnumerable source) where T : class - { - return new IEnumerableWrapper(source); - } - - /// - /// This wrapper allows IEnumerable to be serialized by DataContractSerializer. - /// It implements the minimal amount of surface needed for serialization. - /// - /// Type of item. - class IEnumerableWrapper : IEnumerable - where T : class - { - IEnumerable _collection; - - // The DataContractSerilizer needs a default constructor to ensure the object can be - // deserialized. We have a dummy one since we don't actually need deserialization. - public IEnumerableWrapper() - { - throw new NotImplementedException(); - } - - internal IEnumerableWrapper(IEnumerable collection) - { - this._collection = collection; - } - - // The DataContractSerilizer needs an Add method to ensure the object can be - // deserialized. We have a dummy one since we don't actually need deserialization. - public void Add(T item) - { - throw new NotImplementedException(); - } + /// + /// Builds an URL query string + /// + /// Name value collection + /// Encoding type. Can be null. + /// Whether to encode keys and values + /// The query string without leading a question mark + public static string BuildQueryString(this NameValueCollection nvc, Encoding encoding, bool encode = true) + { + var sb = new StringBuilder(); - public IEnumerator GetEnumerator() - { - return this._collection.GetEnumerator(); - } + if (nvc != null) + { + foreach (string str in nvc) + { + if (sb.Length > 0) + sb.Append('&'); + + if (!encode) + sb.Append(str); + else if (encoding == null) + sb.Append(HttpUtility.UrlEncode(str)); + else + sb.Append(HttpUtility.UrlEncode(str, encoding)); + + sb.Append('='); + + if (!encode) + sb.Append(nvc[str]); + else if (encoding == null) + sb.Append(HttpUtility.UrlEncode(nvc[str])); + else + sb.Append(HttpUtility.UrlEncode(nvc[str], encoding)); + } + } - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)this._collection).GetEnumerator(); - } - } + return sb.ToString(); + } #endregion } - } diff --git a/src/Libraries/SmartStore.Core/Extensions/HtmlTextWriterExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/HtmlTextWriterExtensions.cs index 26edc0d0e3..acd2f75109 100644 --- a/src/Libraries/SmartStore.Core/Extensions/HtmlTextWriterExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/HtmlTextWriterExtensions.cs @@ -4,14 +4,12 @@ using System.Web.UI; namespace SmartStore -{ - +{ public static class HtmlTextWriterExtensions { - public static void AddAttributes(this HtmlTextWriter writer, IDictionary attributes) { - if (attributes.Any>()) + if (attributes.Any()) { foreach (var pair in attributes) { @@ -20,8 +18,5 @@ public static void AddAttributes(this HtmlTextWriter writer, IDictionary webRequest); + Guard.ArgumentNotNull(() => httpRequest); + + var authCookie = httpRequest.Cookies[FormsAuthentication.FormsCookieName]; + if (authCookie == null) + return; + + var sendCookie = new Cookie(authCookie.Name, authCookie.Value, authCookie.Path, httpRequest.Url.Host); + + if (webRequest.CookieContainer == null) + { + webRequest.CookieContainer = new CookieContainer(); + } + + webRequest.CookieContainer.Add(sendCookie); + } } } diff --git a/src/Libraries/SmartStore.Core/Extensions/JsonExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/JsonExtensions.cs deleted file mode 100644 index 4c192adc74..0000000000 --- a/src/Libraries/SmartStore.Core/Extensions/JsonExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Net; -using System.Threading.Tasks; -using Newtonsoft.Json; - - -/////////////////////////////////////////////////////////////////////// -// Needs JSon.Net (Newtonsoft.Json.dll) from http://json.codeplex.com/ -////////////////////////////////////////////////////////////////////// - -namespace SmartStore -{ - public static class JsonExtensions - { - public static async Task GetDynamicJsonObject(this Uri uri) - { - using (WebClient wc = new WebClient()) - { - wc.Encoding = System.Text.Encoding.UTF8; - wc.Headers["User-Agent"] = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET4.0C; .NET4.0E)"; - var response = await wc.DownloadStringTaskAsync(uri); - return JsonConvert.DeserializeObject(response); - } - } - } -} - - - diff --git a/src/Libraries/SmartStore.Core/Extensions/LinqExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/LinqExtensions.cs index 06a3b2dc6d..c0731e7538 100644 --- a/src/Libraries/SmartStore.Core/Extensions/LinqExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/LinqExtensions.cs @@ -1,15 +1,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace SmartStore { - public static class LinqExtensions { - public static PropertyInfo ExtractPropertyInfo(this LambdaExpression propertyAccessor) { return propertyAccessor.ExtractMemberInfo() as PropertyInfo; @@ -20,7 +19,8 @@ public static FieldInfo ExtractFieldInfo(this LambdaExpression propertyAccessor) return propertyAccessor.ExtractMemberInfo() as FieldInfo; } - public static MemberInfo ExtractMemberInfo(this LambdaExpression propertyAccessor) + [SuppressMessage("ReSharper", "CanBeReplacedWithTryCastAndCheckForNull")] + public static MemberInfo ExtractMemberInfo(this LambdaExpression propertyAccessor) { Guard.ArgumentNotNull(() => propertyAccessor); diff --git a/src/Libraries/SmartStore.Core/Extensions/ListExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/ListExtensions.cs deleted file mode 100644 index f966ee5456..0000000000 --- a/src/Libraries/SmartStore.Core/Extensions/ListExtensions.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace SmartStore -{ - - public static class ListExtensions - { - - public static string ToSeparatedString(this IList value) - { - return ToSeparatedString(value, ","); - } - - public static string ToSeparatedString(this IList value, string separator) - { - if (value.Count == 0) - { - return String.Empty; - } - if (value.Count == 1) - { - if (value[0] != null) - { - return value[0].ToString(); - } - return string.Empty; - } - - StringBuilder builder = new StringBuilder(); - bool flag = true; - bool flag2 = false; - foreach (object obj2 in value) - { - if (!flag) - { - builder.Append(separator); - } - if (obj2 != null) - { - builder.Append(obj2.ToString().TrimEnd(new char[0])); - flag2 = true; - } - flag = false; - } - if (!flag2) - { - return string.Empty; - } - return builder.ToString(); - } - - /// - /// Makes a slice of the specified list in between the start and end indexes. - /// - /// The list. - /// The start index. - /// The end index. - /// A slice of the list. - public static IList Slice(this IList list, int? start, int? end) - { - return list.Slice(start, end, null); - } - - /// - /// Makes a slice of the specified list in between the start and end indexes, - /// getting every so many items based upon the step. - /// - /// The list. - /// The start index. - /// The end index. - /// The step. - /// A slice of the list. - public static IList Slice(this IList list, int? start, int? end, int? step) - { - if (list == null) - throw new ArgumentNullException("list"); - - if (step == 0) - throw Error.Argument("step", "Step cannot be zero."); - - List slicedList = new List(); - - // nothing to slice - if (list.Count == 0) - return slicedList; - - // set defaults for null arguments - int s = step ?? 1; - int startIndex = start ?? 0; - int endIndex = end ?? list.Count; - - // start from the end of the list if start is negative - startIndex = (startIndex < 0) ? list.Count + startIndex : startIndex; - - // end from the start of the list if end is negative - endIndex = (endIndex < 0) ? list.Count + endIndex : endIndex; - - // ensure indexes keep within collection bounds - startIndex = Math.Max(startIndex, 0); - endIndex = Math.Min(endIndex, list.Count - 1); - - // loop between start and end indexes, incrementing by the step - for (int i = startIndex; i < endIndex; i += s) - { - slicedList.Add(list[i]); - } - - return slicedList; - } - - } - -} diff --git a/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs index 56cd460b3c..033c5d4230 100644 --- a/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs @@ -11,23 +11,27 @@ namespace SmartStore { public static class MiscExtensions { - public static void Dump(this Exception exc) + public static void Dump(this Exception exception) { try { - exc.StackTrace.Dump(); - exc.Message.Dump(); + exception.StackTrace.Dump(); + exception.Message.Dump(); } catch { } } - public static string ToAllMessages(this Exception exc) + + public static string ToAllMessages(this Exception exception) { var sb = new StringBuilder(); - - while (exc != null) + + while (exception != null) { - sb.Grow(exc.Message, " "); - exc = exc.InnerException; + if (!sb.ToString().EmptyNull().Contains(exception.Message)) + { + sb.Grow(exception.Message, " * "); + } + exception = exception.InnerException; } return sb.ToString(); } @@ -36,55 +40,12 @@ public static string ToElapsedMinutes(this Stopwatch watch) { return "{0:0.0}".FormatWith(TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).TotalMinutes); } + public static string ToElapsedSeconds(this Stopwatch watch) { return "{0:0.0}".FormatWith(TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).TotalSeconds); } - public static bool HasColumn(this DataView dv, string columnName) - { - dv.RowFilter = "ColumnName='" + columnName + "'"; - return dv.Count > 0; - } - public static string GetDataType(this DataTable dt, string columnName) - { - dt.DefaultView.RowFilter = "ColumnName='" + columnName + "'"; - return dt.Rows[0]["DataType"].ToString(); - } - - public static int CountExecute(this OleDbConnection conn, string sqlCount) - { - using (OleDbCommand cmd = new OleDbCommand(sqlCount, conn)) - { - return (int)cmd.ExecuteScalar(); - } - } - - public static object SafeConvert(this TypeConverter converter, string value) - { - try - { - if (converter != null && value.HasValue() && converter.CanConvertFrom(typeof(string))) - { - return converter.ConvertFromString(value); - } - } - catch (Exception exc) - { - exc.Dump(); - } - return null; - } - public static bool IsEqual(this TypeConverter converter, string value, object compareWith) - { - object convertedObject = converter.SafeConvert(value); - - if (convertedObject != null && compareWith != null) - return convertedObject.Equals(compareWith); - - return false; - } - public static bool IsNullOrDefault(this T? value) where T : struct { return default(T).Equals(value.GetValueOrDefault()); @@ -110,29 +71,17 @@ public static string ToHexString(this byte[] bytes, int length = 0) public static T GetMergedDataValue(this IMergedData mergedData, string key, T defaultValue) { - try + if (mergedData.MergedDataValues != null && !mergedData.MergedDataIgnore) { - if (mergedData.MergedDataValues != null && !mergedData.MergedDataIgnore) - { - object value; + object value; - if (mergedData.MergedDataValues.TryGetValue(key, out value)) - return (T)value; - } + if (mergedData.MergedDataValues.TryGetValue(key, out value)) + return (T)value; } - catch (Exception) { } return defaultValue; } - public static bool IsRouteEqual(this RouteData routeData, string controller, string action) - { - if (routeData == null) - return false; - - return routeData.GetRequiredString("controller").IsCaseInsensitiveEqual(controller) && routeData.GetRequiredString("action").IsCaseInsensitiveEqual(action); - } - /// /// Append grow if string builder is empty. Append delimiter and grow otherwise. /// diff --git a/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs index 31bddf144d..f958bdaf73 100644 --- a/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs @@ -10,26 +10,35 @@ namespace SmartStore { public static class StreamExtensions { - - public static bool ToFile(this Stream srcStream, string path) + public static StreamReader ToStreamReader(this Stream stream, bool leaveOpen) + { + return new StreamReader(stream, Encoding.UTF8, true, 0x400, leaveOpen); + } + + public static StreamReader ToStreamReader(this Stream stream, Encoding encoding, bool detectEncoding, int bufferSize, bool leaveOpen) + { + return new StreamReader(stream, encoding, detectEncoding, bufferSize, leaveOpen); + } + + public static bool ToFile(this Stream srcStream, string path) { if (srcStream == null) return false; const int BuffSize = 32768; bool result = true; - int len = 0; Stream dstStream = null; byte[] buffer = new Byte[BuffSize]; try { - using (dstStream = File.OpenWrite(path)) - { + using (dstStream = File.OpenWrite(path)) + { + int len; while ((len = srcStream.Read(buffer, 0, BuffSize)) > 0) dstStream.Write(buffer, 0, len); } - } + } catch { result = false; diff --git a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs index 448f5ffe96..9131705215 100644 --- a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs @@ -7,11 +7,11 @@ using System.Text.RegularExpressions; using System.IO; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace SmartStore { - public static class StringExtensions { public const string CarriageReturnLineFeed = "\r\n"; @@ -112,37 +112,37 @@ public static string NullEmpty(this string value) return (string.IsNullOrEmpty(value)) ? null : value; } - /// - /// Formats a string to an invariant culture - /// - /// The format string. - /// The objects. - /// - [DebuggerStepThrough] + /// + /// Formats a string to an invariant culture + /// + /// The format string. + /// The objects. + /// + [DebuggerStepThrough] public static string FormatInvariant(this string format, params object[] objects) { return string.Format(CultureInfo.InvariantCulture, format, objects); } - /// - /// Formats a string to the current culture. - /// - /// The format string. - /// The objects. - /// - [DebuggerStepThrough] + /// + /// Formats a string to the current culture. + /// + /// The format string. + /// The objects. + /// + [DebuggerStepThrough] public static string FormatCurrent(this string format, params object[] objects) { return string.Format(CultureInfo.CurrentCulture, format, objects); } - /// - /// Formats a string to the current UI culture. - /// - /// The format string. - /// The objects. - /// - [DebuggerStepThrough] + /// + /// Formats a string to the current UI culture. + /// + /// The format string. + /// The objects. + /// + [DebuggerStepThrough] public static string FormatCurrentUI(this string format, params object[] objects) { return string.Format(CultureInfo.CurrentUICulture, format, objects); @@ -160,29 +160,29 @@ public static string FormatWith(this string format, IFormatProvider provider, pa return string.Format(provider, format, args); } - /// - /// Determines whether this instance and another specified System.String object have the same value. - /// - /// The string to check equality. - /// The comparing with string. - /// - /// true if the value of the comparing parameter is the same as this string; otherwise, false. - /// - [DebuggerStepThrough] + /// + /// Determines whether this instance and another specified System.String object have the same value. + /// + /// The string to check equality. + /// The comparing with string. + /// + /// true if the value of the comparing parameter is the same as this string; otherwise, false. + /// + [DebuggerStepThrough] public static bool IsCaseSensitiveEqual(this string value, string comparing) { return string.CompareOrdinal(value, comparing) == 0; } - /// - /// Determines whether this instance and another specified System.String object have the same value. - /// - /// The string to check equality. - /// The comparing with string. - /// - /// true if the value of the comparing parameter is the same as this string; otherwise, false. - /// - [DebuggerStepThrough] + /// + /// Determines whether this instance and another specified System.String object have the same value. + /// + /// The string to check equality. + /// The comparing with string. + /// + /// true if the value of the comparing parameter is the same as this string; otherwise, false. + /// + [DebuggerStepThrough] public static bool IsCaseInsensitiveEqual(this string value, string comparing) { return string.Compare(value, comparing, StringComparison.OrdinalIgnoreCase) == 0; @@ -197,14 +197,14 @@ public static bool IsEmpty(this string value) return string.IsNullOrWhiteSpace(value); } - /// - /// Determines whether the string is all white space. Empty string will return false. - /// - /// The string to test whether it is all white space. - /// - /// true if the string is all white space; otherwise, false. - /// - [DebuggerStepThrough] + /// + /// Determines whether the string is all white space. Empty string will return false. + /// + /// The string to test whether it is all white space. + /// + /// true if the string is all white space; otherwise, false. + /// + [DebuggerStepThrough] public static bool IsWhiteSpace(this string value) { Guard.ArgumentNotNull(value, "value"); @@ -340,26 +340,6 @@ public static string Truncate(this string value, int maxLength, string suffix = } } - /// - /// Determines whether the string contains white space. - /// - /// The string to test for white space. - /// - /// true if the string contains white space; otherwise, false. - /// - [DebuggerStepThrough] - public static bool ContainsWhiteSpace(this string value) - { - Guard.ArgumentNotNull(value, "value"); - - for (int i = 0; i < value.Length; i++) - { - if (char.IsWhiteSpace(value[i])) - return true; - } - return false; - } - /// /// Ensure that a string starts with a string. /// @@ -375,13 +355,13 @@ public static string EnsureStartsWith(this string value, string startsWith) return value.StartsWith(startsWith) ? value : (startsWith + value); } - /// - /// Ensures the target string ends with the specified string. - /// - /// The target. - /// The value. - /// The target string with the value string at the end. - [DebuggerStepThrough] + /// + /// Ensures the target string ends with the specified string. + /// + /// The target. + /// The value. + /// The target string with the value string at the end. + [DebuggerStepThrough] public static string EnsureEndsWith(this string value, string endWith) { Guard.ArgumentNotNull(value, "value"); @@ -401,15 +381,6 @@ public static string EnsureEndsWith(this string value, string endWith) return value + endWith; } - [DebuggerStepThrough] - public static int? GetLength(this string value) - { - if (value == null) - return null; - else - return value.Length; - } - [DebuggerStepThrough] public static string UrlEncode(this string value) { @@ -477,13 +448,13 @@ private static string RemoveHtmlInternal(string s, ICollection removeTag }); } - /// - /// Replaces pascal casing with spaces. For example "CustomerId" would become "Customer Id". - /// Strings that already contain spaces are ignored. - /// - /// String to split - /// The string after being split - [DebuggerStepThrough] + /// + /// Replaces pascal casing with spaces. For example "CustomerId" would become "Customer Id". + /// Strings that already contain spaces are ignored. + /// + /// String to split + /// The string after being split + [DebuggerStepThrough] public static string SplitPascalCase(this string value) { //return Regex.Replace(input, "([A-Z][a-z])", " $1", RegexOptions.Compiled).Trim(); @@ -518,111 +489,23 @@ public static string[] SplitSafe(this string value, string separator) /// Splits a string into two strings /// true: success, false: failure [DebuggerStepThrough] - public static bool SplitToPair(this string value, out string strLeft, out string strRight, string delimiter) { + [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] + public static bool SplitToPair(this string value, out string strLeft, out string strRight, string delimiter) + { int idx = -1; - if (value.IsEmpty() || delimiter.IsEmpty() || (idx = value.IndexOf(delimiter)) == -1) + if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(delimiter) || (idx = value.IndexOf(delimiter)) == -1) { strLeft = value; strRight = ""; return false; } + strLeft = value.Substring(0, idx); strRight = value.Substring(idx + delimiter.Length); + return true; } - [DebuggerStepThrough] - public static string ToCamelCase(this string instance) - { - char ch = instance[0]; - return (ch.ToString().ToLowerInvariant() + instance.Substring(1)); - } - - [DebuggerStepThrough] - public static string ReplaceNewLines(this string value, string replacement) - { - StringReader sr = new StringReader(value); - StringBuilder sb = new StringBuilder(); - - bool first = true; - - string line; - while ((line = sr.ReadLine()) != null) - { - if (first) - first = false; - else - sb.Append(replacement); - - sb.Append(line); - } - - return sb.ToString(); - } - - /// - /// Indents the specified string. - /// - /// The string to indent. - /// The number of characters to indent by. - /// - [DebuggerStepThrough] - public static string Indent(this string value, int indentation) - { - return Indent(value, indentation, ' '); - } - - /// - /// Indents the specified string. - /// - /// The string to indent. - /// The number of characters to indent by. - /// The indent character. - /// - [DebuggerStepThrough] - public static string Indent(this string value, int indentation, char indentChar) - { - Guard.ArgumentNotNull(value, "value"); - Guard.ArgumentIsPositive(indentation, "indentation"); - - StringReader sr = new StringReader(value); - StringWriter sw = new StringWriter(CultureInfo.InvariantCulture); - - ActionTextReaderLine(sr, sw, delegate(TextWriter tw, string line) - { - tw.Write(new string(indentChar, indentation)); - tw.Write(line); - }); - - return sw.ToString(); - } - - /// - /// Numbers the lines. - /// - /// The string to number. - /// - public static string NumberLines(this string value) - { - Guard.ArgumentNotNull(value, "value"); - - StringReader sr = new StringReader(value); - StringWriter sw = new StringWriter(CultureInfo.InvariantCulture); - - int lineNumber = 1; - - ActionTextReaderLine(sr, sw, delegate(TextWriter tw, string line) - { - tw.Write(lineNumber.ToString(CultureInfo.InvariantCulture).PadLeft(4)); - tw.Write(". "); - tw.Write(line); - - lineNumber++; - }); - - return sw.ToString(); - } - [DebuggerStepThrough] public static string EncodeJsString(this string value) { @@ -632,7 +515,7 @@ public static string EncodeJsString(this string value) [DebuggerStepThrough] public static string EncodeJsString(this string value, char delimiter, bool appendDelimiters) { - StringBuilder sb = new StringBuilder(value.GetLength() ?? 16); + StringBuilder sb = new StringBuilder(value != null ? value.Length : 16); using (StringWriter w = new StringWriter(sb, CultureInfo.InvariantCulture)) { EncodeJsString(w, value, delimiter, appendDelimiters); @@ -720,9 +603,12 @@ public static void Dump(this string value, bool appendMarks = false) Debug.WriteLine(value); Debug.WriteLineIf(appendMarks, "------------------------------------------------"); } - - /// Smart way to create a HTML attribute with a leading space. - /// Name of the attribute. + + /// Smart way to create a HTML attribute with a leading space. + /// Name of the attribute. + /// + /// + [SuppressMessage("ReSharper", "StringCompareIsCultureSpecific.3")] public static string ToAttribute(this string value, string name, bool htmlEncode = true) { if (name.IsEmpty()) @@ -778,7 +664,31 @@ public static string Replace(this string value, int x1, int x2, string replaceBy return value; } - [DebuggerStepThrough] + [DebuggerStepThrough] + public static string Replace(this string value, string oldValue, string newValue, StringComparison comparisonType) + { + try + { + int startIndex = 0; + while (true) + { + startIndex = value.IndexOf(oldValue, startIndex, comparisonType); + if (startIndex == -1) + break; + + value = value.Substring(0, startIndex) + newValue + value.Substring(startIndex + oldValue.Length); + + startIndex += newValue.Length; + } + } + catch (Exception exc) + { + exc.Dump(); + } + return value; + } + + [DebuggerStepThrough] public static string TrimSafe(this string value) { return (value.HasValue() ? value.Trim() : value); @@ -868,46 +778,7 @@ public static string Prettify(this string value, bool allowSpace = false, char[] public static string SanitizeHtmlId(this string value) { - if (string.IsNullOrWhiteSpace(value)) - { - return null; - } - StringBuilder builder = new StringBuilder(value.Length); - int index = value.IndexOf("#"); - int num2 = value.LastIndexOf("#"); - if (num2 > index) - { - ReplaceInvalidHtmlIdCharacters(value.Substring(0, index), builder); - builder.Append(value.Substring(index, (num2 - index) + 1)); - ReplaceInvalidHtmlIdCharacters(value.Substring(num2 + 1), builder); - } - else - { - ReplaceInvalidHtmlIdCharacters(value, builder); - } - return builder.ToString(); - } - - private static bool IsValidHtmlIdCharacter(char c) - { - bool invalid = (c == '?' || c == '!' || c == '#' || c == '.' || c == ' ' || c == ';' || c == ':'); - return !invalid; - } - - private static void ReplaceInvalidHtmlIdCharacters(string part, StringBuilder builder) - { - for (int i = 0; i < part.Length; i++) - { - char c = part[i]; - if (IsValidHtmlIdCharacter(c)) - { - builder.Append(c); - } - else - { - builder.Append('_'); - } - } + return System.Web.Mvc.TagBuilder.CreateSanitizedId(value); } public static string Sha(this string value, Encoding encoding) @@ -919,7 +790,6 @@ public static string Sha(this string value, Encoding encoding) byte[] data = encoding.GetBytes(value); return sha1.ComputeHash(data).ToHexString(); - //return BitConverter.ToString(sha1.ComputeHash(data)).Replace("-", ""); } } return ""; @@ -1003,14 +873,15 @@ public static string RemoveInvalidXmlChars(this string s) [DebuggerStepThrough] public static string ReplaceCsvChars(this string s) { - if (s.HasValue()) + if (s.IsEmpty()) { - s = s.Replace(';', ','); - s = s.Replace('\r', ' '); - s = s.Replace('\n', ' '); - return s.Replace("'", ""); + return ""; } - return ""; + + s = s.Replace(';', ','); + s = s.Replace('\r', ' '); + s = s.Replace('\n', ' '); + return s.Replace("'", ""); } #endregion @@ -1080,22 +951,6 @@ private static void EncodeJsString(TextWriter writer, string value, char delimit writer.Write(delimiter); } - - private static void ActionTextReaderLine(TextReader textReader, TextWriter textWriter, ActionLine lineAction) - { - string line; - bool firstLine = true; - while ((line = textReader.ReadLine()) != null) - { - if (!firstLine) - textWriter.WriteLine(); - else - firstLine = false; - - lineAction(textWriter, line); - } - } - #endregion } diff --git a/src/Libraries/SmartStore.Core/Extensions/TypeDescriptorExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/TypeDescriptorExtensions.cs deleted file mode 100644 index f9467d851a..0000000000 --- a/src/Libraries/SmartStore.Core/Extensions/TypeDescriptorExtensions.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.ComponentModel; - -using SmartStore.Linq; - -namespace SmartStore -{ - - public static class TypeDescriptorExtensions - { - - public static IEnumerable GetAttributes(this ICustomTypeDescriptor td) where TAttribute : Attribute - { - var attributes = td.GetAttributes().OfType(); - return TypeExtensions.SortAttributesIfPossible(attributes); - } - - public static IEnumerable GetAttributes(this PropertyDescriptor pd) where TAttribute : Attribute - { - var attributes = pd.Attributes.OfType(); - return TypeExtensions.SortAttributesIfPossible(attributes); - } - - public static IEnumerable GetAttributes(this PropertyDescriptor pd, - Func predicate) - where TAttribute : Attribute - { - Guard.ArgumentNotNull(predicate, "predicate"); - - var attributes = pd.Attributes.OfType().Where(predicate); - return TypeExtensions.SortAttributesIfPossible(attributes); - } - - public static PropertyDescriptor GetProperty(this ICustomTypeDescriptor td, string name) - { - Guard.ArgumentNotEmpty(name, "name"); - return td.GetProperties().Find(name, true); - //.Cast() - //.FirstOrDefault(p => p.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); - } - - public static IEnumerable GetPropertiesWith(this ICustomTypeDescriptor td) - where TAttribute : Attribute - { - return td.GetPropertiesWith(x => true); - } - - public static IEnumerable GetPropertiesWith( - this ICustomTypeDescriptor td, - Func predicate) - where TAttribute : Attribute - { - Guard.ArgumentNotNull(predicate, "predicate"); - - return td.GetProperties() - .Cast() - .Where(p => p.GetAttributes().Any(predicate)); - - //Expression> expression = (TAttribute a) => typeof(TAttribute).IsAssignableFrom(a.GetType()); - - //return td.GetProperties() - // .Cast() - // .Where(p => p.Attributes.Cast() - // .Any(expression.And(predicate).Compile())); - - //var query = from p in this.EntityTypeDescriptor.GetProperties().Cast() - // let attributes = p.Attributes.Cast() - // where attributes.Any(a => typeof(TAttribute).IsAssignableFrom(a.GetType())) - // //where attributes.Any(a => typeof(TAttribute).IsAssignableFrom(a.GetType())) - // select p; - - //return query - // .Select(p => new EntityProperty(p, this)) - // .AsReadOnly(); - } - - } - -} diff --git a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs index 1f651a8ee1..baf9195b18 100644 --- a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs @@ -4,26 +4,26 @@ using System.Reflection; using System.Collections; using System.Diagnostics; -using Fasterflect; +using System.Diagnostics.CodeAnalysis; namespace SmartStore { - public static class TypeExtensions { - private static Type[] s_predefinedTypes; - private static Type[] s_predefinedGenericTypes; - - static TypeExtensions() - { - s_predefinedTypes = new Type[] { typeof(string), typeof(decimal), typeof(DateTime), typeof(TimeSpan), typeof(Guid) }; - s_predefinedGenericTypes = new Type[] { typeof(Nullable<>) }; - } + private static readonly Type[] s_predefinedTypes = new Type[] { typeof(string), typeof(decimal), typeof(DateTime), typeof(TimeSpan), typeof(Guid) }; + private static readonly Type[] s_predefinedGenericTypes = new Type[] { typeof(Nullable<>) }; public static string AssemblyQualifiedNameWithoutVersion(this Type type) { - string[] strArray = type.AssemblyQualifiedName.Split(new char[] { ',' }); - return string.Format("{0},{1}", strArray[0], strArray[1]); + Guard.ArgumentNotNull(() => type); + + if (type.AssemblyQualifiedName != null) + { + var strArray = type.AssemblyQualifiedName.Split(new char[] { ',' }); + return string.Format("{0}, {1}", strArray[0], strArray[1]); + } + + return null; } public static bool IsSequenceType(this Type seqType) @@ -45,15 +45,8 @@ public static bool IsPredefinedSimpleType(this Type type) { return true; } + return s_predefinedTypes.Any(t => t == type); - //foreach (Type type2 in s_predefinedTypes) - //{ - // if (type2 == type) - // { - // return true; - // } - //} - //return false; } public static bool IsStruct(this Type type) @@ -75,15 +68,8 @@ public static bool IsPredefinedGenericType(this Type type) { return false; } + return s_predefinedGenericTypes.Any(t => t == type); - //foreach (Type type2 in s_predefinedGenericTypes) - //{ - // if (type2 == type) - // { - // return true; - // } - //} - //return false; } public static bool IsPredefinedType(this Type type) @@ -97,7 +83,6 @@ public static bool IsPredefinedType(this Type type) public static bool IsInteger(this Type type) { - switch (Type.GetTypeCode(type)) { case TypeCode.SByte: @@ -119,11 +104,6 @@ public static bool IsNullable(this Type type) return type != null && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } - public static bool IsNullAssignable(this Type type) - { - return !type.IsValueType || type.IsNullable(); - } - public static bool IsConstructable(this Type type) { Guard.ArgumentNotNull(type, "type"); @@ -224,246 +204,6 @@ private static bool IsSubClassInternal(Type initialType, Type currentType, Type return IsSubClassInternal(initialType, currentType.BaseType, check, out implementingType); } - public static bool IsIndexed(this PropertyInfo property) - { - Guard.ArgumentNotNull(property, "property"); - return !property.GetIndexParameters().IsNullOrEmpty(); - } - - /// - /// Determines whether the member is an indexed property. - /// - /// The member. - /// - /// true if the member is an indexed property; otherwise, false. - /// - public static bool IsIndexed(this MemberInfo member) - { - Guard.ArgumentNotNull(member, "member"); - - PropertyInfo propertyInfo = member as PropertyInfo; - - if (propertyInfo != null) - return propertyInfo.IsIndexed(); - else - return false; - } - - /// - /// Checks to see if the specified type is assignable. - /// - /// - /// - public static bool IsType(this Type type) - { - return typeof(TType).IsAssignableFrom(type); - } - - - - public static MemberInfo GetSingleMember(this Type type, string name, MemberTypes memberTypes) - { - return type.GetSingleMember( - name, - memberTypes, - BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); - } - - public static MemberInfo GetSingleMember(this Type type, string name, MemberTypes memberTypes, BindingFlags bindingAttr) - { - return type.GetMember( - name, - memberTypes, - bindingAttr).SingleOrDefault(); - } - - public static string GetNameAndAssemblyName(this Type type) - { - Guard.ArgumentNotNull(type, "type"); - return type.FullName + ", " + type.Assembly.GetName().Name; - } - - public static IEnumerable GetFieldsAndProperties(this Type type, BindingFlags bindingAttr) - { - foreach (var fi in type.GetFields(bindingAttr)) - { - yield return fi; - } - - foreach (var pi in type.GetProperties(bindingAttr)) - { - yield return pi; - } - } - - public static MemberInfo GetFieldOrProperty(this Type type, string name, bool ignoreCase) - { - BindingFlags flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; - if (ignoreCase) - flags |= BindingFlags.IgnoreCase; - - return type.GetSingleMember( - name, - MemberTypes.Field | MemberTypes.Property, - flags); - } - - public static List FindMembers(this Type targetType, MemberTypes memberType, BindingFlags bindingAttr, MemberFilter filter, object filterCriteria) - { - Guard.ArgumentNotNull(targetType, "targetType"); - - List memberInfos = new List(targetType.FindMembers(memberType, bindingAttr, filter, filterCriteria)); - - // fix weirdness with FieldInfos only being returned for the current Type - // find base type fields and add them to result - if ((memberType & MemberTypes.Field) != 0 - && (bindingAttr & BindingFlags.NonPublic) != 0) - { - // modify flags to not search for public fields - BindingFlags nonPublicBindingAttr = bindingAttr ^ BindingFlags.Public; - - while ((targetType = targetType.BaseType) != null) - { - memberInfos.AddRange(targetType.FindMembers(MemberTypes.Field, nonPublicBindingAttr, filter, filterCriteria)); - } - } - - return memberInfos; - } - - //public static Type MakeGenericType(this Type genericTypeDefinition, params Type[] innerTypes) - //{ - // Guard.ArgumentNotNull(genericTypeDefinition, "genericTypeDefinition"); - // Guard.ArgumentNotEmpty(innerTypes, "innerTypes"); - // Guard.Argument.IsTrue(genericTypeDefinition.IsGenericTypeDefinition, "genericTypeDefinition", "Type '{0}' must be a generic type definition.".FormatInvariant(genericTypeDefinition)); - - // return genericTypeDefinition.MakeGenericType(innerTypes); - //} - - public static object CreateGeneric(this Type genericTypeDefinition, Type innerType, params object[] args) - { - return CreateGeneric(genericTypeDefinition, new Type[] { innerType }, args); - } - - public static object CreateGeneric(this Type genericTypeDefinition, Type[] innerTypes, params object[] args) - { - return CreateGeneric(genericTypeDefinition, innerTypes, (t, a) => Activator.CreateInstance(t, args)); - } - - public static object CreateGeneric(this Type genericTypeDefinition, Type[] innerTypes, Func instanceCreator, params object[] args) - { - Guard.ArgumentNotNull(() => genericTypeDefinition); - Guard.ArgumentNotNull(() => innerTypes); - Guard.ArgumentNotNull(() => instanceCreator); - if (innerTypes.Length == 0) - throw Error.Argument("innerTypes", "The sequence must contain at least one entry."); - - Type specificType = genericTypeDefinition.MakeGenericType(innerTypes); - - return instanceCreator(specificType, args); - } - - public static IList CreateGenericList(this Type listType) - { - Guard.ArgumentNotNull(listType, "listType"); - return (IList)typeof(List<>).CreateGeneric(listType); - } - - //public static Type RemoveNullable(this Type type) - //{ - // if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>))) - // { - // return type.GetGenericArguments()[0]; - // } - // return type; - //} - - public static bool IsEnumerable(this Type type) - { - Guard.ArgumentNotNull(type, "type"); - return type.IsAssignableFrom(typeof(IEnumerable)); - } - - public static bool IsGenericDictionary(this Type type) - { - if (type.IsInterface && type.IsGenericType) - { - return typeof(IDictionary<,>).Equals(type.GetGenericTypeDefinition()); - } - return (type.GetInterface(typeof(IDictionary<,>).Name) != null); - } - - //public static bool IsListType(this Type type) - //{ - // Guard.ArgumentNotNull(type, "type"); - - // if (type.IsArray) - // return true; - // else if (typeof(IList).IsAssignableFrom(type)) - // return true; - // else if (type.IsSubClass(typeof(IList<>))) - // return true; - // else - // return false; - //} - - /// - /// Gets the member's value on the object. - /// - /// The member. - /// The target object. - /// The member's value on the object. - public static object GetValue(this MemberInfo member, object target) - { - Guard.ArgumentNotNull(member, "member"); - Guard.ArgumentNotNull(target, "target"); - - var type = target.GetType(); - - switch (member.MemberType) - { - case MemberTypes.Field: - return target.GetFieldValue(member.Name); - //return ((FieldInfo)member).GetValue(target); - case MemberTypes.Property: - return target.GetPropertyValue(member.Name); - default: - throw new ArgumentException("MemberInfo '{0}' is not of type FieldInfo or PropertyInfo".FormatInvariant(member.Name), "member"); - } - } - - /// - /// Sets the member's value on the target object. - /// - /// The member. - /// The target. - /// The value. - public static void SetValue(this MemberInfo member, object target, object value) - { - Guard.ArgumentNotNull(member, "member"); - Guard.ArgumentNotNull(target, "target"); - - switch (member.MemberType) - { - case MemberTypes.Field: - target.SetFieldValue(member.Name, value); - break; - //return ((FieldInfo)member).GetValue(target); - case MemberTypes.Property: - try - { - target.SetPropertyValue(member.Name, value); - } - catch (TargetParameterCountException e) - { - throw new ArgumentException("PropertyInfo '{0}' has index parameters".FormatInvariant(member.Name), "member", e); - } - break; - default: - throw new ArgumentException("MemberInfo '{0}' is not of type FieldInfo or PropertyInfo".FormatInvariant(member.Name), "member"); - } - } - /// /// Gets the underlying type of a type. /// @@ -476,62 +216,15 @@ public static Type GetNonNullableType(this Type type) return type.GetGenericArguments()[0]; } - /// - /// Determines whether the specified MemberInfo can be read. - /// - /// The MemberInfo to determine whether can be read. - /// - /// true if the specified MemberInfo can be read; otherwise, false. - /// - /// - /// For methods this will return true if the return type - /// is not void and the method is parameterless. - /// - public static bool CanReadValue(this MemberInfo member) - { - switch (member.MemberType) - { - case MemberTypes.Field: - return true; - case MemberTypes.Property: - return ((PropertyInfo)member).CanRead; - case MemberTypes.Method: - MethodInfo mi = (MethodInfo)member; - return mi.ReturnType != typeof(void) && mi.GetParameters().Length == 0; - default: - return false; - } - } - - /// - /// Determines whether the specified MemberInfo can be set. - /// - /// The MemberInfo to determine whether can be set. - /// - /// true if the specified MemberInfo can be set; otherwise, false. - /// - public static bool CanSetValue(this MemberInfo member) - { - switch (member.MemberType) - { - case MemberTypes.Field: - return true; - case MemberTypes.Property: - return ((PropertyInfo)member).CanWrite; - default: - return false; - } - } - - /// - /// Returns single attribute from the type - /// - /// Attribute to use - /// Attribute provider - /// - /// Null if the attribute is not found - /// If there are 2 or more attributes - public static TAttribute GetAttribute(this ICustomAttributeProvider target, bool inherits) where TAttribute : Attribute + /// + /// Returns single attribute from the type + /// + /// Attribute to use + /// Attribute provider + /// + /// Null if the attribute is not found + /// If there are 2 or more attributes + public static TAttribute GetAttribute(this ICustomAttributeProvider target, bool inherits) where TAttribute : Attribute { if (target.IsDefined(typeof(TAttribute), inherits)) { @@ -544,7 +237,6 @@ public static TAttribute GetAttribute(this ICustomAttributeProvider } return null; - } public static bool HasAttribute(this ICustomAttributeProvider target, bool inherits) where TAttribute : Attribute @@ -552,15 +244,15 @@ public static bool HasAttribute(this ICustomAttributeProvider target return target.IsDefined(typeof(TAttribute), inherits); } - /// - /// Given a particular MemberInfo, return the custom attributes of the - /// given type on that member. - /// - /// Type of attribute to retrieve. - /// The member to look at. - /// True to include attributes inherited from base classes. - /// Array of found attributes. - public static TAttribute[] GetAttributes(this ICustomAttributeProvider target, bool inherits) where TAttribute : Attribute + /// + /// Given a particular MemberInfo, return the custom attributes of the + /// given type on that member. + /// + /// Type of attribute to retrieve. + /// The member to look at. + /// True to include attributes inherited from base classes. + /// Array of found attributes. + public static TAttribute[] GetAttributes(this ICustomAttributeProvider target, bool inherits) where TAttribute : Attribute { if (target.IsDefined(typeof(TAttribute), inherits)) { @@ -569,30 +261,9 @@ public static TAttribute[] GetAttributes(this ICustomAttributeProvid .Cast(); return SortAttributesIfPossible(attributes).ToArray(); - - #region Obsolete - //return target - // .GetCustomAttributes(typeof(TAttribute), inherits) - // .ToArray(a => (TAttribute)a); - #endregion } - return new TAttribute[0]; - #region Obsolete - //// OBSOLETE 1 - //return target.GetCustomAttributes(typeof(TAttribute), inherits).Cast().ToArray(); - - //// OBSOLETE 2 - //object[] attributesAsObjects = member.GetCustomAttributes(typeof(TAttribute), inherits); - //TAttribute[] attributes = new TAttribute[attributesAsObjects.Length]; - //int index = 0; - //Array.ForEach(attributesAsObjects, - // delegate(object o) - // { - // attributes[index++] = (TAttribute)o; - // }); - //return attributes; - #endregion + return new TAttribute[0]; } /// @@ -627,7 +298,8 @@ public static TAttribute[] GetAllAttributes(this MemberInfo member, return attributes.ToArray(); } - internal static IEnumerable SortAttributesIfPossible(IEnumerable attributes) + [SuppressMessage("ReSharper", "SuspiciousTypeConversion.Global")] + internal static IEnumerable SortAttributesIfPossible(IEnumerable attributes) where TAttribute : Attribute { if (typeof(IOrdered).IsAssignableFrom(typeof(TAttribute))) @@ -673,15 +345,16 @@ internal static Type FindIEnumerable(this Type seqType) return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType()); if (seqType.IsGenericType) { - foreach (Type arg in seqType.GetGenericArguments()) + var args = seqType.GetGenericArguments(); + foreach (var arg in args) { - Type ienum = typeof(IEnumerable<>).MakeGenericType(arg); + var ienum = typeof(IEnumerable<>).MakeGenericType(arg); if (ienum.IsAssignableFrom(seqType)) return ienum; } } Type[] ifaces = seqType.GetInterfaces(); - if (ifaces != null && ifaces.Length > 0) + if (ifaces.Length > 0) { foreach (Type iface in ifaces) { diff --git a/src/Libraries/SmartStore.Core/Extensions/XPathExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/XPathExtensions.cs new file mode 100644 index 0000000000..d44109c7f2 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Extensions/XPathExtensions.cs @@ -0,0 +1,98 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Xml.XPath; +using SmartStore.Core.Domain.Localization; + +namespace SmartStore +{ + public static class XPathExtensions + { + /// + /// Safe way to get the value of a xpath navigator item + /// + /// Type + /// The node + /// Optional xpath + /// Optional default value + /// For localized values. Xpath scheme: Localized/xpath[@culture = 'language.LanguageCulture'] + /// If null is passed, CultureInfo.InvariantCulture is used. + /// The value + public static T GetValue(this XPathNavigator node, string xpath = null, T defaultValue = default(T), Language language = null, CultureInfo culture = null) + { + try + { + if (node != null) + { + if (xpath.IsEmpty()) + { + if (node.Value.HasValue()) + return node.Value.Convert(culture); + + return defaultValue; + } + + if (language != null) + xpath = "Localized/{0}[@culture = '{1}']".FormatWith(xpath, language.LanguageCulture.EmptyNull().ToLower()); + + var n = node.SelectSingleNode(xpath); + + if (n != null && n.Value.HasValue()) + return n.Value.Convert(culture); + } + } + catch (Exception exc) + { + exc.Dump(); + } + + return defaultValue; + } + + /// + /// Safe way to get a string value of a xpath navigator item + /// + /// The node + /// Optional xpath + /// Optional default value + /// The value + public static string GetString(this XPathNavigator node, string xpath = null, string defaultValue = null) + { + // note: better not merge GetString(...) and GetValue(...) + try + { + if (node != null) + { + if (xpath.IsEmpty()) + return node.Value; + + var n = node.SelectSingleNode(xpath); + + if (n != null) + return n.Value; + } + } + catch (Exception exc) + { + exc.Dump(); + } + + return defaultValue; + } + + /// + /// Safe way to get a localized string value of a xpath navigator item + /// + /// The node + /// For localized values. Xpath scheme: Localized/xpath[@culture = 'language.LanguageCulture'] + /// Optional xpath + /// Optional default value + /// The value + public static string GetString(this XPathNavigator node, Language language, string xpath, string defaultValue = null) + { + return node.GetString( + "Localized/{0}[@culture = '{1}']".FormatWith(xpath.EmptyNull(), language.LanguageCulture.EmptyNull().ToLower()), + defaultValue); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Extensions/XmlNodeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/XmlNodeExtensions.cs index 4f5ce383fb..601c4cef6d 100644 --- a/src/Libraries/SmartStore.Core/Extensions/XmlNodeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/XmlNodeExtensions.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Globalization; using System.Xml; namespace SmartStore @@ -16,7 +17,7 @@ public static class XmlNodeExtensions XmlAttribute attr = node.Attributes[attributeName]; if (attr != null) { - return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(attr.InnerText); + return attr.InnerText.Convert(); } } } @@ -27,6 +28,7 @@ public static class XmlNodeExtensions return defaultValue; } + /// Safe way to get inner text of an attribute. public static string GetAttributeText(this XmlNode node, string attributeName) { @@ -34,19 +36,20 @@ public static string GetAttributeText(this XmlNode node, string attributeName) } /// Safe way to get inner text of a node. - public static T GetText(this XmlNode node, string xpath = null, T defaultValue = default(T)) + public static T GetText(this XmlNode node, string xpath = null, T defaultValue = default(T), CultureInfo culture = null) { try { if (node != null) { if (xpath.IsEmpty()) - return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(node.InnerText); + return node.InnerText.Convert(); - XmlNode n = node.SelectSingleNode(xpath); - if (n != null) - return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(n.InnerText); - } + var n = node.SelectSingleNode(xpath); + + if (n != null && n.InnerText.HasValue()) + return n.InnerText.Convert(culture); + } } catch (Exception exc) { @@ -55,11 +58,11 @@ public static string GetAttributeText(this XmlNode node, string attributeName) return defaultValue; } + /// Safe way to get inner text of a node. public static string GetText(this XmlNode node, string xpath = null, string defaultValue = default(string)) { return node.GetText(xpath, defaultValue); } - - } // class + } } diff --git a/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs index 000994fb77..ca4e1c2c71 100644 --- a/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs @@ -1,10 +1,9 @@ using System; -using System.ComponentModel; using System.Xml; +using SmartStore.Core.Domain.Localization; namespace SmartStore { - /// codehint: sm-add public static class XmlWriterExtensions { public static void WriteCData(this XmlWriter writer, string name, string value, string prefix = null, string ns = null) @@ -15,19 +14,40 @@ public static void WriteCData(this XmlWriter writer, string name, string value, writer.WriteStartElement(name); else writer.WriteStartElement(prefix, name, ns); + writer.WriteCData(value.RemoveInvalidXmlChars()); + writer.WriteEndElement(); } } - public static void WriteNode(this XmlWriter writer, string name, Action content) - { - if (name.HasValue() && content != null) - { + + /// + /// Created a simple or CData node element + /// + /// The writer + /// Node name + /// Node value + /// The language. Its culture is always converted to lowercase! + /// Whether to create simple or CData node + public static void Write(this XmlWriter writer, string name, string value, Language language = null, bool asCData = false) + { + if (name.HasValue() && value != null) + { + if (language != null && value.IsEmpty()) + return; // do not create too many empty nodes for empty localized values + writer.WriteStartElement(name); - content(); + + if (language != null) + writer.WriteAttributeString("culture", language.LanguageCulture.EmptyNull().ToLower()); + + if (asCData) + writer.WriteCData(value.RemoveInvalidXmlChars()); + else + writer.WriteString(value.RemoveInvalidXmlChars()); + writer.WriteEndElement(); } } - - } // class + } } diff --git a/src/Libraries/SmartStore.Core/Fakes/FakeController.cs b/src/Libraries/SmartStore.Core/Fakes/FakeController.cs new file mode 100644 index 0000000000..1ff087bb77 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Fakes/FakeController.cs @@ -0,0 +1,8 @@ +using System.Web.Mvc; + +namespace SmartStore.Core.Fakes +{ + public class FakeController : Controller + { + } +} diff --git a/src/Libraries/SmartStore.Core/Financial/Currency.cs b/src/Libraries/SmartStore.Core/Financial/Currency.cs deleted file mode 100644 index a30be2bf3f..0000000000 --- a/src/Libraries/SmartStore.Core/Financial/Currency.cs +++ /dev/null @@ -1,307 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Globalization; - -namespace SmartStore.Financial -{ - - [Serializable] - public struct Currency : IEquatable - { - #region Fields - - internal readonly RegionInfo Region; - public static readonly Currency Empty = new Currency(); - - #endregion - - #region Ctor - - public Currency(RegionInfo region) - : this() - { - Guard.ArgumentNotNull(() => region); - - Region = region; - - this.ThreeLetterISOCode = region.ISOCurrencySymbol; - this.Symbol = region.CurrencySymbol; - this.NativeName = region.CurrencyNativeName; - this.EnglishName = region.CurrencyEnglishName; - } - - public Currency(Currency currency) - : this() - { - this.ThreeLetterISOCode = currency.ThreeLetterISOCode; - this.Symbol = currency.Symbol; - this.NativeName = currency.NativeName; - this.EnglishName = currency.EnglishName; - this.Region = currency.Region; - } - - #endregion - - #region Properties - - /// - /// Gets the ISO standard three-letter name. - /// - public string ThreeLetterISOCode { get; private set; } - - ///// - ///// Gets the integer-based ISO code. - ///// - //public int NumericISOCode { get; private set; } - - /// - /// Gets the prefixing symbol of the currency. - /// - public string Symbol { get; private set; } - - /// - /// Gets the native name of the currency. - /// - public string NativeName { get; private set; } - - /// - /// Gets the english name of the currency. - /// - public string EnglishName { get; private set; } - - // TODO: (?) DecimalDigits, Separator, GroupSeparator, GroupSizes, NegativePatterm, PositivePattern - - #endregion - - #region Methods - - public static Currency Default - { - get - { - return new Currency(RegionInfo.CurrentRegion); - } - } - - public static Currency GetCurrency(string threeLetterISOCode) - { - Guard.ArgumentNotEmpty(threeLetterISOCode, "threeLetterISOCode"); - - if (threeLetterISOCode.Length != 3) - throw new ArgumentException("The currency ISO code must be 3 letters in length.", "threeLetterISOCode"); - - var query = - from r in GetValidRegions() - where r.ISOCurrencySymbol.Equals(threeLetterISOCode, StringComparison.InvariantCultureIgnoreCase) - select r; - var region = query.First(); - - return new Currency(region); - } - - public static Currency GetCurrency(RegionInfo region) - { - return new Currency(region); - } - - public static bool TryGetCurrency(string threeLetterISOCode, out Currency currency) - { - currency = Currency.Empty; - - try - { - currency = Currency.GetCurrency(threeLetterISOCode); - return (currency.ThreeLetterISOCode != null); - } - catch - { - return false; - } - } - - public static IEnumerable AllCurrencies - { - get - { - return from r in GetValidRegions() - select new Currency(r); - } - } - - internal static IEnumerable GetValidRegions() - { - return - from c in - (from c in CultureInfo.GetCultures(CultureTypes.InstalledWin32Cultures) - where !c.IsNeutralCulture && c.LCID != 0x7f - select c) - select new RegionInfo(c.Name); - } - - #endregion - - #region Comparison - - public override int GetHashCode() - { - return this.ThreeLetterISOCode.ToUpperInvariant().GetHashCode(); - } - - public override bool Equals(object obj) - { - if (obj == null) - return false; - - if (obj.GetType() != typeof(Currency)) - return false; - - return this.Equals((Currency)obj); - } - - public bool Equals(Currency other) - { - return (this.ThreeLetterISOCode.Equals(other.ThreeLetterISOCode, StringComparison.InvariantCultureIgnoreCase)); - } - - public override string ToString() - { - return "{0} ({1})".FormatCurrent( - this.Symbol ?? this.ThreeLetterISOCode, - this.NativeName ?? this.EnglishName ?? this.ThreeLetterISOCode); - } - - #endregion - - #region Implicit operators - - public static implicit operator Currency(string threeLetterISOCode) - { - return Currency.GetCurrency(threeLetterISOCode); - } - - public static implicit operator Currency(Money value) - { - return new Currency(value.Currency); - } - - #endregion - - #region Operator overloads - - public static bool operator ==(Currency left, Currency right) - { - return left.Equals(right); - } - - public static bool operator !=(Currency left, Currency right) - { - return !left.Equals(right); - } - - #endregion - - #region Temp/Cargo - - // ISOCode, NumericISOCode, Symbol, EnglishName, DecimalDigits - - //new CurrencyInfo("ARS", 32, "", "Argentine peso", 2, false), - //new CurrencyInfo("AUD", 36, "", "Australian dollar", 2, false), - //new CurrencyInfo("ATS", 40, "", "Austrian schilling", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("BSD", 44, "", "Bahamian dollar", 2, false), - //new CurrencyInfo("BHD", 48, "", "Bahraini dinar", 2, false), - //new CurrencyInfo("BDT", 50, "", "Bangladesh taka", 2, false), - //new CurrencyInfo("BEF", 56, "", "Belgian franc", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("BWP", 72, "", "Botswanan pula", 2, false), - //new CurrencyInfo("BRL", 986, "", "Brazilian real", 2, false), - //new CurrencyInfo("GBP", 826, "£", "British pound", 2, true), - //new CurrencyInfo("BND", 96, "", "Brunei dollar", 2, false), - //new CurrencyInfo("BGN", 975, "", "Bulgarian lev", 2, false), - //new CurrencyInfo("CAD", 124, "", "Canadian dollar", 2, false), - //new CurrencyInfo("CLP", 152, "", "Chilean peso", 2, false), - //new CurrencyInfo("CNY", 156, "", "Chinese yuan renminbi", 2, false), - //new CurrencyInfo("COP", 170, "", "Colombian peso", 2, false), - //new CurrencyInfo("HRK", 191, "", "Croatian kuna", 2, false), - //new CurrencyInfo("CYP", 196, "", "Cyprus pound", 2, false), - //new CurrencyInfo("CZK", 203, "", "Czech koruna", 2, false), - //new CurrencyInfo("DKK", 208, "DK", "Danische Krone", 2, true), - //new CurrencyInfo("DEM", 276, "DM", "Deutsche Mark", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("EGP", 818, "", "Egyptian pound", 2, false), - //new CurrencyInfo("EEK", 233, "", "Estonian kroon", 2, false), - //new CurrencyInfo("EUR", 978, "", "Euro", 2, true), - //new CurrencyInfo("XEU", 954, "", "European Currency Unit", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("FJD", 242, "", "Fiji dollar", 2, false), - //new CurrencyInfo("FIM", 246, "", "Finnish markka", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("FRF", 250, "", "French franc", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("GHC", 288, "", "Ghana cedi", 2, false), - //new CurrencyInfo("GRD", 300, "", "Greek drachma", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("HNL", 340, "", "Honduras lempira", 2, false), - //new CurrencyInfo("HKD", 344, "", "Hong Kong dollar", 2, false), - //new CurrencyInfo("HUF", 348, "", "Hungarian forint", 2, false), - //new CurrencyInfo("ISK", 352, "", "Iceland krona", 2, false), - //new CurrencyInfo("INR", 356, "", "Indian rupee", 2, false), - //new CurrencyInfo("IDR", 360, "", "Indonesian rupiah", 2, false), - //new CurrencyInfo("IRR", 364, "", "Iranian rial", 2, false), - //new CurrencyInfo("IQD", 368, "", "Iraqi dinar", 2, false), - //new CurrencyInfo("IEP", 372, "", "Irish pound", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("ILS", 376, "Shekel", "Israeli shekel", 2, false), - //new CurrencyInfo("ITL", 380, "", "Italian lira", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("JMD", 388, "", "Jamaican dollar", 2, false), - //new CurrencyInfo("JPY", 392, "¥", "Japanese yen", 2, true), - //new CurrencyInfo("KWD", 414, "", "Kuwaiti dinar", 2, false), - //new CurrencyInfo("LVL", 428, "", "Latvian lats", 2, false), - //new CurrencyInfo("LYD", 434, "", "Libyan dinar", 2, false), - //new CurrencyInfo("LTL", 440, "", "Lithuanian litas", 2, false), - //new CurrencyInfo("LUF", 442, "", "Luxembourg franc", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("MYR", 458, "", "Malaysian ringgit", 2, false), - //new CurrencyInfo("MTL", 470, "", "Maltese lira", 2, false), - //new CurrencyInfo("MUR", 480, "", "Mauritius rupee", 2, false), - //new CurrencyInfo("MXP", 484, "", "Mexican peso", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("MXN", 484, "", "Mexican peso", 2, false), - //new CurrencyInfo("MAD", 504, "", "Moroccan dirham", 2, false), - //new CurrencyInfo("MMK", 104, "", "Myanmar kyat", 2, false), - //new CurrencyInfo("NPR", 524, "", "Nepalese rupee", 2, false), - //new CurrencyInfo("ANG", 532, "", "Netherlands Antillian guilder", 2, false), - //new CurrencyInfo("NLG", 528, "", "Netherlands guilder", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("NZD", 554, "", "New Zealand dollar", 2, false), - //new CurrencyInfo("NOK", 578, "NK", "Norwegische Krone", 2, true), - //new CurrencyInfo("OMR", 512, "", "Omani rial", 2, false), - //new CurrencyInfo("PKR", 586, "", "Pakistan rupee", 2, false), - //new CurrencyInfo("PAB", 590, "", "Panamanian balboa", 2, false), - //new CurrencyInfo("PEN", 604, "", "Peruvian sol nuevo", 2, false), - //new CurrencyInfo("PHP", 608, "", "Philippine peso", 2, false), - //new CurrencyInfo("PLN", 985, "Zloty", "Polish zloty", 2, true), - //new CurrencyInfo("PTE", 620, "", "Portuguese escudo", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("QAR", 634, "", "Qatari riyal", 2, false), - //new CurrencyInfo("ROL", 642, "", "Romanian leu", 2, false), - //new CurrencyInfo("RON", 0, "", "Romanian new leu", 2, false), - //new CurrencyInfo("RUB", 643, "Rubel", "Russian ruble", 2, true), - //new CurrencyInfo("SAR", 682, "", "Saudi riyal", 2, false), - //new CurrencyInfo("SGD", 702, "", "Singapore dollar", 2, false), - //new CurrencyInfo("SKK", 703, "", "Slovak koruna", 2, false), - //new CurrencyInfo("SIT", 705, "", "Slovenia tolar", 2, false), - //new CurrencyInfo("ZAR", 710, "Rand", "South African rand", 2, true), - //new CurrencyInfo("ZAL", 991, "", "South African rand (financial)", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("KRW", 410, "", "South Korean won", 2, false), - //new CurrencyInfo("ESP", 724, "", "Spanish peseta", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("XDR", 960, "", "Special Drawing Right", 2, false), - //new CurrencyInfo("LKR", 144, "", "Sri Lankan rupee", 2, false), - //new CurrencyInfo("SEK", 752, "SK", "Schwedische Krone", 2, true), - //new CurrencyInfo("CHF", 756, "Franken", "Swiss franc", 2, true), - //new CurrencyInfo("TWD", 901, "", "Taiwan new dollar", 2, false), - //new CurrencyInfo("THB", 764, "", "Thai baht", 2, false), - //new CurrencyInfo("TTD", 780, "", "Trinidad and Tobago dollar", 2, false), - //new CurrencyInfo("TND", 788, "", "Tunisian dinar", 2, false), - //new CurrencyInfo("TRL", 792, "", "Turkish lira", 2, false), // Gibt es nicht mehr - //new CurrencyInfo("TRY", 949, "", "Turkish new lira", 2, true), - //new CurrencyInfo("AED", 784, "", "U.A.E. dirham", 2, false), - //new CurrencyInfo("USD", 840, "$", "US Dollar", 2, true), - //new CurrencyInfo("VEB", 862, "", "Venezuelan bolivar", 2, false) - - - #endregion - - } - -} diff --git a/src/Libraries/SmartStore.Core/Financial/Money.cs b/src/Libraries/SmartStore.Core/Financial/Money.cs deleted file mode 100644 index 9166aaa4c1..0000000000 --- a/src/Libraries/SmartStore.Core/Financial/Money.cs +++ /dev/null @@ -1,652 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Globalization; - -namespace SmartStore.Financial -{ - - [Serializable] - public struct Money : IComparable, IComparable, IFormattable, IConvertible - { - #region Fields - - public static readonly Money Zero = new Money(0, Currency.Empty); - - #endregion - - #region Ctor - - public Money(decimal amount) - : this(amount, Currency.Default) - { - } - - public Money(decimal amount, string currencyISOCode) - : this(amount, Currency.GetCurrency(currencyISOCode)) - { - } - - //public Money(decimal amount, int currencyCode) - //{ - // // TODO - //} - - public Money(decimal amount, Currency currency) - : this() - { - this.Value = amount; - this.Currency = currency; - } - - public Money(Money money) - : this() - { - this.Value = money.Value; - this.Currency = money.Currency; - } - - #endregion - - #region Properties - - public decimal Value { get; private set; } - - public Currency Currency { get; private set; } - - #endregion - - #region Methods - - public override int GetHashCode() - { - if (this.Value == 0) - return 0; - - return this.Value.GetHashCode() | this.Currency.GetHashCode(); - } - - public override bool Equals(object obj) - { - if (obj == null) - return false; - - if (obj.GetType() != typeof(Money)) - return false; - - Money other = (Money)obj; - - if (other.Value == 0 && this.Value == 0) - return true; - - return other.Value == this.Value && other.Currency == this.Currency; - } - - 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.Value == other.Value) - return 0; - if (this.Value < other.Value) - return -1; - return 1; - } - - private static void GuardCurrenciesAreEqual(Money a, Money b) - { - if (a.Currency != b.Currency) - throw new InvalidOperationException("Cannot operate on money values with different currencies."); - } - - #endregion - - #region ToString - - /// - /// Returns a string-representation of the money value. - /// - /// The string value of the money. - public override string ToString() - { - return this.ToString("C", null, false); - } - - public string ToString(bool useISOCodeAsSymbol) - { - return this.ToString("C", null, useISOCodeAsSymbol); - } - - public string ToString(string format) - { - return this.ToString(format, null, false); - } - - public string ToString(string format, bool useISOCodeAsSymbol) - { - return this.ToString(format, null, useISOCodeAsSymbol); - } - - public string ToString(IFormatProvider provider) - { - return this.ToString("C", provider, false); - } - - public string ToString(IFormatProvider provider, bool useISOCodeAsSymbol) - { - return this.ToString("C", provider, useISOCodeAsSymbol); - } - - public string ToString(string format, IFormatProvider provider) - { - return this.ToString(format, provider, false); - } - - public string ToString(string format, IFormatProvider provider, bool useISOCodeAsSymbol) - { - // REVIEW: was ist mit Digits, Groups, Decimal usw. Müssen wir die nicht von DB oder sonstwo beziehen? - - Guard.ArgumentNotEmpty(format, "format"); - - NumberFormatInfo info = null; - - CultureInfo ci; - - if (provider == null) - provider = CultureInfo.GetCultureInfo(this.Currency.Region.Name).NumberFormat; - - info = provider as NumberFormatInfo; - if (info == null) - { - ci = provider as CultureInfo; - if (ci != null) - info = ci.NumberFormat; - } - - if (info != null) - { - info = (NumberFormatInfo)info.Clone(); - - if (Currency != Currency.Empty) - { - info.CurrencySymbol = useISOCodeAsSymbol ? - Currency.ThreeLetterISOCode : - Currency.Symbol.NullEmpty() ?? Currency.ThreeLetterISOCode; - } - else - { - info.CurrencySymbol = "?"; - } - } - - return Value.ToString(format, info); - } - - #endregion - - #region Implicit/explicit operator overloads - - public static implicit operator Money(byte value) - { - return new Money(System.Convert.ToDecimal(value)); - } - - public static implicit operator Money(int value) - { - return new Money(System.Convert.ToDecimal(value)); - } - - public static implicit operator Money(long value) - { - return new Money(System.Convert.ToDecimal(value)); - } - - public static implicit operator Money(sbyte value) - { - return new Money(System.Convert.ToDecimal(value)); - } - - public static implicit operator Money(short value) - { - return new Money(System.Convert.ToDecimal(value)); - } - - public static implicit operator Money(uint value) - { - return new Money(System.Convert.ToDecimal(value)); - } - - public static implicit operator Money(ulong value) - { - return new Money(System.Convert.ToDecimal(value)); - } - - public static implicit operator Money(ushort value) - { - return new Money(System.Convert.ToDecimal(value)); - } - - public static implicit operator Money(decimal value) - { - return new Money(value); - } - - public static implicit operator Money(double value) - { - return new Money(System.Convert.ToDecimal(value)); - } - - public static implicit operator Money(float value) - { - return new Money(System.Convert.ToDecimal(value)); - } - - public static explicit operator byte(Money money) - { - return System.Convert.ToByte(money.Value); - } - - public static explicit operator decimal(Money money) - { - return money.Value; - } - - public static explicit operator double(Money money) - { - return System.Convert.ToDouble(money.Value); - } - - public static explicit operator float(Money money) - { - return System.Convert.ToSingle(money.Value); - } - - public static explicit operator int(Money money) - { - return System.Convert.ToInt32(money.Value); - } - - public static explicit operator long(Money money) - { - return System.Convert.ToInt64(money.Value); - } - - public static explicit operator sbyte(Money money) - { - return System.Convert.ToSByte(money.Value); - } - - public static explicit operator short(Money money) - { - return System.Convert.ToInt16(money.Value); - } - - public static explicit operator ushort(Money money) - { - return System.Convert.ToUInt16(money.Value); - } - - public static explicit operator uint(Money money) - { - return System.Convert.ToUInt32(money.Value); - } - - public static explicit operator ulong(Money money) - { - return System.Convert.ToUInt64(money.Value); - } - - #endregion - - #region Equality/Comparison - - public static bool operator ==(Money a, Money b) - { - return a.Equals(b); - } - - public static bool operator !=(Money a, Money b) - { - return !a.Equals(b); - } - - public static bool operator >(Money a, Money b) - { - if (b.Value == 0 && a.Value > 0) - return true; - - if (b.Value < 0 && a.Value >= 0) - return true; - - if (a == Money.Zero) - return b.Value < 0; - - if (b == Money.Zero) - return a.Value > 0; - - GuardCurrenciesAreEqual(a, b); - - return a.Value > b.Value; - - } - - public static bool operator <(Money a, Money b) - { - if (a.Value == 0 && b.Value > 0) - return true; - - if (a.Value < 0 && b.Value >= 0) - return true; - - if (b == Money.Zero) - return a.Value < 0; - - if (a == Money.Zero) - return b.Value > 0; - - GuardCurrenciesAreEqual(a, b); - - return a.Value < b.Value; - } - - public static bool operator <=(Money a, Money b) - { - if (a.Value < 0 && b.Value >= 0) - return true; - - if (a.Value == 0 && b.Value >= 0) - return true; - - GuardCurrenciesAreEqual(a, b); - - return a.Value < b.Value || a.Equals(b); - - } - - public static bool operator >=(Money a, Money b) - { - if (b.Value < 0 && a.Value >= 0) - return true; - - if (b.Value == 0 && a.Value >= 0) - return true; - - GuardCurrenciesAreEqual(a, b); - - return a.Value > b.Value || a.Equals(b); - - } - - #endregion - - #region Addition - - public static Money operator ++(Money a) - { - a.Value++; - - return a; - } - - public static Money operator +(Money a, Money b) - { - if (a == Money.Zero) - return b; - - if (b == Money.Zero) - return a; - - GuardCurrenciesAreEqual(a, b); - - return new Money(a.Value + b.Value, a.Currency); - } - - #endregion - - #region Subtraction - - public static Money operator --(Money a) - { - a.Value--; - - return a; - } - - public static Money operator -(Money a, Money b) - { - if (a == Money.Zero) - return new Money(0 - b.Value, a.Currency); - - if (b == Money.Zero) - return a; - - GuardCurrenciesAreEqual(a, b); - - return new Money(a.Value - b.Value, a.Currency); - } - - #endregion - - #region Multiplication - - public static Money operator *(Money a, Money b) - { - GuardCurrenciesAreEqual(a, b); - return new Money(a.Value * b.Value, a.Currency); - } - - public static Money operator *(Money a, decimal b) - { - return new Money(a.Value * b, a.Currency); - } - - public static Money operator *(decimal a, Money b) - { - return new Money(b.Value * a, b.Currency); - } - - public static Money operator *(Money a, int b) - { - return new Money(a.Value * b, a.Currency); - } - - public static Money operator *(int a, Money b) - { - return new Money(b.Value * a, b.Currency); - } - - #endregion - - #region Division - - public static Money operator /(Money a, Money b) - { - GuardCurrenciesAreEqual(a, b); - return new Money(a.Value / b.Value, a.Currency); - } - - public static Money operator /(Money a, decimal b) - { - return new Money(a.Value / b, a.Currency); - } - - public static Money operator /(decimal a, Money b) - { - return new Money(a / b.Value, b.Currency); - } - - public static Money operator /(Money a, int b) - { - return new Money(a.Value / b, a.Currency); - } - - public static Money operator /(int a, Money b) - { - return new Money(a / b.Value, b.Currency); - } - - #endregion - - #region Parse - - // [...] TODO - - #endregion - - #region Exchange & Math - - /// - /// 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 == Money.Zero) - return 0; - - if (denominator == Money.Zero) - throw new DivideByZeroException("Attempted to divide by zero!"); - - GuardCurrenciesAreEqual(numerator, denominator); - - return numerator.Value / denominator.Value; - } - - /// - /// 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 m1, Money m2) - { - if (m1 == m2) // This will check currency. - return m1; - - return new Money(Math.Min(m1.Value, m2.Value), m1.Currency); - } - - /// - /// 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 m1, Money m2) - { - if (m1 == m2) // This will check currency. - return m1; - - return new Money(Math.Max(m1.Value, m2.Value), m1.Currency); - } - - /// - /// 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.Value), value.Currency); - } - - #endregion - - #region IConvertible Members - - public TypeCode GetTypeCode() - { - return TypeCode.Decimal; - } - - public bool ToBoolean(IFormatProvider provider) - { - throw Error.InvalidCast(typeof(Money), typeof(bool)); - } - - public byte ToByte(IFormatProvider provider) - { - return (byte)this.Value; - } - - public char ToChar(IFormatProvider provider) - { - throw Error.InvalidCast(typeof(Money), typeof(bool)); - } - - public DateTime ToDateTime(IFormatProvider provider) - { - throw Error.InvalidCast(typeof(Money), typeof(bool)); - } - - public decimal ToDecimal(IFormatProvider provider) - { - return this.Value; - } - - public double ToDouble(IFormatProvider provider) - { - return (double)this.Value; - } - - public short ToInt16(IFormatProvider provider) - { - return (short)this.Value; - } - - public int ToInt32(IFormatProvider provider) - { - return (int)this.Value; - } - - public long ToInt64(IFormatProvider provider) - { - return (long)this.Value; - } - - public sbyte ToSByte(IFormatProvider provider) - { - return (sbyte)this.Value; - } - - public float ToSingle(IFormatProvider provider) - { - return (float)this.Value; - } - - public object ToType(Type conversionType, IFormatProvider provider) - { - return System.Convert.ChangeType(this.Value, conversionType, provider); - } - - public ushort ToUInt16(IFormatProvider provider) - { - return (ushort)this.Value; - } - - public uint ToUInt32(IFormatProvider provider) - { - return (uint)this.Value; - } - - public ulong ToUInt64(IFormatProvider provider) - { - return (ulong)this.Value; - } - - #endregion - - } - -} diff --git a/src/Libraries/SmartStore.Core/Html/CodeFormatter/CodeFormatHelper.cs b/src/Libraries/SmartStore.Core/Html/CodeFormatter/CodeFormatHelper.cs index d2de15007c..a840a6ed53 100644 --- a/src/Libraries/SmartStore.Core/Html/CodeFormatter/CodeFormatHelper.cs +++ b/src/Libraries/SmartStore.Core/Html/CodeFormatter/CodeFormatHelper.cs @@ -17,31 +17,6 @@ public partial class CodeFormatHelper #region Utilities - /// - /// Code evaluator method - /// - /// Match - /// Formatted text - private static string CodeEvaluator(Match match) - { - if (!match.Success) - return match.Value; - - var options = new HighlightOptions(); - - options.Language = match.Groups["lang"].Value; - options.Code = match.Groups["code"].Value; - options.DisplayLineNumbers = match.Groups["linenumbers"].Value == "on" ? true : false; - options.Title = match.Groups["title"].Value; - options.AlternateLineNumbers = match.Groups["altlinenumbers"].Value == "on" ? true : false; - - string result = match.Value.Replace(match.Groups["begin"].Value, ""); - result = result.Replace(match.Groups["end"].Value, ""); - result = Highlight(options, result); - return result; - - } - /// /// Code evaluator method /// diff --git a/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs b/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs index 51d36bfc7c..f5f5527993 100644 --- a/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs +++ b/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs @@ -44,9 +44,9 @@ private static string EnsureOnlyAllowedHtml(string text) private static bool IsValidTag(string tag, string tags) { string[] allowedTags = tags.Split(','); - if (tag.IndexOf("javascript") >= 0) return false; - if (tag.IndexOf("vbscript") >= 0) return false; - if (tag.IndexOf("onclick") >= 0) return false; + 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; var endchars = new char[] { ' ', '>', '/', '\t' }; @@ -54,12 +54,7 @@ private static bool IsValidTag(string tag, string tags) if (pos > 0) tag = tag.Substring(0, pos); if (tag[0] == '/') tag = tag.Substring(1); - foreach (string aTag in allowedTags) - { - if (tag == aTag) return true; - } - - return false; + return allowedTags.Any(aTag => tag == aTag); } #endregion @@ -87,12 +82,12 @@ public static string FormatText(string text, bool stripTags, { if (stripTags) { - text = HtmlUtils.StripTags(text); + text = StripTags(text); } if (allowHtml) { - text = HtmlUtils.EnsureOnlyAllowedHtml(text); + text = EnsureOnlyAllowedHtml(text); } else { @@ -101,7 +96,7 @@ public static string FormatText(string text, bool stripTags, if (convertPlainTextToHtml) { - text = HtmlUtils.ConvertPlainTextToHtml(text); + text = ConvertPlainTextToHtml(text); } if (allowBBCode) @@ -208,7 +203,6 @@ public static string ConvertHtmlToPlainText(string text, /// Converts an attribute string spec to a html table putting each new line in a TR and each attr name/value in a TD. /// /// The text to convert - /// A value indicating whether to encode (HTML) values /// The formatted (html) string public static string ConvertPlainTextToTable(string text, string tableCssClass = null) { diff --git a/src/Libraries/SmartStore.Core/Html/ResolveLinksHelper.cs b/src/Libraries/SmartStore.Core/Html/ResolveLinksHelper.cs index d0e72938fe..c58f9540ba 100644 --- a/src/Libraries/SmartStore.Core/Html/ResolveLinksHelper.cs +++ b/src/Libraries/SmartStore.Core/Html/ResolveLinksHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text.RegularExpressions; @@ -23,6 +24,8 @@ public partial class ResolveLinksHelper /// /// Shortens any absolute URL to a specified maximum length /// + [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] + [SuppressMessage("ReSharper", "StringLastIndexOfIsCultureSpecific.1")] private static string ShortenUrl(string url, int max) { if (url.Length <= max) diff --git a/src/Libraries/SmartStore.Core/IHideObjectMembers.cs b/src/Libraries/SmartStore.Core/IHideObjectMembers.cs index 7228c572b6..97947ea881 100644 --- a/src/Libraries/SmartStore.Core/IHideObjectMembers.cs +++ b/src/Libraries/SmartStore.Core/IHideObjectMembers.cs @@ -1,13 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; namespace SmartStore { - [EditorBrowsable(EditorBrowsableState.Never)] public interface IHideObjectMembers { diff --git a/src/Libraries/SmartStore.Core/IO/Media/FileSystemStorageProvider.cs b/src/Libraries/SmartStore.Core/IO/Media/FileSystemStorageProvider.cs index 8a047c9110..636c7fbee5 100644 --- a/src/Libraries/SmartStore.Core/IO/Media/FileSystemStorageProvider.cs +++ b/src/Libraries/SmartStore.Core/IO/Media/FileSystemStorageProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Web.Hosting; @@ -12,9 +13,10 @@ public class FileSystemStorageProvider : IStorageProvider private readonly string _storagePath; private readonly string _publicPath; - public FileSystemStorageProvider(FileSystemSettings settings) + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public FileSystemStorageProvider(FileSystemSettings settings) { - string mediaPath = CommonHelper.MapPath("~/Media/", false); + var mediaPath = CommonHelper.MapPath("~/Media/", false); _storagePath = Path.Combine(mediaPath, settings.DirectoryName); var appPath = ""; @@ -29,11 +31,13 @@ public FileSystemStorageProvider(FileSystemSettings settings) _publicPath = appPath + "Media/" + settings.DirectoryName + "/"; } - string Map(string path) { + private string Map(string path) + { return string.IsNullOrEmpty(path) ? _storagePath : Path.Combine(_storagePath, path); } - static string Fix(string path) { + static string Fix(string path) + { return string.IsNullOrEmpty(path) ? "" : Path.DirectorySeparatorChar != '/' diff --git a/src/Libraries/SmartStore.Core/IO/Media/IStorageProvider.cs b/src/Libraries/SmartStore.Core/IO/Media/IStorageProvider.cs index a43de9e8d6..38a8958457 100644 --- a/src/Libraries/SmartStore.Core/IO/Media/IStorageProvider.cs +++ b/src/Libraries/SmartStore.Core/IO/Media/IStorageProvider.cs @@ -1,4 +1,4 @@ - +using System; using System.Collections.Generic; namespace SmartStore.Core.IO.Media @@ -48,12 +48,12 @@ public interface IStorageProvider /// 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); + /// + /// 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. @@ -62,12 +62,12 @@ public interface IStorageProvider /// 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); + /// + /// 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. diff --git a/src/Libraries/SmartStore.Core/IO/MimeTypes.cs b/src/Libraries/SmartStore.Core/IO/MimeTypes.cs index afecc35c0c..e44c81b0a2 100644 --- a/src/Libraries/SmartStore.Core/IO/MimeTypes.cs +++ b/src/Libraries/SmartStore.Core/IO/MimeTypes.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Security; using System.Text; @@ -24,13 +25,14 @@ public static string MapNameToMimeType(string fileNameOrExtension) /// /// The mime type /// The corresponding file extension (without dot) + [SuppressMessage("ReSharper", "RedundantAssignment")] public static string MapMimeTypeToExtension(string mimeType) { if (mimeType.IsEmpty()) return null; return _mimeMap.GetOrAdd(mimeType, k => { - string result = null; + string result; try { diff --git a/src/Libraries/SmartStore.Core/IO/VirtualPath/DefaultVirtualPathProvider.cs b/src/Libraries/SmartStore.Core/IO/VirtualPath/DefaultVirtualPathProvider.cs index 5e2164b149..102649e1b2 100644 --- a/src/Libraries/SmartStore.Core/IO/VirtualPath/DefaultVirtualPathProvider.cs +++ b/src/Libraries/SmartStore.Core/IO/VirtualPath/DefaultVirtualPathProvider.cs @@ -92,7 +92,7 @@ public bool IsMalformedVirtualPath(string virtualPath) if (string.IsNullOrEmpty(virtualPath)) return true; - if (virtualPath.IndexOf("..") >= 0) + if (virtualPath.IndexOf("..", StringComparison.OrdinalIgnoreCase) >= 0) { virtualPath = virtualPath.Replace(Path.DirectorySeparatorChar, '/'); string rootPrefix = virtualPath.StartsWith("~/") ? "~/" : virtualPath.StartsWith("/") ? "/" : ""; diff --git a/src/Libraries/SmartStore.Core/IPageable.cs b/src/Libraries/SmartStore.Core/IPageable.cs index 4751bc51c0..60d6b3f63f 100644 --- a/src/Libraries/SmartStore.Core/IPageable.cs +++ b/src/Libraries/SmartStore.Core/IPageable.cs @@ -3,8 +3,6 @@ using System.Collections; using System.Collections.Generic; -// codehint: sm-add (new file) - namespace SmartStore.Core { diff --git a/src/Libraries/SmartStore.Core/IStoreContext.cs b/src/Libraries/SmartStore.Core/IStoreContext.cs index 0986f24bb8..2ab8bfede9 100644 --- a/src/Libraries/SmartStore.Core/IStoreContext.cs +++ b/src/Libraries/SmartStore.Core/IStoreContext.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Core { @@ -44,7 +39,6 @@ public interface IStoreContext /// /// IsSingleStoreMode ? 0 : CurrentStore.Id /// - /// codehint: sm-add int CurrentStoreIdIfMultiStoreMode { get; } } } diff --git a/src/Libraries/SmartStore.Core/ITransient.cs b/src/Libraries/SmartStore.Core/ITransient.cs new file mode 100644 index 0000000000..761dcee833 --- /dev/null +++ b/src/Libraries/SmartStore.Core/ITransient.cs @@ -0,0 +1,9 @@ +using System; + +namespace SmartStore +{ + public interface ITransient + { + bool IsTransient { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/IWebHelper.cs b/src/Libraries/SmartStore.Core/IWebHelper.cs index d5b4e22a4a..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 @@ -116,24 +126,7 @@ public partial interface IWebHelper /// /// A value indicating whether /// Redirect URL; empty string if you want to redirect to the current page URL - void RestartAppDomain(bool makeRedirect = false, string redirectUrl = ""); - - /// - /// Get a value indicating whether the request is made by search engine (web crawler) - /// - /// HTTP context - /// Result - bool IsSearchEngine(HttpContextBase context); - - /// - /// Gets a value that indicates whether the client is being redirected to a new location - /// - bool IsRequestBeingRedirected { get; } - - /// - /// Gets or sets a value that indicates whether the client is being redirected to a new location using POST - /// - bool IsPostBeingDone { get; set; } - + /// Usually true after a new plugin was installed (nukes the MVC cache) + void RestartAppDomain(bool makeRedirect = false, string redirectUrl = "", bool aggressive = false); } } diff --git a/src/Libraries/SmartStore.Core/IWorkContext.cs b/src/Libraries/SmartStore.Core/IWorkContext.cs index d3a54cf702..e137ac98bd 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 49b3bc022e..79a7a41f22 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs @@ -19,24 +19,21 @@ public class AppDomainTypeFinder : ITypeFinder { #region Private Fields - private string _assemblySkipLoadingPattern = @"^System|^mscorlib|^Microsoft|^CppCodeProvider|^VJSharpCodeProvider|^WebDev|^Nuget|^Castle|^Iesi|^log4net|^Autofac|^AutoMapper|^EntityFramework|^EPPlus|^Fasterflect|^FiftyOne|^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"; + 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 bool _ignoreReflectionErrors = true; private bool _loadAppDomainAssemblies = true; - private string _assemblyRestrictToLoadingPattern = ".*"; private IList _customAssemblyNames = new List(); #endregion - #region Constructors - - /// Creates a new instance of the AppDomainTypeFinder. - public AppDomainTypeFinder() - { - } - - #endregion - #region Properties /// The app domain to look for types in. @@ -45,43 +42,53 @@ public virtual AppDomain App get { return AppDomain.CurrentDomain; } } - /// Gets or sets wether SmartStore should iterate assemblies in the app domain when loading SmartStore types. Loading patterns are applied when loading these assemblies. + /// + /// Gets or sets wether SmartStore should iterate assemblies in the app domain when loading SmartStore types. Loading patterns are applied when loading these assemblies. + /// public bool LoadAppDomainAssemblies { get { return _loadAppDomainAssemblies; } set { _loadAppDomainAssemblies = value; } } - /// Gets or sets assemblies loaded at startup in addition to those loaded in the AppDomain. + /// + /// Gets or sets assemblies loaded at startup in addition to those loaded in the AppDomain. + /// public IList CustomAssemblyNames { get { return _customAssemblyNames; } set { _customAssemblyNames = value; } } - /// Gets the pattern for dlls that we know don't need to be investigated. + /// + /// Gets the pattern for dlls that we know don't need to be investigated. + /// public string AssemblySkipLoadingPattern { get { return _assemblySkipLoadingPattern; } - set { _assemblySkipLoadingPattern = value; } + 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 arn't investigated (e.g. by not including something like "^SmartStore|..." you may break core functionality. + /// + /// 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; } - } - - #endregion - - #region Internal Attributed Assembly class - - private class AttributedAssembly - { - internal Assembly Assembly { get; set; } - internal Type PluginAttributeType { get; set; } + set + { + _assemblyRestrictToLoadingPattern = value; + _assemblyRestrictToLoadingRegex = null; + _assemblyMatchTable.Clear(); + } } #endregion @@ -103,7 +110,7 @@ public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable - /// Caches attributed assembly information so they don't have to be re-read - /// - private readonly List _attributedAssemblies = new List(); - - /// - /// Caches the assembly attributes that have been searched for - /// - private readonly List _assemblyAttributesSearched = new List(); - - /// Gets the assemblies related to the current implementation. + /// 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 List(); + var addedAssemblyNames = new HashSet(); var assemblies = new List(); if (LoadAppDomainAssemblies) @@ -184,12 +183,15 @@ public virtual IList GetAssemblies(bool ignoreInactivePlugins = false) #endregion - /// 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 it's name matches the configured patterns add it to our list. + /// /// /// - private void AddAssembliesInAppDomain(List addedAssemblyNames, List assemblies) + private void AddAssembliesInAppDomain(HashSet addedAssemblyNames, List assemblies) { - foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + var curDomainAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in curDomainAssemblies) { if (Matches(assembly.FullName)) { @@ -202,8 +204,10 @@ private void AddAssembliesInAppDomain(List addedAssemblyNames, ListAdds specificly configured assemblies. - protected virtual void AddCustomAssemblies(List addedAssemblyNames, List assemblies) + /// + /// Adds specificly configured assemblies. + /// + protected virtual void AddCustomAssemblies(HashSet addedAssemblyNames, List assemblies) { foreach (string assemblyName in CustomAssemblyNames) { @@ -216,25 +220,68 @@ protected virtual void AddCustomAssemblies(List addedAssemblyNames, List } } - /// Check if a dll is one of the shipped dlls that we know don't need to be investigated. + /// + /// Check if a dll is one of the shipped dlls that we know 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) { - return !Matches(assemblyFullName, AssemblySkipLoadingPattern) - && Matches(assemblyFullName, AssemblyRestrictToLoadingPattern); - } + if (_assemblyMatchTable.ContainsKey(assemblyFullName)) + { + return _assemblyMatchTable[assemblyFullName]; + } + + if (_assemblySkipLoadingRegex == null) + { + lock (s_lock) + { + if (_assemblySkipLoadingRegex == null) + { + _assemblySkipLoadingRegex = new Regex(_assemblySkipLoadingPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline); + } + } + } - /// 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); + if (_assemblySkipLoadingRegex.IsMatch(assemblyFullName)) + { + _assemblyMatchTable[assemblyFullName] = false; + return false; + } + + if (_assemblyRestrictToLoadingRegex == null) + { + lock (s_lock) + { + if (_assemblyRestrictToLoadingRegex == null) + { + _assemblyRestrictToLoadingRegex = new Regex(_assemblyRestrictToLoadingPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline); + } + } + } + + var matches = _assemblyRestrictToLoadingRegex.IsMatch(assemblyFullName); + _assemblyMatchTable[assemblyFullName] = matches; + + return matches; + + //return !Matches(assemblyFullName, AssemblySkipLoadingPattern) && Matches(assemblyFullName, AssemblyRestrictToLoadingPattern); } - /// Makes sure matching assemblies in the supplied folder are loaded in the app domain. + ///// + ///// 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) { diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs b/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs index 7a0c3d9dbb..a53cc21140 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs @@ -3,38 +3,22 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.ComponentModel; -using Fasterflect; +using SmartStore.Utilities; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using SmartStore.ComponentModel; namespace SmartStore { - /// /// Provides a standard base class for facilitating sophisticated comparison of objects. /// [Serializable] public abstract class ComparableObject { - /// - /// To help ensure hashcode uniqueness, a carefully selected random number multiplier - /// is used within the calculation. Goodrich and Tamassia's Data Structures and - /// Algorithms in Java asserts that 31, 33, 37, 39 and 41 will produce the fewest number - /// of collissions. See http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/ - /// for more information. - /// - protected const int HashMultiplier = 31; + private readonly HashSet _extraSignatureProperties = new HashSet(StringComparer.OrdinalIgnoreCase); - private readonly List _extraSignatureProperties = new List(); - - /// - /// This static member caches the domain signature properties to avoid looking them up for - /// each instance of the same type. - /// - /// A description of the ThreadStatic attribute may be found at - /// http://www.dotnetjunkies.com/WebLog/chris.taylor/archive/2005/08/18/132026.aspx - /// - [ThreadStatic] - private static IDictionary> s_signatureProperties; + private static readonly ConcurrentDictionary _signaturePropertyNames = new ConcurrentDictionary(); public override bool Equals(object obj) { @@ -53,28 +37,32 @@ public override bool Equals(object obj) /// if at all, in an object's lifetime; it's important that properties are carefully /// selected which truly represent the signature of an object. /// + [SuppressMessage("ReSharper", "BaseObjectGetHashCodeCallInGetHashCode")] + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] public override int GetHashCode() { unchecked { - var signatureProperties = GetSignatureProperties(); + var signatureProperties = GetSignatureProperties().ToArray(); Type t = this.GetType(); - // It's possible for two objects to return the same hash code based on - // identically valued properties, even if they're of two different types, - // so we include the object's type in the hash calculation - int hashCode = t.GetHashCode(); + var combiner = HashCodeCombiner.Start(); - foreach (var pi in signatureProperties) + // It's possible for two objects to return the same hash code based on + // identically valued properties, even if they're of two different types, + // so we include the object's type in the hash calculation + combiner.Add(t.GetHashCode()); + + foreach (var prop in signatureProperties) { - object value = this.GetPropertyValue(pi.Name); // pi.GetValue(this); + var value = prop.GetValue(this); if (value != null) - hashCode = (hashCode * HashMultiplier) ^ value.GetHashCode(); + combiner.Add(value.GetHashCode()); } - if (signatureProperties.Any()) - return hashCode; + if (signatureProperties.Length > 0) + return combiner.CombinedHash; // If no properties were flagged as being part of the signature of the object, // then simply return the hashcode of the base object as the hashcode. @@ -102,8 +90,8 @@ protected virtual bool HasSameSignatureAs(ComparableObject compareTo) foreach (var pi in signatureProperties) { - object thisValue = this.GetPropertyValue(pi.Name); - object thatValue = compareTo.GetPropertyValue(pi.Name); + object thisValue = pi.GetValue(this); + object thatValue = pi.GetValue(compareTo); if (thisValue == null && thatValue == null) continue; @@ -123,56 +111,46 @@ protected virtual bool HasSameSignatureAs(ComparableObject compareTo) /// /// - public IEnumerable GetSignatureProperties() + public IEnumerable GetSignatureProperties() { - IEnumerable properties; - - // Init the signaturePropertiesDictionary here due to reasons described at - // http://blogs.msdn.com/jfoscoding/archive/2006/07/18/670497.aspx - if (s_signatureProperties == null) - s_signatureProperties = new Dictionary>(); - - var t = GetType(); - - if (s_signatureProperties.TryGetValue(t, out properties)) - return properties; - - return (s_signatureProperties[t] = GetSignaturePropertiesCore()); + var type = GetType(); + var propertyNames = GetSignaturePropertyNamesCore(); + + foreach (var name in propertyNames) + { + var fastProperty = FastProperty.GetProperty(type, name); + if (fastProperty != null) + { + yield return fastProperty; + } + } } /// /// Enforces the template method pattern to have child objects determine which specific /// properties should and should not be included in the object signature comparison. /// - protected virtual IEnumerable GetSignaturePropertiesCore() + protected virtual string[] GetSignaturePropertyNamesCore() { - Type t = this.GetType(); - //var properties = TypeDescriptor.GetProvider(t).GetTypeDescriptor(t) - // .GetPropertiesWith(); + Type type = this.GetType(); + string[] names; - //if (_extraSignatureProperties.Count > 0) - //{ - // properties = properties.Union(_extraSignatureProperties); - //} + if (!_signaturePropertyNames.TryGetValue(type, out names)) + { + names = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => Attribute.IsDefined(p, typeof(ObjectSignatureAttribute), true)) + .Select(p => p.Name) + .ToArray(); - //return new PropertyDescriptorCollection(properties.ToArray(), true); + _signaturePropertyNames.TryAdd(type, names); + } - var properties = t.GetProperties() - .Where(p => Attribute.IsDefined(p, typeof(ObjectSignatureAttribute), true)); + if (_extraSignatureProperties.Count == 0) + { + return names; + } - return properties.Union(_extraSignatureProperties).ToList(); - } - - /// - /// Adds an extra property to the type specific signature properties list. - /// - /// The property to add. - /// Both lists are unioned, so - /// that no duplicates can occur within the global descriptor collection. - protected void RegisterSignatureProperty(PropertyInfo propertyInfo) - { - Guard.ArgumentNotNull(() => propertyInfo); - _extraSignatureProperties.Add(propertyInfo); + return names.Union(_extraSignatureProperties).ToArray(); } /// @@ -185,13 +163,7 @@ protected void RegisterSignatureProperty(string propertyName) { Guard.ArgumentNotEmpty(() => propertyName); - Type t = GetType(); - - var pi = t.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance); - if (pi == null) - throw Error.Argument("propertyName", "Could not find property '{0}' on type '{1}'.", propertyName, t); - - RegisterSignatureProperty(pi); + _extraSignatureProperties.Add(propertyName); } } @@ -214,7 +186,7 @@ protected void RegisterSignatureProperty(Expression> expression) { Guard.ArgumentNotNull(() => expression); - base.RegisterSignatureProperty(expression.ExtractPropertyInfo()); + base.RegisterSignatureProperty(expression.ExtractPropertyInfo().Name); } public virtual bool Equals(T other) diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ContextState.cs b/src/Libraries/SmartStore.Core/Infrastructure/ContextState.cs new file mode 100644 index 0000000000..996493f80d --- /dev/null +++ b/src/Libraries/SmartStore.Core/Infrastructure/ContextState.cs @@ -0,0 +1,81 @@ +using System; +using System.Runtime.Remoting.Messaging; +using System.Threading; +using System.Web; + +namespace SmartStore.Core.Infrastructure +{ + /// + /// Holds some state for the current HttpContext or thread + /// + /// The type of data to store + public class ContextState where T : class + { + private readonly string _name; + private readonly Func _defaultValue; + + public ContextState(string name) + { + _name = name; + } + + public ContextState(string name, Func defaultValue) + { + _name = name; + _defaultValue = defaultValue; + } + + public T GetState() + { + if (HttpContext.Current == null) + { + var data = CallContext.GetData(_name); + + if (data == null) + { + if (_defaultValue != null) + { + CallContext.SetData(_name, data = _defaultValue()); + return data as T; + } + } + + return data as T; + } + + if (HttpContext.Current.Items[_name] == null) + { + HttpContext.Current.Items[_name] = _defaultValue == null ? null : _defaultValue(); + } + + return HttpContext.Current.Items[_name] as T; + } + + public void SetState(T state) + { + if (HttpContext.Current == null) + { + CallContext.SetData(_name, state); + } + else + { + HttpContext.Current.Items[_name] = state; + } + } + + public void RemoveState() + { + if (HttpContext.Current == null) + { + CallContext.FreeNamedDataSlot(_name); + } + else + { + if (HttpContext.Current.Items.Contains(_name)) + { + HttpContext.Current.Items.Remove(_name); + } + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DateRange.cs b/src/Libraries/SmartStore.Core/Infrastructure/DateRange.cs deleted file mode 100644 index bb331223be..0000000000 --- a/src/Libraries/SmartStore.Core/Infrastructure/DateRange.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace SmartStore -{ - - public class DateRange : ValueObject, ICloneable - { - - public DateRange() - { - } - - public DateRange(DateTime to) - : this(default(DateTime), to) - { - } - - public DateRange(DateTime from, DateTime to) - { - DateFrom = from; - DateTo = to; - } - - [ObjectSignature] - public DateTime? DateFrom { get; set; } - - [ObjectSignature] - public DateTime? DateTo { get; set; } - - public bool IsDefined - { - get { return DateFrom.HasValue || DateTo.HasValue; } - } - - //public override int GetHashCode() - //{ - // return SystemUtil.GetHashCode(this.DateFrom, this.DateTo); - //} - - //public override bool Equals(object obj) - //{ - // if (ReferenceEquals(null, obj)) return false; - // if (ReferenceEquals(this, obj)) return true; - - // DateRange other = (DateRange)obj; - // return this.DateFrom.Equals(other.DateFrom) && this.DateTo.Equals(other.DateTo); - //} - - #region ICloneable Members - - public DateRange Clone() - { - return new DateRange() - { - DateFrom = this.DateFrom, - DateTo = this.DateTo - }; - } - - object ICloneable.Clone() - { - return this.Clone(); - } - - #endregion - - } - -} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacLifetimeScopeProvider.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacLifetimeScopeProvider.cs deleted file mode 100644 index b307f82e78..0000000000 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacLifetimeScopeProvider.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Web; -using Autofac; -using Autofac.Integration.Mvc; - -namespace SmartStore.Core.Infrastructure.DependencyManagement -{ - public class AutofacLifetimeScopeProvider : ILifetimeScopeProvider - { - private readonly ILifetimeScope _container; - internal static readonly object HttpRequestTag = "AutofacWebRequest"; - - public AutofacLifetimeScopeProvider(ILifetimeScope container) - { - Guard.ArgumentNotNull(() => container); - - this._container = container; - AutofacRequestLifetimeHttpModule.SetLifetimeScopeProvider(this); - - } - - public ILifetimeScope ApplicationContainer - { - get { return _container; } - } - - public void EndLifetimeScope() - { - try - { - ILifetimeScope lifetimeScope = LifetimeScope; - if (lifetimeScope != null) - { - lifetimeScope.Dispose(); - HttpContext.Current.Items.Remove(typeof(ILifetimeScope)); - } - } - catch { } - } - - public ILifetimeScope GetLifetimeScope(Action configurationAction) - { - //little hack here to get dependencies when HttpContext is not available - if (HttpContext.Current != null) - { - return LifetimeScope ?? (LifetimeScope = GetLifetimeScopeCore(configurationAction)); - } - else - { - return GetLifetimeScopeCore(configurationAction); - } - } - - protected virtual ILifetimeScope GetLifetimeScopeCore(Action configurationAction) - { - return (configurationAction == null) - ? _container.BeginLifetimeScope(HttpRequestTag) - : _container.BeginLifetimeScope(HttpRequestTag, configurationAction); - } - - private static ILifetimeScope LifetimeScope - { - get - { - return (ILifetimeScope)HttpContext.Current.Items[typeof(ILifetimeScope)]; - } - set - { - HttpContext.Current.Items[typeof(ILifetimeScope)] = value; - } - } - - - } -} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs index 9f84b3d57b..a3fa432497 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs @@ -1,7 +1,5 @@ using System; -using System.Threading; using System.Web; -using Autofac; using Autofac.Integration.Mvc; namespace SmartStore.Core.Infrastructure.DependencyManagement @@ -12,7 +10,6 @@ namespace SmartStore.Core.Infrastructure.DependencyManagement /// public class AutofacRequestLifetimeHttpModule : IHttpModule { - #region New public void Init(HttpApplication context) { @@ -42,13 +39,12 @@ public static void SetLifetimeScopeProvider(ILifetimeScopeProvider lifetimeScope internal static ILifetimeScopeProvider LifetimeScopeProvider { get; - set; + private set; } public void Dispose() { } - #endregion } } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs index b06cf107b0..f39bbeedeb 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs @@ -1,12 +1,11 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Web; -using System.Threading; +using System.Reflection; using Autofac; using Autofac.Builder; -using Autofac.Integration.Mvc; -using SmartStore.Utilities; +using SmartStore.ComponentModel; using SmartStore.Core.Caching; namespace SmartStore.Core.Infrastructure.DependencyManagement @@ -14,6 +13,7 @@ namespace SmartStore.Core.Infrastructure.DependencyManagement public class ContainerManager { private readonly IContainer _container; + private readonly ConcurrentDictionary _cachedActivators = new ConcurrentDictionary(); public ContainerManager(IContainer container) { @@ -25,9 +25,9 @@ public IContainer Container get { return _container; } } - public T Resolve(string key = "", ILifetimeScope scope = null) where T : class + public T Resolve(object key = null, ILifetimeScope scope = null) where T : class { - if (string.IsNullOrEmpty(key)) + if (key == null) { return (scope ?? Scope()).Resolve(); } @@ -49,9 +49,9 @@ public object ResolveNamed(string name, Type type, ILifetimeScope scope = null) return (scope ?? Scope()).ResolveNamed(name, type); } - public T[] ResolveAll(string key = "", ILifetimeScope scope = null) + public T[] ResolveAll(object key = null, ILifetimeScope scope = null) { - if (string.IsNullOrEmpty(key)) + if (key == null) { return (scope ?? Scope()).Resolve>().ToArray(); } @@ -65,30 +65,67 @@ public T ResolveUnregistered(ILifetimeScope scope = null) where T : class public object ResolveUnregistered(Type type, ILifetimeScope scope = null) { - var constructors = type.GetConstructors(); - foreach (var constructor in constructors) - { - try - { - var parameters = constructor.GetParameters(); - var parameterInstances = new List(); - foreach (var parameter in parameters) - { - var service = Resolve(parameter.ParameterType, scope); - if (service == null) - throw new SmartException("Unkown dependency"); - parameterInstances.Add(service); - } - return Activator.CreateInstance(type, parameterInstances.ToArray()); - } - catch (SmartException) - { + FastActivator activator; + object[] parameterInstances = null; + + if (!_cachedActivators.TryGetValue(type, out activator)) + { + var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + foreach (var constructor in constructors) + { + var parameterTypes = constructor.GetParameters().Select(p => p.ParameterType).ToArray(); + if (TryResolveAll(parameterTypes, out parameterInstances, scope)) + { + activator = new FastActivator(constructor); + _cachedActivators.TryAdd(type, activator); + break; + } + } + } + if (activator != null) + { + if (parameterInstances == null) + { + TryResolveAll(activator.ParameterTypes, out parameterInstances, scope); } - } - throw new SmartException("No contructor was found that had all the dependencies satisfied."); + if (parameterInstances != null) + { + return activator.Activate(parameterInstances); + } + } + + throw new SmartException("No constructor for {0} was found that had all the dependencies satisfied.".FormatInvariant(type.Name.NaIfEmpty())); } + private bool TryResolveAll(Type[] types, out object[] instances, ILifetimeScope scope = null) + { + instances = null; + + try + { + var instances2 = new object[types.Length]; + + for (int i = 0; i < types.Length; i++) + { + var service = Resolve(types[i], scope); + if (service == null) + { + return false; + } + + instances2[i] = service; + } + + instances = instances2; + return true; + } + catch + { + return false; + } + } + public bool TryResolve(Type serviceType, ILifetimeScope scope, out object instance) { return (scope ?? Scope()).TryResolve(serviceType, out instance); @@ -121,21 +158,17 @@ public T InjectUnsetProperties(T instance, ILifetimeScope scope = null) public ILifetimeScope Scope() { - ILifetimeScope scope = null; - try - { - scope = AutofacDependencyResolver.Current.RequestLifetimeScope; - } - catch { } + var scope = _container.Resolve().GetLifetimeScope(null); + return scope ?? _container; + } - if (scope == null) + public ILifetimeScopeAccessor ScopeAccessor + { + get { - // really hackisch. But strange things are going on ?? :-) - scope = _container.BeginLifetimeScope("AutofacWebRequest"); + return _container.Resolve(); } - - return scope ?? _container; - } + } } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs new file mode 100644 index 0000000000..03e0fbbba7 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs @@ -0,0 +1,94 @@ +using System; +using System.Diagnostics; +using System.Web; +using Autofac; +using Autofac.Core.Lifetime; +using SmartStore.Core.Data; +using SmartStore.Core.Logging; +using SmartStore.Utilities; + +namespace SmartStore.Core.Infrastructure.DependencyManagement +{ + public class DefaultLifetimeScopeAccessor : ILifetimeScopeAccessor + { + private ContextState _state; + private readonly ILifetimeScope _rootContainer; + internal static readonly object ScopeTag = "AutofacWebRequest"; + + public DefaultLifetimeScopeAccessor(ILifetimeScope rootContainer) + { + Guard.ArgumentNotNull(() => rootContainer); + + //rootContainer.ChildLifetimeScopeBeginning += OnScopeBeginning; + + this._rootContainer = rootContainer; + this._state = new ContextState("CustomLifetimeScopeProvider.WorkScope"); + } + + public ILifetimeScope ApplicationContainer + { + get { return _rootContainer; } + } + + public IDisposable BeginContextAwareScope() + { + var disposer = HttpContext.Current != null + ? ActionDisposable.Empty + : new ActionDisposable(() => this.EndLifetimeScope()); + + return disposer; + } + + public void EndLifetimeScope() + { + try + { + var scope = _state.GetState(); + if (scope != null) + { + try + { + if (DataSettings.DatabaseIsInstalled()) + scope.Resolve().Flush(); + } + catch { } + + scope.Dispose(); + _state.RemoveState(); + } + } + catch { } + } + + public ILifetimeScope GetLifetimeScope(Action configurationAction) + { + var scope = _state.GetState(); + if (scope == null) + { + _state.SetState((scope = BeginLifetimeScope(configurationAction))); + //scope.CurrentScopeEnding += OnScopeEnding; + } + return scope; + } + + //private void OnScopeBeginning(object sender, LifetimeScopeBeginningEventArgs args) + //{ + // bool isWeb = System.Web.HttpContext.Current != null; + // Debug.WriteLine("Scope Begin, Web: " + isWeb); + //} + + //private void OnScopeEnding(object sender, LifetimeScopeEndingEventArgs args) + //{ + // bool isWeb = System.Web.HttpContext.Current != null; + // Debug.WriteLine("Scope END, Web: " + isWeb); + //} + + private ILifetimeScope BeginLifetimeScope(Action configurationAction) + { + return (configurationAction == null) + ? _rootContainer.BeginLifetimeScope(ScopeTag) + : _rootContainer.BeginLifetimeScope(ScopeTag, configurationAction); + } + + } +} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeProvider.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeProvider.cs new file mode 100644 index 0000000000..9f01c73248 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeProvider.cs @@ -0,0 +1,35 @@ +using System; +using Autofac; +using Autofac.Integration.Mvc; + +namespace SmartStore.Core.Infrastructure.DependencyManagement +{ + public class DefaultLifetimeScopeProvider : ILifetimeScopeProvider + { + private readonly ILifetimeScopeAccessor _accessor; + + public DefaultLifetimeScopeProvider(ILifetimeScopeAccessor accessor) + { + Guard.ArgumentNotNull(() => accessor); + + this._accessor = accessor; + AutofacRequestLifetimeHttpModule.SetLifetimeScopeProvider(this); + } + + public ILifetimeScope ApplicationContainer + { + get { return _accessor.ApplicationContainer; } + } + + public void EndLifetimeScope() + { + _accessor.EndLifetimeScope(); + } + + public ILifetimeScope GetLifetimeScope(Action configurationAction) + { + return _accessor.GetLifetimeScope(configurationAction); + } + + } +} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ILifetimeScopeAccessor.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ILifetimeScopeAccessor.cs new file mode 100644 index 0000000000..03beda80b8 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ILifetimeScopeAccessor.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using Autofac; + +namespace SmartStore.Core.Infrastructure.DependencyManagement +{ + public interface ILifetimeScopeAccessor + { + /// + /// Gets the global, application-wide container. + /// + ILifetimeScope ApplicationContainer { get; } + + /// + /// Ends the current lifetime scope. + /// + void EndLifetimeScope(); + + /// + /// Gets a nested lifetime scope that services can be resolved from + /// + /// A configuration action that will execute during lifetime scope creation. + /// + /// A new or existing nested lifetime scope. + /// + ILifetimeScope GetLifetimeScope(Action configurationAction); + + /// + /// Either creates a new lifetime scope when HttpContext.Current is null, + /// OR returns the current http context scoped lifetime. + /// + /// + /// A disposable object which does nothing when internal lifetime scope is bound to the http context, + /// OR ends the lifetime scope otherwise. + /// + /// + /// This method is intended for usage in background threads or tasks. There may be situations where HttpContext is present, + /// especially when a task was started with TaskScheduler.FromCurrentSynchronizationContext(). In this case it may not be + /// desirable to create a new scope, but use the existing, http context bound scope instead. + /// + IDisposable BeginContextAwareScope(); + } +} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DisposableObject.cs b/src/Libraries/SmartStore.Core/Infrastructure/DisposableObject.cs index 1c7aa25811..e3b01f2e42 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DisposableObject.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DisposableObject.cs @@ -8,18 +8,18 @@ namespace SmartStore public abstract class DisposableObject : IDisposable { - private bool _disposed = false; + private bool _isDisposed; public virtual bool IsDisposed { [DebuggerStepThrough] - get { return _disposed; } + get { return _isDisposed; } } [DebuggerStepThrough] protected void CheckDisposed() { - if (IsDisposed) + if (_isDisposed) { throw Error.ObjectDisposed(GetType().FullName); } @@ -28,7 +28,7 @@ protected void CheckDisposed() [DebuggerStepThrough] protected void CheckDisposed(string errorMessage) { - if (IsDisposed) + if (_isDisposed) { throw Error.ObjectDisposed(GetType().FullName, errorMessage); } @@ -37,17 +37,20 @@ protected void CheckDisposed(string errorMessage) [DebuggerStepThrough] public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); + if (!_isDisposed) + { + Dispose(true); + GC.SuppressFinalize(this); + } } protected void Dispose(bool disposing) { - if (!_disposed) + if (!_isDisposed) { OnDispose(disposing); } - _disposed = true; + _isDisposed = true; } protected abstract void OnDispose(bool disposing); diff --git a/src/Libraries/SmartStore.Core/Infrastructure/EngineContext.cs b/src/Libraries/SmartStore.Core/Infrastructure/EngineContext.cs index e8abc8ee61..aa7c8497ee 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/EngineContext.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/EngineContext.cs @@ -13,14 +13,16 @@ namespace SmartStore.Core.Infrastructure public class EngineContext { #region Initialization Methods + /// Initializes a static instance of the SmartStore factory. /// Creates a new factory instance even though the factory has been previously initialized. + /// A custom implementation of to use instead of the built-in engine [MethodImpl(MethodImplOptions.Synchronized)] - public static IEngine Initialize(bool forceRecreate) + public static IEngine Initialize(bool forceRecreate, IEngine engine = null) { if (Singleton.Instance == null || forceRecreate) { - Singleton.Instance = CreateEngineInstance(); + Singleton.Instance = engine ?? CreateEngineInstance(); Singleton.Instance.Initialize(); } return Singleton.Instance; diff --git a/src/Libraries/SmartStore.Core/Infrastructure/EnumFriendlyNameAttribute.cs b/src/Libraries/SmartStore.Core/Infrastructure/EnumFriendlyNameAttribute.cs deleted file mode 100644 index 1be47733be..0000000000 --- a/src/Libraries/SmartStore.Core/Infrastructure/EnumFriendlyNameAttribute.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace SmartStore -{ - - /// - /// Provides a friendly display name for an enumerated type value. - /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public sealed class EnumFriendlyNameAttribute : Attribute - { - public EnumFriendlyNameAttribute(string friendlyName) - { - FriendlyName = friendlyName; - } - - public string FriendlyName { get; private set; } - } - - /// - /// Provides a description for an enumerated type. - /// - [AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field, AllowMultiple = false)] - public sealed class EnumDescriptionAttribute : Attribute - { - - /// - /// Initializes a new instance of the - /// class. - /// - /// The description to store in this attribute. - /// - public EnumDescriptionAttribute(string description) - { - Description = description; - } - - /// - /// Gets the description stored in this attribute. - /// - /// The description stored in the attribute. - public string Description { get; private set; } - } - -} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/Error.cs b/src/Libraries/SmartStore.Core/Infrastructure/Error.cs index d8752d8169..47f03f6f2e 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/Error.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/Error.cs @@ -2,8 +2,6 @@ using System.Globalization; using System.Diagnostics; -using Fasterflect; - namespace SmartStore { public static class Error @@ -77,7 +75,7 @@ public static Exception Argument(Func arg, string message, params object[] [DebuggerStepThrough] public static Exception InvalidOperation(string message, params object[] args) { - return Error.InvalidOperation(message, null, args); + return InvalidOperation(message, null, args); } [DebuggerStepThrough] diff --git a/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs b/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs index f90ae96153..5a15aba9f3 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; @@ -113,7 +114,7 @@ public static void ArgumentNotEmpty(Func arg) { if (arg().IsEmpty()) { - string argName = GetParamName(arg); + var argName = GetParamName(arg); throw Error.Argument(argName, "String parameter '{0}' cannot be null or all whitespace.", argName); } } @@ -303,7 +304,7 @@ public static void ArgumentNotDisposed(DisposableObject arg, string argName) [DebuggerStepThrough] public static void PagingArgsValid(int indexArg, int sizeArg, string indexArgName, string sizeArgName) { - ArgumentNotNegative(indexArg, indexArgName, "PageIndex cannot be below 0"); + ArgumentNotNegative(indexArg, indexArgName, "PageIndex cannot be below 0"); if (indexArg > 0) { // if pageIndex is specified (> 0), PageSize CANNOT be 0 @@ -317,9 +318,10 @@ public static void PagingArgsValid(int indexArg, int sizeArg, string indexArgNam } [DebuggerStepThrough] + [SuppressMessage("ReSharper", "UnusedMember.Local")] private static string GetParamName(Expression> expression) { - string name = string.Empty; + var name = string.Empty; MemberExpression body = expression.Body as MemberExpression; if (body != null) diff --git a/src/Libraries/SmartStore.Core/Infrastructure/IEngine.cs b/src/Libraries/SmartStore.Core/Infrastructure/IEngine.cs index 7053d65252..43d05ecce3 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/IEngine.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/IEngine.cs @@ -18,7 +18,6 @@ public interface IEngine /// /// Initialize components and plugins in the SmartStore environment. /// - /// Config void Initialize(); T Resolve(string name = null) where T : class; diff --git a/src/Libraries/SmartStore.Core/Infrastructure/Misc.cs b/src/Libraries/SmartStore.Core/Infrastructure/Misc.cs deleted file mode 100644 index c09656ec4b..0000000000 --- a/src/Libraries/SmartStore.Core/Infrastructure/Misc.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Reflection; -using System.Text; -using System.Globalization; -using System.IO; - -namespace SmartStore -{ - - //internal delegate T Creator(); - - internal static class Misc - { - - public static bool TryAction(Func func, out T output) - { - Guard.ArgumentNotNull(() => func); - - try - { - output = func(); - return true; - } - catch - { - output = default(T); - return false; - } - } - - /// - /// Perform an action if the string is not null or empty. - /// - /// The value. - /// The action to perform. - public static void IfNotNullOrEmpty(string value, Action action) - { - IfNotNullOrEmpty(value, action, null); - } - - private static void IfNotNullOrEmpty(string value, Action trueAction, Action falseAction) - { - if (!string.IsNullOrEmpty(value)) - { - if (trueAction != null) - trueAction(value); - } - else - { - if (falseAction != null) - falseAction(value); - } - } - - } -} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ObjectSignatureAttribute.cs b/src/Libraries/SmartStore.Core/Infrastructure/ObjectSignatureAttribute.cs index 3095534f82..42b53f80c4 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/ObjectSignatureAttribute.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/ObjectSignatureAttribute.cs @@ -1,12 +1,9 @@ using System; - namespace SmartStore -{ - +{ [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class ObjectSignatureAttribute : Attribute { } - } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs b/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs index a946ef9a53..a9143bed56 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs @@ -4,7 +4,6 @@ namespace SmartStore { - public static class RegularExpressions { @@ -19,10 +18,10 @@ public static class RegularExpressions public static readonly Regex IsNotNumber = new Regex("[^0-9.-]", RegexOptions.Compiled); 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); - // insufficient, fails on valid email first+second-test01@domain.com - //public static readonly Regex IsEmail = new Regex(@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$", 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); @@ -31,7 +30,7 @@ public static class RegularExpressions 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); - public static readonly Regex IsCultureCode = new Regex(@"^([a-z]{2})|([a-z]{2}-[A-Z]{2})$", RegexOptions.Singleline | RegexOptions.Compiled); + public static readonly Regex IsCultureCode = new Regex(@"^[a-z]{2}(-[A-Z]{2})?$", RegexOptions.Singleline | RegexOptions.Compiled); public static readonly Regex IsYearRange = new Regex(@"^(\d{4})-(\d{4})$", RegexOptions.Compiled); diff --git a/src/Libraries/SmartStore.Core/Infrastructure/SmartStoreEngine.cs b/src/Libraries/SmartStore.Core/Infrastructure/SmartStoreEngine.cs index dccbbcb958..accd095b46 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/SmartStoreEngine.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/SmartStoreEngine.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Configuration; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using System.Web.Mvc; using Autofac; using Autofac.Integration.Mvc; -using SmartStore.Core.Configuration; using SmartStore.Core.Data; using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.Core.Plugins; @@ -39,19 +36,42 @@ protected virtual void RunStartupTasks() } } - protected virtual void RegisterDependencies() + protected virtual ITypeFinder CreateTypeFinder() + { + return new WebAppTypeFinder(); + } + + protected virtual object CreateDependencyResolver(IContainer container) + { + var scopeProvider = container.Resolve(); + var dependencyResolver = new AutofacDependencyResolver(container, scopeProvider); + return dependencyResolver; + } + + protected virtual ContainerManager RegisterDependencies() { var builder = new ContainerBuilder(); var container = builder.Build(); + var typeFinder = CreateTypeFinder(); // core dependencies builder = new ContainerBuilder(); - builder.RegisterInstance(this).As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); + builder.RegisterInstance(this).As(); + builder.RegisterInstance(typeFinder).As(); + + // Autofac + var lifetimeScopeAccessor = new DefaultLifetimeScopeAccessor(container); + var lifetimeScopeProvider = new DefaultLifetimeScopeProvider(lifetimeScopeAccessor); + builder.RegisterInstance(lifetimeScopeAccessor).As(); + builder.RegisterInstance(lifetimeScopeProvider).As(); + + var dependencyResolver = new AutofacDependencyResolver(container, lifetimeScopeProvider); + builder.RegisterInstance(dependencyResolver); + DependencyResolver.SetResolver(dependencyResolver); + builder.Update(container); // register dependencies provided by other assemblies - var typeFinder = container.Resolve(); builder = new ContainerBuilder(); var registrarTypes = typeFinder.FindClassesOfType(); var registrarInstances = new List(); @@ -67,12 +87,7 @@ protected virtual void RegisterDependencies() } builder.Update(container); - // AutofacDependencyResolver - var scopeProvider = new AutofacLifetimeScopeProvider(container); - var dependencyResolver = new AutofacDependencyResolver(container, scopeProvider); - DependencyResolver.SetResolver(dependencyResolver); - - _containerManager = new ContainerManager(container); + return new ContainerManager(container); } #endregion @@ -84,7 +99,7 @@ protected virtual void RegisterDependencies() /// public void Initialize() { - RegisterDependencies(); + _containerManager = RegisterDependencies(); if (DataSettings.DatabaseIsInstalled()) { RunStartupTasks(); diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ValueObject.cs b/src/Libraries/SmartStore.Core/Infrastructure/ValueObject.cs deleted file mode 100644 index 066c47f810..0000000000 --- a/src/Libraries/SmartStore.Core/Infrastructure/ValueObject.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Reflection; -using Fasterflect; - -namespace SmartStore -{ - - /// - /// Base class for complex value objects, which do not have - /// identifiers. - /// - /// Type of the entity, which is the value obect. - public abstract class ValueObject : ComparableObject> - where T : ValueObject - { - - /// - /// Registers all properties of the subclass as signature properties. - /// - protected void RegisterProperties() - { - RegisterProperties((pd) => true); - } - - /// - /// Registers any properties of the subclass matching - /// the given as signature properties. - /// - protected void RegisterProperties(Func filter) - { - foreach (var pi in this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)) - { - if (filter(pi)) - RegisterSignatureProperty(pi); - } - } - - public override bool Equals(object obj) - { - return base.Equals(obj); - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - - public override string ToString() - { - var sb = new StringBuilder(); - var properties = GetSignatureProperties(); - int propsCount = properties.Count(); - - int i = 1; - - foreach (var pi in properties) - { - object value = this.GetPropertyValue(pi.Name); - if (value == null) - continue; - - sb.Append(pi.Name + ": " + value.ToString()); - if (i < propsCount) - sb.Append(", "); - - i++; - } - - return sb.ToString(); - } - - public static bool operator ==(ValueObject x, ValueObject y) - { - if ((object)x == null) - return (object)y == null; - - return x.Equals(y); - } - - public static bool operator !=(ValueObject x, ValueObject y) - { - return !(x == y); - } - - } - -} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs b/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs index c57861d90a..90dc70012f 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs @@ -15,8 +15,8 @@ public class WebAppTypeFinder : AppDomainTypeFinder { #region Fields - private bool _ensureBinFolderAssembliesLoaded = true; - private bool _binFolderAssembliesLoaded = false; + private bool _ensureBinFolderAssembliesLoaded; + private bool _binFolderAssembliesLoaded; #endregion @@ -63,14 +63,12 @@ public virtual string GetBinDirectory() } } - public override IList GetAssemblies(bool ignoreInactivePlugins = false) { if (this.EnsureBinFolderAssembliesLoaded && !_binFolderAssembliesLoaded) { _binFolderAssembliesLoaded = true; string binPath = GetBinDirectory(); - //binPath = _webHelper.MapPath("~/bin"); LoadMatchingAssemblies(binPath); } diff --git a/src/Libraries/SmartStore.Core/Linq/Expanders/LambdaPathExpander.cs b/src/Libraries/SmartStore.Core/Linq/Expanders/LambdaPathExpander.cs index 185b8a946e..4c7c1ec45d 100644 --- a/src/Libraries/SmartStore.Core/Linq/Expanders/LambdaPathExpander.cs +++ b/src/Libraries/SmartStore.Core/Linq/Expanders/LambdaPathExpander.cs @@ -57,16 +57,14 @@ public virtual void Expand(Type type, string path) var tokenizer = new StringTokenizer(path, ".", false); foreach (string member in tokenizer) { - // Property oder Field des Members ermitteln - MemberInfo prop = t.GetFieldOrProperty(member, true); - //MemberInfo prop = t.GetProperty(member); + // Property ermitteln + //MemberInfo prop = t.GetFieldOrProperty(member, true); + var prop = t.GetProperty(member, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance); if (prop == null) throw new ArgumentException("The property or member '{0}' does not exist in type '{1}'.".FormatInvariant(member, t.FullName)); - Type memberType = (prop.MemberType == MemberTypes.Property) - ? ((PropertyInfo)prop).PropertyType - : ((FieldInfo)prop).FieldType; + Type memberType = prop.PropertyType; DoExpand(t, member); diff --git a/src/Libraries/SmartStore.Core/Linq/Expressions/ExpressionVisitor.cs b/src/Libraries/SmartStore.Core/Linq/Expressions/ExpressionVisitor.cs index b1a66d8885..63cf7761fd 100644 --- a/src/Libraries/SmartStore.Core/Linq/Expressions/ExpressionVisitor.cs +++ b/src/Libraries/SmartStore.Core/Linq/Expressions/ExpressionVisitor.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; @@ -21,6 +22,7 @@ namespace SmartStore.Linq.Expressions /// for anyone outside of Microsoft to be using... /// [DebuggerStepThrough, DebuggerNonUserCode] + [SuppressMessage("ReSharper", "PossibleUnintendedReferenceComparison")] public abstract class ExpressionVisitor { /// diff --git a/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs b/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs new file mode 100644 index 0000000000..ed9beed2ad --- /dev/null +++ b/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace SmartStore.Core.Localization +{ + public static class LocalizationHelper + { + private readonly static HashSet _cultureCodes = + new HashSet( + CultureInfo.GetCultures(CultureTypes.NeutralCultures | CultureTypes.SpecificCultures | CultureTypes.UserCustomCulture) + .Select(x => x.IetfLanguageTag) + .Where(x => !string.IsNullOrWhiteSpace(x))); + + public static bool IsValidCultureCode(string cultureCode) + { + return _cultureCodes.Contains(cultureCode); + } + + public static string GetLanguageNativeName(string locale) + { + try + { + if (locale.HasValue()) + { + var info = CultureInfo.GetCultureInfoByIetfLanguageTag(locale); + if (info != null) + return info.NativeName; + } + } + catch { } + + return null; + } + + public static string GetCurrencySymbol(string locale) + { + try + { + if (locale.HasValue()) + { + var info = new RegionInfo(locale); + if (info != null) + return info.CurrencySymbol; + } + } + catch { } + + return null; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Logging/ICustomerActivityService.cs b/src/Libraries/SmartStore.Core/Logging/ICustomerActivityService.cs index 0c32d85236..3ff8526a96 100644 --- a/src/Libraries/SmartStore.Core/Logging/ICustomerActivityService.cs +++ b/src/Libraries/SmartStore.Core/Logging/ICustomerActivityService.cs @@ -1,15 +1,14 @@ using System; using System.Collections.Generic; -using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Logging; namespace SmartStore.Core.Logging { - /// - /// Customer activity service interface - /// - public partial interface ICustomerActivityService + /// + /// Customer activity service interface + /// + public partial interface ICustomerActivityService { /// /// Inserts an activity log type item @@ -75,19 +74,27 @@ ActivityLog InsertActivity(string systemKeyword, /// Activity log void DeleteActivity(ActivityLog activityLog); - /// - /// Gets all activity log items - /// - /// Log item creation from; null to load all customers - /// Log item creation to; null to load all customers - /// Customer identifier; null to load all customers - /// Activity log type identifier - /// Page index - /// Page size - /// Activity log collection - IPagedList GetAllActivities(DateTime? createdOnFrom, - DateTime? createdOnTo, int? customerId, - int activityLogTypeId, int pageIndex, int pageSize); + /// + /// Gets all activity log items + /// + /// Log item creation from; null to load all customers + /// Log item creation to; null to load all customers + /// Customer identifier; null to load all customers + /// Activity log type identifier + /// Page index + /// Page size + /// Customer email + /// Customer system name + /// Activity log collection + IPagedList GetAllActivities( + DateTime? createdOnFrom, + DateTime? createdOnTo, + int? customerId, + int activityLogTypeId, + int pageIndex, + int pageSize, + string email = null, + bool? customerSystemAccount = null); /// /// Gets an activity log item @@ -96,9 +103,16 @@ IPagedList GetAllActivities(DateTime? createdOnFrom, /// Activity log item ActivityLog GetActivityById(int activityLogId); - /// - /// Clears activity log - /// - void ClearAllActivities(); + /// + /// Gets activity logs be identifier + /// + /// Activity log identifiers + /// List of activity logs + IList GetActivityByIds(int[] activityLogIds); + + /// + /// Clears activity log + /// + void ClearAllActivities(); } } diff --git a/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs b/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs index 86c8f4400e..fb8a10be35 100644 --- a/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs +++ b/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs @@ -9,38 +9,61 @@ public static class LoggingExtensions public static void Debug(this ILogger logger, string message, Exception exception = null, Customer customer = null) { - FilteredLog(logger, LogLevel.Debug, message, exception, customer); + FilteredLog(logger, LogLevel.Debug, message, null, exception, customer); } public static void Information(this ILogger logger, string message, Exception exception = null, Customer customer = null) { - FilteredLog(logger, LogLevel.Information, message, exception, customer); + 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, exception, customer); + 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, exception, customer); + FilteredLog(logger, LogLevel.Error, message, null, exception, customer); } - public static void Fatal(this ILogger logger, string message, Exception exception = null, Customer customer = null) + 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 Fatal(this ILogger logger, string message, Exception exception = null, Customer customer = null) { - FilteredLog(logger, LogLevel.Fatal, message, exception, customer); + FilteredLog(logger, LogLevel.Fatal, message, null, exception, customer); } - private static void FilteredLog(ILogger logger, LogLevel level, string message, Exception exception = null, Customer customer = null) + public static void Error(this ILogger logger, Exception exception, Customer customer = null) + { + FilteredLog(logger, LogLevel.Error, exception.ToAllMessages(), null, exception, customer); + } + + public static void ErrorsAll(this ILogger logger, Exception exception, Customer customer = null) + { + while (exception != null) + { + FilteredLog(logger, LogLevel.Error, exception.Message, exception.StackTrace, exception, customer); + 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 + // don't log thread abort exception if ((exception != null) && (exception is System.Threading.ThreadAbortException)) return; if (logger.IsEnabled(level)) { - string fullMessage = exception == null ? string.Empty : exception.ToString(); + if (exception != null && fullMessage.IsEmpty()) + { + fullMessage = "{0}\n{1}\n{2}".FormatCurrent(exception.Message, new String('-', 20), exception.StackTrace); + } + logger.InsertLog(level, message, fullMessage, customer); } } diff --git a/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs index 8bf32d705b..593a39ddb0 100644 --- a/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs +++ b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs @@ -1,18 +1,20 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Diagnostics; +using System.IO; using System.Text; -using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Customers; -using System.Diagnostics; +using SmartStore.Core.Domain.Logging; +using SmartStore.Utilities; namespace SmartStore.Core.Logging { public class TraceLogger : DisposableObject, ILogger { private readonly TraceSource _traceSource; + private readonly StreamWriter _streamWriter; - public TraceLogger() : this("SmartStore.log") + public TraceLogger() : this(CommonHelper.MapPath("~/App_Data/SmartStore.log")) { } @@ -23,23 +25,37 @@ public TraceLogger(string fileName) _traceSource = new TraceSource("SmartStore"); _traceSource.Switch = new SourceSwitch("LogSwitch", "Error"); _traceSource.Listeners.Remove("Default"); - + var console = new ConsoleTraceListener(false); console.Filter = new EventTypeFilter(SourceLevels.All); console.Name = "console"; + _traceSource.Listeners.Add(console); + var textListener = new TextWriterTraceListener(fileName); textListener.Filter = new EventTypeFilter(SourceLevels.All); textListener.TraceOutputOptions = TraceOptions.DateTime; - _traceSource.Listeners.Add(console); - _traceSource.Listeners.Add(textListener); - - // Allow the trace source to send messages to - // listeners for all event types. Currently only + try + { + // force UTF-8 encoding (even if the text just contains ANSI characters) + var append = File.Exists(fileName); + _streamWriter = new StreamWriter(fileName, append, Encoding.UTF8); + + textListener.Writer = _streamWriter; + + _traceSource.Listeners.Add(textListener); + } + catch (IOException) + { + // file is locked by another process + } + + // Allow the trace source to send messages to + // listeners for all event types. Currently only // error messages or higher go to the listeners. - // Messages must get past the source switch to - // get to the listeners, regardless of the settings + // Messages must get past the source switch to + // get to the listeners, regardless of the settings // for the listeners. _traceSource.Switch.Level = SourceLevels.All; } @@ -85,12 +101,17 @@ public IList GetLogByIds(int[] logIds) public void InsertLog(LogContext context) { var type = LogLevelToEventType(context.LogLevel); - _traceSource.TraceEvent(type, (int)type, "{0}: {1}".FormatCurrent(type.ToString().ToUpper(), context.ShortMessage)); + var msg = context.ShortMessage.Grow(context.FullMessage, Environment.NewLine); + + if (msg.HasValue()) + { + _traceSource.TraceEvent(type, (int)type, "{0}: {1}".FormatCurrent(type.ToString().ToUpper(), msg)); + } } public void InsertLog(LogLevel logLevel, string shortMessage, string fullMessage = "", Customer customer = null) { - var context = new LogContext() + var context = new LogContext { LogLevel = logLevel, ShortMessage = shortMessage, @@ -120,12 +141,19 @@ private TraceEventType LogLevelToEventType(LogLevel level) public void Flush() { + _traceSource.Flush(); } protected override void OnDispose(bool disposing) { _traceSource.Flush(); _traceSource.Close(); + + if (_streamWriter != null) + { + _streamWriter.Close(); + _streamWriter.Dispose(); + } } } } diff --git a/src/Libraries/SmartStore.Core/Packaging/FolderUpdater.cs b/src/Libraries/SmartStore.Core/Packaging/FolderUpdater.cs index 314eb5d61c..d02cc71cce 100644 --- a/src/Libraries/SmartStore.Core/Packaging/FolderUpdater.cs +++ b/src/Libraries/SmartStore.Core/Packaging/FolderUpdater.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.IO; using SmartStore.Core.Logging; @@ -14,6 +15,7 @@ public interface IFolderUpdater void Restore(DirectoryInfo backupfolder, DirectoryInfo existingFolder); } + [SuppressMessage("ReSharper", "NotAccessedField.Local")] public class FolderUpdater : IFolderUpdater { public class FolderContent @@ -83,6 +85,7 @@ private FolderContent GetFolderContent(DirectoryInfo folder, IEnumerable files, IEnumerable ignores) { if (!folder.Exists) diff --git a/src/Libraries/SmartStore.Core/Packaging/NuGet/ExtensionReferenceRepository.cs b/src/Libraries/SmartStore.Core/Packaging/NuGet/ExtensionReferenceRepository.cs index e010cc1507..f5ca4be0fe 100644 --- a/src/Libraries/SmartStore.Core/Packaging/NuGet/ExtensionReferenceRepository.cs +++ b/src/Libraries/SmartStore.Core/Packaging/NuGet/ExtensionReferenceRepository.cs @@ -53,14 +53,12 @@ public override bool SupportsPrereleasePackages /// internal class PluginReferenceRepository : ExtensionReferenceRepository { - private readonly IPluginFinder _pluginFinder; private readonly IList _descriptors; public PluginReferenceRepository(IProjectSystem project, IPackageRepository sourceRepository, IPluginFinder pluginFinder) : base(project, sourceRepository) { - _pluginFinder = pluginFinder; - _descriptors = _pluginFinder.GetPluginDescriptors().ToList(); + _descriptors = pluginFinder.GetPluginDescriptors().ToList(); } public override IQueryable GetPackages() @@ -83,14 +81,12 @@ public override IQueryable GetPackages() /// internal class ThemeReferenceRepository : ExtensionReferenceRepository { - private readonly IThemeRegistry _themeRegistry; private readonly ICollection _themeManifests; public ThemeReferenceRepository(IProjectSystem project, IPackageRepository sourceRepository, IThemeRegistry themeRegistry) : base(project, sourceRepository) { - _themeRegistry = themeRegistry; - _themeManifests = _themeRegistry.GetThemeManifests(true); + _themeManifests = themeRegistry.GetThemeManifests(true); } public override IQueryable GetPackages() diff --git a/src/Libraries/SmartStore.Core/Packaging/NuGet/FileBasedProjectSystem.cs b/src/Libraries/SmartStore.Core/Packaging/NuGet/FileBasedProjectSystem.cs index f6294a4e68..a32cae4e91 100644 --- a/src/Libraries/SmartStore.Core/Packaging/NuGet/FileBasedProjectSystem.cs +++ b/src/Libraries/SmartStore.Core/Packaging/NuGet/FileBasedProjectSystem.cs @@ -8,7 +8,7 @@ namespace SmartStore.Core.Packaging { - internal class FileBasedProjectSystem : PhysicalFileSystem, IProjectSystem, IFileSystem + internal class FileBasedProjectSystem : PhysicalFileSystem, IProjectSystem { public FileBasedProjectSystem(string root) diff --git a/src/Libraries/SmartStore.Core/Packaging/NuGet/NullSourceRepository.cs b/src/Libraries/SmartStore.Core/Packaging/NuGet/NullSourceRepository.cs index 95cd8ca76b..5d98420378 100644 --- a/src/Libraries/SmartStore.Core/Packaging/NuGet/NullSourceRepository.cs +++ b/src/Libraries/SmartStore.Core/Packaging/NuGet/NullSourceRepository.cs @@ -12,10 +12,6 @@ namespace SmartStore.Core.Packaging /// internal class NullSourceRepository : PackageRepositoryBase { - public NullSourceRepository() - { - } - public override IQueryable GetPackages() { return Enumerable.Empty().AsQueryable(); diff --git a/src/Libraries/SmartStore.Core/Packaging/PackageBuilder.cs b/src/Libraries/SmartStore.Core/Packaging/PackageBuilder.cs index 53e322fe59..ee78bbc8fd 100644 --- a/src/Libraries/SmartStore.Core/Packaging/PackageBuilder.cs +++ b/src/Libraries/SmartStore.Core/Packaging/PackageBuilder.cs @@ -18,12 +18,10 @@ namespace SmartStore.Core.Packaging public class PackageBuilder : IPackageBuilder { private readonly IWebSiteFolder _webSiteFolder; - private readonly IVirtualPathProvider _virtualPathProvider; - public PackageBuilder(IWebSiteFolder webSiteFolder, IVirtualPathProvider virtualPathProvider) + public PackageBuilder(IWebSiteFolder webSiteFolder) { this._webSiteFolder = webSiteFolder; - this._virtualPathProvider = virtualPathProvider; } private static readonly string[] _ignoredThemeExtensions = new[] { @@ -38,18 +36,18 @@ private static bool IgnoreFile(string filePath) { return String.IsNullOrEmpty(filePath) || _ignoredThemePaths.Any(filePath.Contains) || - _ignoredThemeExtensions.Contains(Path.GetExtension(filePath) ?? ""); + _ignoredThemeExtensions.Contains(Path.GetExtension(filePath).NullEmpty() ?? ""); } public Stream BuildPackage(PluginDescriptor pluginDescriptor) { - return BuildPackage(PackagingUtils.ConvertToExtensionDescriptor(pluginDescriptor)); + return BuildPackage(pluginDescriptor.ConvertToExtensionDescriptor()); } public Stream BuildPackage(ThemeManifest themeManifest) { - return BuildPackage(PackagingUtils.ConvertToExtensionDescriptor(themeManifest)); + return BuildPackage(themeManifest.ConvertToExtensionDescriptor()); } private Stream BuildPackage(ExtensionDescriptor extensionDescriptor) @@ -58,7 +56,7 @@ private Stream BuildPackage(ExtensionDescriptor extensionDescriptor) BeginPackage(context); try { - EstablishPaths(context, _webSiteFolder, extensionDescriptor.Location, extensionDescriptor.Id, extensionDescriptor.ExtensionType); + EstablishPaths(context, _webSiteFolder, extensionDescriptor.Id, extensionDescriptor.ExtensionType); SetCoreProperties(context, extensionDescriptor); EmbedFiles(context); } @@ -100,7 +98,7 @@ private static void SetCoreProperties(BuildContext context, ExtensionDescriptor } } - private static void EstablishPaths(BuildContext context, IWebSiteFolder webSiteFolder, string locationPath, string extensionName, string extensionType = "Plugin") + private static void EstablishPaths(BuildContext context, IWebSiteFolder webSiteFolder, string extensionName, string extensionType = "Plugin") { context.SourceFolder = webSiteFolder; if (extensionType.IsCaseInsensitiveEqual("theme")) @@ -129,11 +127,11 @@ private static void EmbedFiles(BuildContext context) // 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, ""); - EmbedVirtualFile(context, relativePath, MediaTypeNames.Application.Octet); + EmbedVirtualFile(context, relativePath); } } - private static void EmbedVirtualFile(BuildContext context, string relativePath, string contentType) + private static void EmbedVirtualFile(BuildContext context, string relativePath) { var file = new VirtualPackageFile( context.SourceFolder, @@ -153,8 +151,6 @@ private class BuildContext public IWebSiteFolder SourceFolder { get; set; } public string SourcePath { get; set; } public string TargetPath { get; set; } - - public XDocument Project { get; set; } } #endregion diff --git a/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs b/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs index 5039a7b331..5a0664ef51 100644 --- a/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs +++ b/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using NuGet; @@ -62,7 +63,7 @@ public PackageInfo Install(Stream packageStream, string location, string applica { Guard.ArgumentNotNull(() => packageStream); - IPackage package = null; + IPackage package; try { package = new ZipPackage(packageStream); @@ -215,6 +216,7 @@ public void Uninstall(string packageId, string applicationFolder) } } + [SuppressMessage("ReSharper", "UnusedMethodReturnValue.Local")] private bool RestoreExtensionFolder(string extensionFolder, string extensionId) { var virtualSource = _virtualPathProvider.Combine("~", extensionFolder, extensionId); diff --git a/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs b/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs index 71ff369f40..f54398153e 100644 --- a/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs +++ b/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading; @@ -17,16 +18,34 @@ namespace SmartStore.Core.Packaging { - internal sealed class AppUpdater : DisposableObject + public sealed class AppUpdater : DisposableObject { - private const string UpdatePackagePath = "~/App_Data/Update"; + public const string UpdatePackagePath = "~/App_Data/Update"; private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); private TraceLogger _logger; #region Package update - public bool TryUpdateFromPackage() + [SuppressMessage("ReSharper", "RedundantAssignment")] + public bool InstallablePackageExists() + { + string packagePath = null; + var package = FindPackage(false, out packagePath); + + if (package == null) + return false; + + if (!ValidatePackage(package)) + return false; + + if (!CheckEnvironment()) + return false; + + return true; + } + + internal bool TryUpdateFromPackage() { // NEVER EVER (!!!) make an attempt to auto-update in a dev environment!!!!!!! if (CommonHelper.IsDevEnvironment) @@ -37,7 +56,7 @@ public bool TryUpdateFromPackage() try { string packagePath = null; - var package = FindPackage(out packagePath); + var package = FindPackage(true, out packagePath); if (package == null) return false; @@ -84,7 +103,7 @@ private TraceLogger CreateLogger(IPackage package) return new TraceLogger(logFile); } - private IPackage FindPackage(out string path) + private IPackage FindPackage(bool createLogger, out string path) { path = null; var dir = CommonHelper.MapPath(UpdatePackagePath, false); @@ -95,17 +114,18 @@ private IPackage FindPackage(out string path) var files = Directory.GetFiles(dir, "SmartStore.*.nupkg", SearchOption.TopDirectoryOnly); // TODO: allow more than one package in folder and return newest - if (files == null || files.Length == 0 || files.Length > 1) + if (files.Length == 0 || files.Length > 1) return null; - IPackage package = null; - try { path = files[0]; - package = new ZipPackage(files[0]); - _logger = CreateLogger(package); - _logger.Information("Found update package '{0}'".FormatInvariant(package.GetFullName())); + IPackage package = new ZipPackage(files[0]); + if (createLogger) + { + _logger = CreateLogger(package); + _logger.Information("Found update package '{0}'".FormatInvariant(package.GetFullName())); + } return package; } catch { } @@ -198,7 +218,7 @@ private PackageInfo ExecuteUpdate(IPackage package) #region Migrations - public void ExecuteMigrations() + internal void ExecuteMigrations() { if (!DataSettings.DatabaseIsInstalled()) return; diff --git a/src/Libraries/SmartStore.Core/PagedList.cs b/src/Libraries/SmartStore.Core/PagedList.cs index fe76c66b32..4f4689496e 100644 --- a/src/Libraries/SmartStore.Core/PagedList.cs +++ b/src/Libraries/SmartStore.Core/PagedList.cs @@ -4,8 +4,6 @@ namespace SmartStore.Core { - - // codehint: sm-add (whole file) public abstract class PagedListBase : IPageable { @@ -33,10 +31,10 @@ protected PagedListBase(int pageIndex, int pageSize, int totalItemsCount) // only here for compat reasons with nc public void LoadPagedList(IPagedList pagedList) { - this.Init(pagedList as IPageable); + this.Init(pagedList); } - public virtual void Init(IPageable pageable) + public void Init(IPageable pageable) { Guard.ArgumentNotNull(pageable, "pageable"); @@ -79,7 +77,10 @@ public int TotalPages { get { - var total = this.TotalCount / this.PageSize; + if (this.PageSize == 0) + return 0; + + var total = this.TotalCount / this.PageSize; if (this.TotalCount % this.PageSize > 0) total++; @@ -145,7 +146,8 @@ public virtual IEnumerator GetEnumerator() public class PagedList : PagedListBase { - public PagedList(int pageIndex, int pageSize, int totalItemsCount) : base(pageIndex, pageSize, totalItemsCount) + public PagedList(int pageIndex, int pageSize, int totalItemsCount) + : base(pageIndex, pageSize, totalItemsCount) { } } diff --git a/src/Libraries/SmartStore.Core/PagedList`T.cs b/src/Libraries/SmartStore.Core/PagedList`T.cs index 63db21a70d..af5039d810 100644 --- a/src/Libraries/SmartStore.Core/PagedList`T.cs +++ b/src/Libraries/SmartStore.Core/PagedList`T.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Data.Entity; +using System.Data.Entity.Infrastructure; namespace SmartStore.Core { @@ -19,7 +21,25 @@ public class PagedList : List, IPagedList public PagedList(IQueryable source, int pageIndex, int pageSize) { Guard.ArgumentNotNull(source, "source"); - Init(source.Skip(pageIndex * pageSize).Take(pageSize), pageIndex, pageSize, source.Count()); + + if (pageIndex == 0 && pageSize == int.MaxValue) + { + // avoid unnecessary SQL + Init(source, pageIndex, pageSize, source.Count()); + } + else + { + if (source.Provider is IDbAsyncQueryProvider) + { + // 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()); + } + else + { + Init(source.Skip(pageIndex * pageSize).Take(pageSize), pageIndex, pageSize, source.Count()); + } + } } /// @@ -31,6 +51,7 @@ public PagedList(IQueryable source, int pageIndex, int pageSize) public PagedList(IList source, int pageIndex, int pageSize) { Guard.ArgumentNotNull(source, "source"); + Init(source.Skip(pageIndex * pageSize).Take(pageSize), pageIndex, pageSize, source.Count); } @@ -43,12 +64,10 @@ public PagedList(IList source, int pageIndex, int pageSize) /// Total count public PagedList(IEnumerable source, int pageIndex, int pageSize, int totalCount) { - // codehint: sm-edit Guard.ArgumentNotNull(source, "source"); Init(source, pageIndex, pageSize, totalCount); } - // codehint: sm-add private void Init(IEnumerable source, int pageIndex, int pageSize, int totalCount) { Guard.PagingArgsValid(pageIndex, pageSize,"pageIndex", "pageSize"); diff --git a/src/Libraries/SmartStore.Core/Plugins/BasePlugin.cs b/src/Libraries/SmartStore.Core/Plugins/BasePlugin.cs index 16c8e45a85..80ae85fd5a 100644 --- a/src/Libraries/SmartStore.Core/Plugins/BasePlugin.cs +++ b/src/Libraries/SmartStore.Core/Plugins/BasePlugin.cs @@ -2,10 +2,6 @@ { public abstract class BasePlugin : IPlugin { - protected BasePlugin() - { - } - /// /// Gets or sets the plugin descriptor /// diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs b/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs index 235c4756ce..0409454f60 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; using System.Xml; @@ -52,7 +53,7 @@ public string BrandImageFileName if (_brandImageFileName == null) { // "null" means we haven't checked yet! - var filesToCheck = new string[] { "branding.png", "branding.gif", "branding.jpg", "branding.jpeg" }; + var filesToCheck = new [] { "branding.png", "branding.gif", "branding.jpg", "branding.jpeg" }; var dir = this.PhysicalPath; foreach (var file in filesToCheck) { @@ -234,7 +235,8 @@ public IPlugin Instance() return Instance(); } - public int CompareTo(PluginDescriptor other) + [SuppressMessage("ReSharper", "StringCompareToIsCultureSpecific")] + public int CompareTo(PluginDescriptor other) { if (DisplayOrder != other.DisplayOrder) return DisplayOrder.CompareTo(other.DisplayOrder); diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs index dfa85d8ef7..7fd04123aa 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs @@ -13,7 +13,7 @@ using System.Web.Hosting; using Microsoft.Web.Infrastructure; using Microsoft.Web.Infrastructure.DynamicModuleHelper; -using SmartStore.Core.ComponentModel; +using SmartStore.ComponentModel; using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.Core.Plugins; using SmartStore.Core.Packaging; @@ -178,7 +178,6 @@ public static void Initialize() } IncompatiblePlugins = incompatiblePlugins.AsReadOnly(); - } } diff --git a/src/Libraries/SmartStore.Core/Plugins/Providers/IsHiddenAttribute.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/IsHiddenAttribute.cs new file mode 100644 index 0000000000..6ba32b9d5a --- /dev/null +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/IsHiddenAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace SmartStore.Core.Plugins +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class IsHiddenAttribute : Attribute + { + public IsHiddenAttribute(bool isHidden) + { + IsHidden = isHidden; + } + + public bool IsHidden { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs index c07d51555c..d07a628170 100644 --- a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using SmartStore.Core.Domain.DataExchange; namespace SmartStore.Core.Plugins { @@ -67,6 +68,16 @@ public class ProviderMetadata /// public bool IsEditable { get; set; } + /// + /// Gets or sets a value indicating whether the provider is hidden (by decorating with ) + /// + public bool IsHidden { get; set; } + + /// + /// Gets or sets flags that reflects what features of export data processing is supported by a provider + /// + public ExportFeatures ExportFeatures { get; set; } + /// /// Gets or sets an array of widget system names, which depend on the current provider /// diff --git a/src/Libraries/SmartStore.Core/RouteInfo.cs b/src/Libraries/SmartStore.Core/RouteInfo.cs index af8c5c7a18..b6856d1a0f 100644 --- a/src/Libraries/SmartStore.Core/RouteInfo.cs +++ b/src/Libraries/SmartStore.Core/RouteInfo.cs @@ -5,7 +5,6 @@ namespace SmartStore { - public class RouteInfo { public RouteInfo(RouteInfo cloneFrom) @@ -14,22 +13,35 @@ public RouteInfo(RouteInfo cloneFrom) Guard.ArgumentNotNull(() => cloneFrom); } - public RouteInfo(string action, string controller, object routeValues) + public RouteInfo(string action, object routeValues) + : this(action, null, routeValues) + { + } + + public RouteInfo(string action, string controller, object routeValues) : this(action, controller, new RouteValueDictionary(routeValues)) { - Guard.ArgumentNotNull(() => routeValues); } - public RouteInfo(string action, string controller, IDictionary routeValues) + public RouteInfo(string action, IDictionary routeValues) + : this(action, null, routeValues) + { + } + + public RouteInfo(string action, string controller, IDictionary routeValues) : this(action, controller, new RouteValueDictionary(routeValues)) { Guard.ArgumentNotNull(() => routeValues); } - public RouteInfo(string action, string controller, RouteValueDictionary routeValues) + public RouteInfo(string action, RouteValueDictionary routeValues) + : this(action, null, routeValues) + { + } + + public RouteInfo(string action, string controller, RouteValueDictionary routeValues) { Guard.ArgumentNotEmpty(() => action); - Guard.ArgumentNotEmpty(() => controller); Guard.ArgumentNotNull(() => routeValues); this.Action = action; @@ -56,5 +68,4 @@ public RouteValueDictionary RouteValues } } - } diff --git a/src/Libraries/SmartStore.Core/Security/SmartStorePrincipal.cs b/src/Libraries/SmartStore.Core/Security/SmartStorePrincipal.cs new file mode 100644 index 0000000000..991551ca7c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Security/SmartStorePrincipal.cs @@ -0,0 +1,39 @@ +using System.Security; +using System.Security.Claims; +using System.Security.Principal; +using System.Web.Security; +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Core +{ + + public class SmartStoreIdentity : ClaimsIdentity + { + [SecuritySafeCritical] + public SmartStoreIdentity(int customerId, string name, string type) + : base(new GenericIdentity(name, type)) + { + CustomerId = customerId; + } + + public int CustomerId { get; private set; } + + public override bool IsAuthenticated { get { return CustomerId != 0; } } + } + + + public class SmartStorePrincipal : IPrincipal + { + public SmartStorePrincipal(Customer customer, string type) + { + this.Identity = new SmartStoreIdentity(customer.Id, customer.Username, type); + } + + public bool IsInRole(string role) + { + return (Identity != null && Identity.IsAuthenticated && role.HasValue() && Roles.IsUserInRole(Identity.Name, role)); + } + + public IIdentity Identity { get; private set; } + } +} diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index efc736c244..a14afb2ba4 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -10,7 +10,7 @@ Properties SmartStore.Core SmartStore.Core - v4.5 + v4.5.1 512 @@ -62,24 +62,21 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + True - False - ..\..\packages\Autofac.Mvc5.3.3.1\lib\net45\Autofac.Integration.Mvc.dll + ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll + True False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll - - - False - ..\..\packages\fasterflect.2.1.3\lib\net40\Fasterflect.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll True @@ -88,9 +85,9 @@ ..\..\packages\Microsoft.Web.Xdt.1.0.0\lib\net40\Microsoft.Web.XmlTransform.dll - - False - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True False @@ -101,37 +98,37 @@ - ..\..\packages\System.Linq.Dynamic.1.0.0\lib\net40\System.Linq.Dynamic.dll + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll @@ -152,18 +149,58 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + @@ -190,10 +227,6 @@ - - - - @@ -215,7 +248,6 @@ - @@ -233,7 +265,6 @@ - @@ -280,7 +311,7 @@ - + @@ -324,15 +355,12 @@ - - - @@ -346,11 +374,11 @@ + - @@ -412,23 +440,17 @@ - - - - - - @@ -543,15 +565,19 @@ - - + + + - + + + + @@ -572,6 +598,7 @@ + @@ -580,6 +607,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/201503032227054_V22Res.cs b/src/Libraries/SmartStore.Data/Migrations/201503032227054_V22Res.cs index 76f959e4a8..b66b0d7113 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201503032227054_V22Res.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201503032227054_V22Res.cs @@ -80,6 +80,33 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Spezifikationsattribut zuordnen"); builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Bundled.ShowNotOnProductPage") .Value("en", "Don't show on product page"); + + builder.AddOrUpdate("Admin.Configuration.Themes.Option.SaveThemeChoiceInCookie", + "Save theme choice in cookie", + "Benutzer Theme in Cookie speichern"); + builder.AddOrUpdate("Admin.Configuration.Themes.Option.SaveThemeChoiceInCookie.Hint", + "If unchecked, user's theme choice is associated to the account, which may be undesirable when, for example, multiple users share a guest account.", + "Wenn nicht gewhlt, wird das Benutzer Theme mit dem Kundenkonto verknpft, was u.U. unerwnscht sein kann, wenn sich bspw. mehrere User einen Gastzugang teilen."); + + builder.AddOrUpdate("ShoppingCart.SelectAttribute", + "Please select '{0}'.", + "Bitte '{0}' auswhlen."); + + builder.AddOrUpdate("ShoppingCart.AttributeError", + "An unknown error has occurred in the examination of the selected product variant.", + "Ein unbekannter Fehler ist bei der Prfung der ausgewhlten Produktvariante aufgetreten."); + + builder.AddOrUpdate("Common.Execution", + "Execution", + "Ausfhrung"); + + builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.OptionsCount", + "Number options", + "Anzahl Optionen"); + + builder.AddOrUpdate("Admin.Common.DeleteSelected", + "Delete selected", + "Ausgewhlte lschen"); } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201503101851123_PdfConverterUser.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201503101851123_PdfConverterUser.Designer.cs new file mode 100644 index 0000000000..93180e898a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201503101851123_PdfConverterUser.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.0-30225")] + public sealed partial class PdfConverterUser : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(PdfConverterUser)); + + string IMigrationMetadata.Id + { + get { return "201503101851123_PdfConverterUser"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201503101851123_PdfConverterUser.cs b/src/Libraries/SmartStore.Data/Migrations/201503101851123_PdfConverterUser.cs new file mode 100644 index 0000000000..d0a855570c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201503101851123_PdfConverterUser.cs @@ -0,0 +1,41 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Core.Domain.Customers; + using SmartStore.Data.Setup; + + public partial class PdfConverterUser : DbMigration, IDataSeeder + { + public override void Up() + { + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.Set().AddOrUpdate(x => x.SystemName, + new Customer + { + Email = "builtin@pdf-converter-record.com", + CustomerGuid = Guid.NewGuid(), + PasswordFormat = PasswordFormat.Clear, + AdminComment = "Built-in system record used for the PDF converter.", + Active = true, + IsSystemAccount = true, + SystemName = SystemCustomerNames.PdfConverter, + CreatedOnUtc = DateTime.UtcNow, + LastActivityDateUtc = DateTime.UtcNow, + } + ); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201503101851123_PdfConverterUser.resx b/src/Libraries/SmartStore.Data/Migrations/201503101851123_PdfConverterUser.resx new file mode 100644 index 0000000000..6e085155b0 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201503101851123_PdfConverterUser.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/201504131412183_NewCategoryProperties.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201504131412183_NewCategoryProperties.Designer.cs new file mode 100644 index 0000000000..cf72dff95e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504131412183_NewCategoryProperties.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.0-30225")] + public sealed partial class NewCategoryProperties : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(NewCategoryProperties)); + + string IMigrationMetadata.Id + { + get { return "201504131412183_NewCategoryProperties"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201504131412183_NewCategoryProperties.cs b/src/Libraries/SmartStore.Data/Migrations/201504131412183_NewCategoryProperties.cs new file mode 100644 index 0000000000..0ae7100ae6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504131412183_NewCategoryProperties.cs @@ -0,0 +1,109 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using SmartStore.Core.Data; + using SmartStore.Data.Setup; + + public partial class NewCategoryProperties : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Category", "FullName", c => c.String(maxLength: 400)); + AddColumn("dbo.Category", "BottomDescription", c => c.String()); + AddColumn("dbo.ReturnRequest", "RequestedActionUpdatedOnUtc", c => c.DateTime()); + AddColumn("dbo.ReturnRequest", "AdminComment", c => c.String(maxLength: 4000)); + AddColumn("dbo.CheckoutAttribute", "IsActive", c => c.Boolean(nullable: false)); + + if (HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer) + { + this.SqlFileOrResource("LatestProductLoadAllPaged.sql"); + } + } + + public override void Down() + { + // inverse of LatestProductLoadAllPaged.sql does not make sense to me + + DropColumn("dbo.CheckoutAttribute", "IsActive"); + DropColumn("dbo.ReturnRequest", "AdminComment"); + DropColumn("dbo.ReturnRequest", "RequestedActionUpdatedOnUtc"); + DropColumn("dbo.Category", "BottomDescription"); + DropColumn("dbo.Category", "FullName"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Catalog.Categories.Fields.FullName", + "Complete name", + "Vollstndiger Name", + "Complete name displayed as title on the category page.", + "Vollstndiger Name, der als Titel auf der Warengruppenseite angezeigt wird."); + + builder.AddOrUpdate("Admin.Catalog.Categories.Fields.BottomDescription", + "Bottom description", + "Untere Beschreibung", + "Optional second description displayed below products on the category page.", + "Optionale zweite Beschreibung, die auf der Warengruppenseite unterhalb der Produkte angezeigt wird."); + + builder.AddOrUpdate("Admin.Catalog.Products.List.SearchWithoutCategories", + "Without category mapping", + "Ohne Warengruppenzuordnung", + "Filters for products without category mapping.", + "Filtert nach Produkten ohne Warengruppenzuordnung."); + + builder.AddOrUpdate("Admin.Catalog.Products.List.SearchWithoutManufacturers", + "Without manufacturer mapping", + "Ohne Herstellerzuordnung", + "Filters for products without manufacturer mapping.", + "Filtert nach Produkten ohne Herstellerzuordnung."); + + builder.AddOrUpdate("Admin.Common.AdminComment", + "Admin comment", + "Admin-Kommentar", + "Admin comment for internal use. Won't be published.", + "Kommentar fr internen Gebrauch. Wird nicht verffentlicht."); + + builder.AddOrUpdate("Admin.ReturnRequests.Fields.RequestedActionUpdatedOnUtc", + "Last update of requested action", + "Letzte Aktualisierung der angeforderten Aktion", + "Date when the requested action was updated the last time.", + "Datum, an dem die angeforderte Aktion zuletzt gendert wurde."); + + builder.AddOrUpdate("Admin.Common.CreateMutuallyAssociations", + "Create all mutual associations", + "Alle gegenseitigen Zuordnungen erstellen"); + + builder.AddOrUpdate("Admin.Common.AskCreateMutuallyAssociations", + "Do you want to create all mutual associations?", + "Mchten Sie alle gegenseitigen Zuordnungen erstellen?"); + + builder.AddOrUpdate("Admin.Common.CreateMutuallyAssociationsResult", + "There were {0} mutual association(s) created.", + "Es wurden {0} gegenseitige Zuordnung(en) erstellt."); + + builder.AddOrUpdate("Admin.Configuration.Plugins.LicensingInvalidStoreUrl", + "The license key cannot be activated for the entered store URL. Please enter the right store URL in your store details before activating the key.", + "Der Lizenzschlssel kann fr die hinterlegte Shop-URL nicht aktiviert werden. Bitte tragen Sie vor der Aktivierung in den Shop-Details die korrekte Shop-URL ein."); + + builder.AddOrUpdate("Admin.Configuration.Plugins.ConfirmLicensing", + "Please check whether the licensing is done for the right store URL! Proceed with the licensing?", + "Bitte berprfen Sie, ob die Lizenzierung fr die richtige Shop-URL erfolgt! Mit der Lizenzierung fortfahren?"); + + builder.AddOrUpdate("Admin.Common.Unlicensed", + "Unlicensed", + "Unlizenziert"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201504131412183_NewCategoryProperties.resx b/src/Libraries/SmartStore.Data/Migrations/201504131412183_NewCategoryProperties.resx new file mode 100644 index 0000000000..8ebb7814c7 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504131412183_NewCategoryProperties.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 + + + H4sIAAAAAAAEAOy9WXMcO7Im+D5m8x9keupuuy0dqarM6padM20kRR7RrhYWk5Ju3RdaMBIkUYyMyBMLl2qbXzYP85PmLwyA2BDYt1gylS9SMgB8ABwOh8PhcPx//8//++v/et4krx5BXsAs/e31uze/vH4F0jhbw/Tut9dVefs///r6f/1f/+f/8evpevP86nub7084HyqZFr+9vi/L7d/evi3ie7CJijcbGOdZkd2Wb+Js8zZaZ2/f//LLv7999+4tQBCvEdarV79eVmkJN4D8gf48ydIYbMsqSj5na5AUzXeUsiKor75EG1Bsoxj89nq1ifJyVWY5ePMhKqPXr44SGKFmrEBy+/pVlKZZGZWokX/7VoBVmWfp3WqLPkTJ1csWoHy3UVKApvF/67Ob9uOX97gfb/uCLVRcFWW2sQR896eGMG/Z4k7kfd0RDpHuFJG4fMG9JuT77fXfq4h8+pbC8vUrtsK/nSQ5zjyg8AkhM8oG0zcfYA5i9PXlDQ30b6+Y7P/Wccn7N4id3vzyb69OqqSscvBbCqoyj5J/e3VR3SQw/g/wcpU9gPS3tEoSuumo8Sht8AF9usizLcjLl0tw23TofP361dthubdswa4YVabu5nla/un961dfUOXRTQI6zqBIQnr1O0hBHpVgfRGVJchTjAFI/7nambrwv21tiBXRlHr96nP0/Amkd+X9b6//gubQGXwG6/ZD0wBEVjQBUZkyr4CggepKP4AizuG25iHfujVVwWKbRC+fsjhKAnTUqLKv+RrkuiFUI50XH8BthHiyhTnOsgREqQbo17f9jFLOM1TduorL4ypdJ+C8BJujEtHkpirBGUxK3HrruXeCJF2S3b3RQR9mo7yunmj6WtVIHdGDAX2Pksob7by4yAFaBpGUBms73kZgX6JHeEcILaXb61eXICFZinu4rZdcLUte0+XP8mxzmSUGk4Qqdr3KqjzGAiazL3sV5XegDDWTQ07dw1yV19UQy3dG1MQOBNZqPX4oaBmLM6T9tigfQAw3UfL6FZq7MWy08L++frXCa+pvr/9sincB0BRJy+gOWM98O63lz7+EWM1X91lemqkq6GeACmvJ0AmKwpdKH+EaXN1Xm5s0gokv2Hc08DeJ98gR4VDcOwj/0XStkxxgAfA1/VbGHcujL1dwY6/aftuuPcCkCxyzehRzLXPtUuWyzLVLpN2i3tRg1uFrpoyik8Os+vWbyS9as1WdseuGUQfMm65qtK2i4a9eHJQKhXisSYSr89UFLqIc1fl7nlVb1JAw+kWzCJyna/gI11WUJC8LWcota51+bUftm7K+o/UGpifZZgN6PW60ylquBRu0JPtvOdHgPH1NP2YbcBFAV/wMyghN8acsXxej64y4simHGdd3BUulaStMz46SJHs6IaIf5JfgEYInbwX1aLvNs0ewvkSyM71bVRs/vvmSlYERW7grJN4TptPebQwHuqpu/gnQ/MuOYm89/xPcwBI3jqxk3kO8eqjGn3RRWt1GMVYYcrTqlV/QlqffFIxW7e+IxUav5Lz4Hd6WJ1HuvWFqcULoFpfgjwrm4Gt5jyheC39vTmkwe1VFJa3f/RJGqFVYky1hjDWZo/WaaYN3n86LD9lTmmSR/3a3wfEduW9pUk/wFtC7j2hU6gn39ZbDdLQ/NTCnz1uYE7X6Q/TCYppBHMUlfCQQIdj+Y1SsIqTjgFCjOkTjmqdtzbcC5Ed3OQC0mufamAHYFXgeX288Ly5BXOU1uq/0aIBOXuIE1I3ylXE04gXIYeY9+zpMsvYTYM+5cl6s7uH2NMVFvNnxvDhDw48BtwGGBMlTiGdelLSIJ/fYAGFuVrZu/lX0fPqMdiHec+G8OC28yYlac4K2Q3dZ/uLLOEjFQVuh8/QRTU0Eh9T+e39uRBpe/BDsyABbYwni0WMEUVmYUKieVl5hS50XLJgG7Pmn7KnuNV5pEJrvoKANArx9Idv3syxv23gM0O7L86Apih8ybC3H/kvep8J4N4gRz2taoh1It+n11iiITR+NEtxUmzCDVCNGz+EQCQHAugGDYAJlFc0F3MDj6uW4KsveuOAxt3DuH7C4T2BRhgFtJn8CEPMiuT44nnHFPEHaOYGDsf/5Ew0SfAX6mqzHraDZmJxkhcXprO2ysEU4UWLZEXPMVYn26O2ZmPCYzBzrNF17ILXmrNMUn6kF4a9GbA2QwchM0Qi2SetEm48rCOo56S3w0RoKirI7OjzJNjcwbU7RAjIhanPrilAcbbcJ9NeYfwB4dz/eVBzuY4LD/4DrEdE/jkubbqXxlScdkJ8wCeeQEM5HAiTAxc9uuQ4SfAfhI8hfcGFL6wntIG5Z9DgqQLP6BNl2d3ifQVRUOaid38d1Ve7qPNqEdPXqYPGPIbQZaYnTAjnR0ligwpjXG6cOkGM/iVCGjwEopkZozNV9VttTkOAL50TbLIndEin0VWlTr5vcV1lvK++dVeS5OA8iRVZbhyFaGqgcba6HGTkfm0G6zL1mmMnRHaj30TH06HJ3cNJ6bkkcoix71Bi8yIZY3qHWLKZwdGKzyDrA5XNsOH2IqGw7nVHRflE2WR+EeR37cQEJjrILTR5F65kcsoaz2Rzb3B2Cy5tcZ1G0eJhB1mAml2N7yQ70FsYkC+2vK2+/uIiiP+oCsv5pSjn29yq6U3buepCPl6dUslSc0nkcpen3KIdIqRPtI5XNV5RTDJBBKdkomRR1HCoW2qrjFr217aJzv4Y3QhU8OMzIMyGdLuXCQSZbNmTPhhWNZbPyzR3mkDaYyWbbZNqGI2hulyziDC6R4wU+h2j0jT2Q+3spDheDG6W2+3XwQpbXpfHJfT+OT247MiE8Nb4VeK8U6LaR/P5ScKNWW5Xtrtz65LcxmTnYuRormUPJxruqOMmqbZaeIE7xtkxxSOLjtzCnb2RkiH8kmWW+PNojYeLZ+oHotvFXmWYrxm+86QKqvXyfj1uWlJltFyeBiWEMi4S+D5zxwtgi0UA1nI8dqzSdEJcQ9EOUUd4VYW6v60DtvtvjPlALcViK5XVpluKRrsfg2yqBatYZ8Ke7K3GclYhPJ72Eg8P+jL4ytdMo1PWbw4WZID2r79+FcgBsLG2WJ1X4/tQK/gv41T24+lNcZXX8Dhbb2SOnwfk6dB7z2HcYeABdRumd0mcrWAiBgDfZRvBfWPDNoaWdgu9lwAGOVCTo1HcInj6rN1WO0jj04aPDlkV/ACnY3xhbYWsxLWx9f1LVZupbzKZxmjyXwe8uf9sCa939M1jD6E1T/qC4K8RXTaJjmEZ577Le/GUykXR+hhuAMZVL2Cg350F2BhOQqncHfwp0+fELeLKW20FPSPmpKs6hPSFl5rXbtB1g+kfiOEzjqaJ8ydT36XQQpyXLeTZIjqtkk8ZyBof1bdC1NUTwHIH9LcgpFoV3mMXKeUPIFWD+sWS/rBKweilKsNEYzMLYL1ZbMPrp0DFMEuxdiCvxt1x0lz/DwF2CAtE2JhvNLhwJmreWlpEeppPwKnNAmMGjKo1eMPPU9zinrLgdDb21JXTNjV3AcpxOn5GgqoOoT7YX7f0Mxjupke5Clec6bmvPwNvVI6Y6DXRYbeR1aRaCcWKqn2RJln8EzyRM8+S170SYddt9VoAjTgbpMGvkdQXaaYU6cDkvzkCEB6JVEGa0H0tXqp5DDXzt+8xyZ/vuh2RTxGccYQvncjtA194gu7gL3l3fXzbQaAf5MLp8oMn9U8iIIbca3msZFlJfbhn8IZmH8gIjyA+fGzom7Q8iSwIJkYP0WLIf1aSeLRQnHFyBluUKdHDe2UHnnYM/y+75szidcg0Xd/6MS5TOKQrCTCF2G/WVVf99Ro0TTEd4hSM8NPUT75ZGxJxkaYlPt6bQ5XXSO5QJGxNukoCoddhyP6J8BMn2tkr+AQoSZDQI2JfMBctHh7e97y3R2yW3wi0bWqM0tEhBUaC5qgtxwJW5ZuazpEfSEuor7fJiXmcH7ax2ET+tztH9OuxP5HW1NPq9gl2t9W+HKM4a56xA8ShPN9SzYuNVcxEVBd6AjC5824rOsnwT+Xv8NGirKBl/3Zj0tZeAYZaPbm9hAgNsVEn0Xe/dTCDN+ryoHUKO4sEzis4++tN5l3yKivJ8e7Reo/3O+L4BQfcNuOmfsjuYOl5OxuXbGM5SCPdLuTVJJSpDK/yvqWyUbziXynuH81lsVZ3G6acbe1Uz2byCtg6zyBvM5LNtNbvrUDW7yXPd6zR8u9k8nNIlzWh7U4D2XdKQm8kqoPYgh5zYw2y2tD59xipNlBxV5T1WauogQ5cgrq2Dgh6oSgiHwagANyZmpWwHCKkB1eYiKyR3UbpkYUf4VK7VgixOTbzKtjBWtJGkyxs5TBa3kslj20xiYhK3kCQJGzdM4drFJNs26RKgXUaKfayAbIAHWYRNFOfgmirJZt/kpyhfX2QwLYuPsChlx/6CfJLWy/MJ+qDIbNuT1gfRaK3hMgvEH5NHLgDZjNYRnqjAm/KojWwuIfmlmTjay3N6be07gtrv7NFGI0vfNACHXb28rjOYF+VkmvskFel2/YG2BtlmG6UvE9QjdkrX7MgQd+BAqY8wjW1P9U6op0dGtAeQqfluqorej17Rf8Et1pWiRBPsKZAp6D5LgfYZyUAvA7fv5y18vy3fydRzSLgUtgttl6dfAJkkbtFm063XanqSKhvH5OSbOMggbegwl19wpZZcHt76DcZheZbXpVkwTcPzuHhkFI3hw9uCSsBCRa6/eso+AUzE8yLTCNr3AQTS1X0OgGmFfwpQIRK0IIcxU5nno8vfI28TbzjfkBEcSPnLY921NeHmh85z3Reu5RETqUOfW7Ad0hax3pDSklvSrYFwFyxnwgx844W5vPZxQ3o4LBktADN0h4VDIUf23ps0nHfY+V2KaF0//WrvRGcglSjBMoU8YtU/CxHmNr+H2qmHRjhAOkxveV2BrqrPpF7e3OTgEUYaMREm9uSiFRfVxtRxPecmv3LV93K56o40A3heNViHOa+Y8w2pvO+EGbhPhPGFP8dBIBHr7dUDf9rz8vBH/LLTIqkvgNO0Pk6yu85Dy3pK49LFGwpjGX7cTWMm8Y3GnccmYOsJKnd5aRCFLEXR+rrP2LOTKJ1jJWEmbzaqm+LBQxjgsBbI6/oUpXdVdOftlRjqfoJt/KZsHeKIyemiWc3s3pel2pWtwTsRPCRqq4xnZXjQ+sGzkQXfPI+1HK5+LvES4UhHeNQ6JbaiOK2GrPFTuWSaNrWVzdJmEtQ+17CNg0Thcj3M4bVW942wXqtJICX4L5L5TYtzWLLldc1ja2lHpqG6css3Rv2o9B8VWIEpDuvOkujufIM6i8M/a8gdJMzXZbm/TxKMYO2qY6/Vg3EJCiLaxMJclFMoMZUZOfGuzu11xiWC9paqAsyDhNXLugABXAmtNSJkpEcm29p1YRJH2iSdF3h2XSTVHUw5IaK9dpdVsUj4BFGdAgkFVqUyEyFOQuFCcv85VKgECnIZ1rZB63yn4Y+oaDoY7kz2YnjDfcrL8cr7//o79U4M+AU8Fe4mXVz6DQWxDB5rLbqTRNaY0nyMKY1vT4QzH7eIQkanBva6z9iztSidY2JhJm+WrZvizq+4/EFRkte149bj1X2Wh5iOltWeEdE1da3BdnTzGGl/Rpv7oiyqB6N5kJ65bVm6JVG0TeEShavrMIftFRNqfRZbWpy0ANauolQV3HZOWZJ8z0qyJYtJaB1rbQBDFG9YoGXosLhVR2nx5ODMI9/adJjifQ1DiGs6P7WZkWfjdzCKvH77ZqppHuNeQxwUQTUfjuxDOpJaVF9I/HqL+K9fYRf0ZF2GRYRkGjZTpM4znHpUknC60enWoQKZ6SoJDugvJ7gwgAYyxVlOeEmIg2yQ1xVqkziPfKhDkjWa7vi3sYNt0oI+x042X7+TADJXGZaUM54KLmI36rszc9sFEGkt2gEMEoTy3l3z72WsXM4bLkQieS5aqLwuE2DoIDcJMNBBrsvr0p1q/uUvoxjPcgCC3QEPFdwTATUSDeMwQ6ARrUHCebZxOGvm91w6TIJwmo6usZQB+QYWhSbKHptLHi9QnZOXROrsfsolg+1ybxnEVY5QOQIc5JOzfBpVX5xHNvbP3o1es0ewz2CTmNNzzOa80yRWBd700jZUwIfZreD1QJcYpwny1o5y3btbOEEIrLbKRjGZsOavmJcJ901T1VEcg6KYpsILfAEabVrHjFTudjF0pAjErJC1i1vsJGu7cMEOgpWULfqQxQcRKq+LxDr2foQ4kBwO5Bllu2+60F9hDxM+YucvnltHAWclhyJQuGkD+xDdmibSGUWNpGJ9y5tJZfKXZ01jvAQawThINHldhEqLkWh4tDCAL1ATfk/li2J4C8n+bLR5FcCn+d8hePKEwCGfnQIiSHBCDXGLV8tduyOE/VgQ7J5cEMpaybMMVmuCroFNJmHryE9V0+oM/kuAp/Q/CH55XYRAv+dZtR35lHmku1pTOjd+6R9S8SJUkMUBS9Ag+5B9XCJ+rneL+zksl+bXdCZGmlNpYmlOZ/CX5k0jvEQ6wTjIdYWQ2Xtp/BPOccULXqbzW/h2l0gAOM3vZjCsp3b9+lf932FWy+siBJroJRBSl/dTw6TDSzEsDB+S9LYuDB8Ks3zjh5AXh4atgjUkDNpF9IKvkQQFq+NeT/hIbXcXpMpzkMYvk7wI1FZ2GfV+nh9ADDdR8vrVRY5+ESn2+t1fX79a4ZAHv73+q/NcGHpO+Y7T96icUqysqpsSicTkPI2TKwxrRKw/W3drUNnp8zSVXeHK0NiQh52n6uGg0ml62kidaXrYVDZpz1BFFlPZvrKBcESrCMQqQpScATA2TeU1j01gec1jU7vBHz8WJ+Gh0Zm0nevj1kLEymhVIMW7StdgfbQZtSf0w7Q/QA4Q2wWIh34P4oesKo9KxEc3VQmm3PtylRf/uZngwc1G9Qh1L+no9hYmEM3JYPuD7eg0IBd68KYGP7abAyS/ThBvDXUnZ5ZCSLQb/HgDiZs8iRrO0Wa8yANR8QDWsiEZtYcnj4/jP26KKzp93sKcGBI+Z2l5P3Gd/wDR+PSk51f9KtEHcAO975lQUEcxWTc/Zsl6Av7gK56IMamKj6P0YZINMFPnJCKGrvP8ZMrqugeBpqvy/CaaQLloVlOiAHax68ae9xXaEORN3NWrPEqLKMY/e9Vg8qonmTKyyi9BUSXjk/0k2mJD+rQE5yudqLer6qbT0aft8kWVx/dRAaY04F9E0PWWNvte6Njj0lSH9+dI4Gyr+rRkcqvxB5CAcq+eylLshC8BPnqjLgOLjy3k70EMDlOEJ5OE26/ZjP3ppCid80AQZrL1dVP649U1iFzxhimSpjk74LUmnG8F2kx/hEWZSR4iFGW8bs5a+8bKc3Gnvoqstm9t/A5vyX5L2wlRxutvBVj/gOU91xl9bq5TBkVsO0dK4VhqCv4moda49jNJXGPZdKeWfalDL8lahpMlLeuTxC2j0m1bdgnWAGzAmpY1p9I3NulcWqbQZuY6oy9h/Q44ars8mF+bypN9mCJ8sZxKdmnV1kgaczlZacdkkAg9NpeXW5hYEDq8YlzDFEKBefArkdfVnVZ6GmJrqeZrzR3n/ar+wMR0he1LaBbZ9iM3V9S5bfWFRqaMoSAYNXwouZxmulhbcPUXE6Edprm8rpZevjN0sIKGABu8tBL8/G8kgdJS01zb7UtoFN32Izcv1bltBQqjaE2htBt1SKzTeQkcfyFzECzyulrzFiUVRM9omEmnEP5sI3sUnBdtY0kUsiiA0agFPMmq7US250tEjC1EIzyJca2rbZrAMSuQ4n3sFD2rq5qmW5/RZouKSzpaPedFzx0ZCX8zs1l0JAOU/ZJsZHbiFnDT3oglqbI/17IyfI8kWaWrsiy/15JMtdDrosjhjSGTzTgikvcljiB6/kX9qJovzN+rqOm916YjheVFDuPRPW27ikZ3rJ2iN5P0ZGxH4Na2UeuIY5NsWNvYtJvFG3VaL9QP2VOaZNE6wDNR50ULFkyN/4QmSFqAFtdyC4Sl9Q8A7+4tti6aK29Vuk4AUrOi8f0NagF/QuLPheJvqaJ0VBRZjJ2H162yIj71CKglyTQ/nVblb9+1OGgUHtcIDiKNFdJ6VDXN6nKJGtYkKprW5vDXK7+QNxa89EoMcdAr5XUF0Qa/UG9hTBe1sL0xmfXOHovc02okgdnBvnC6CQ7+nSab4GDdK5qzAO8wBxV8F+hGfE3wEBjHURKlMfDclhWjXwabyoA2kmRQOrKJvF1Ebm2qfJzYUGYOf+IU2CHIpDcBj5taNx6Xpyoaj5rOh+gg/+R1BdFBrvIofkAUn8gfm9ylDbu7IzwDXL28P4AEPoL8xbH45LqPsXMdO+klvne2Ln9yR1A6x3UvA/gGDjJInQCHubwiMtGQIYTSweiun5GETkE86USH6NPYy7WTIeQ8kM1X8Wxx3K8gdkwvwR/4LUZ3A8EA5jAPFPMgRNyvYJMg1HYpzOHTJYiKLD3L8pqbpjeDNPwLiNk7yAGBXwPUd5zMhra5qDp+eJdVGd3efsmmiCRztN7AVH8D98+/BHloYyDbwoR/W9BNONcdNUUTyV5alEOw7xRm89xsZkQ5O0GLkJ8/BYt0WNlU8z/AynYR5Y1+Y3lCWJ/nORSkhziEh2UwC2QYt5B5IiGhKQlyxEPYLWI0i2UYpWMXRHHP3JLdxVBKXdP56T2GNJtgpyHPG/SOMVeRaC2RZtK329kiqzrf5WoRHPPK8uibHOTQtwFZbdFcu23e1OtkgcuJVFRGSXb3Rol7WB5Hl+diwn/dDuN1OK4VOMbUGUxQxwK8hY74+ulr2nT7gjpaco9V5RzY3mmWK1ldNOWNCvCPDBuVsrbNKvjEobdqOFMSqFAs6aKE8ts9KLvqLDhVsAe5aSvwRn6UaVw3mzEkmJhMkvfS557q7PlOAKnhJxfFWp6yXgkOpQDaF+d1QweMEYRfcLF3EHg/g2hSca/kvHj6KcedNXtMW5+d2lV0578tQyCHqeU8tf4c6PEv3VqtXJWv+6EsBMstnSxbRwd5QvDm9yiHUdpHVT/JNjcwjTw1Yj34gZflda0eqgCsrLl7X0LVIWiYSj5HaXUbxXgQ8ws02lqvszDVWtqmNbebSENCof2A63BgH8Nem4oKQChn641sCIt/DKGNDnGOigLepWgWQcJH52vVGfS7MMfCrZcgtu1bnjm1Jxf4EqztTbiCXMnzNqbt5PESEpnxQ5hjH2Lr/FqVX28JaH3ea01UeVA1ijlUq71i8bseYnCagGlRmWHNuHzIkxODuuWGVYNSLp11NbHSs9i1s0MMqx7TRV26PSgf4qyJrSy8anjQB0cX6A1MMEPrFXgu0afNdvwo9jg0zx8VzP0vq1PzJS3zLAnhFzLT6RU7hSzEq61MdRakLM+59EdoDzItY9tDtbHVoKssMInzqDQLCEtcSyWvlgbK8jLDgh1ICMtDwMXksIoYrCJHCYw0e6cAknomE/dooV98NaUmzGtodYnAHrhdXpeEbv5+NDs0iTTekVmS5as/qigHxeXdzeidqm1L63+imUA79wf3Ha2vm05Q0XmBkNBCDuIAQZQ8dEgGiYiGEGrtJ5g+YEffOWPY2Wpby9GzDPVOM2XNaUVqA3I5rD6fwRpGb1qAwzqjmLcNjbwDTn7rI6h9yxNvccJDjR6d7himUf82ZPuXSXW6hSot0WBg2NH7cgYTkE6x+p4+oz4V4fVV0TL1BTzZ8ZOxiLmCIG9O25w13A7jIGbkdQVahYNc5GrvXeB1zfF0zNeQGfTukfZ2C+6oULHoWPd6mLVXHcQ5OOVAki3k8UxfhcBMyCUqWqgyBZo7I2JGdAnOgb8U9X8HeSGvayJ3KFaHUWobI1W6KpLTFOf33oOtQIx4JUwnNJ4bWaG8th/Ij+FTdpd1fhOznXGwfS83yXG2fpni1eVac2yPvL+A8inLHwKPrp3M+xyRG4Geoq9BOUhAeV01+X3ZvkaZR5o6KGvmivs92ABibrhJXNZhUr54M4A5cKO8LkKo0ZeVQHxqYF9U1PI+zNo1JvcfR/HDeUq8w1bVDXV05LyJlSAepkTQAR5tXxwqssccEXAlrCeMtqDLy237tAVCblFllQk2rJqsxv0Ispml3dyvwAbpyV5ODSK4gyCR1zWPdvYdgifU1Pvpa3bdihnzM2JEcJflLwF4mYU68PGBjyfj40a4B2BjBunAxQcunoyLyVDA2MtdMttssvQNi3RgY3ldYcxHqEe/51m1nZ4tUc0zzEKNaWAk788xTQVHcXIJ4szpNXVynoDg3nQghxk39oyb02CrPBi3di48XyddHwK7bXzLfZg6e9OVP/DzPvPzKqnupq811DXoT1F6V0V3Yy0Mq/gerKsEXEXFg8sBCipWvKFBDnNJXtc8t0rQ+p2la8+n4gK5LVqLjTAeEWW2/Zqe5nnm/Wzkp4hE2M9LhwcPcNnTdO1YclXFMSgK13rpzody1TBXP29vYQLdjBdd2aL/eRAy8rqO1usc8Ym3pwxIQIALIY6LoPzx6Lp3woOXjj2uu1z9UQuXyB2u8Dn8jlNAVCD+a5+Ns+b7DzAHMX7l8M0A6cD68ro066vpBTDb1eWlKMEG0eGJbEWmrv4Sk87Qjfivy7MLNsz9Aa1jzUUC/5nSgR0my2Gy7NNkOanyHKSxy4Pd/SRpQQ6Tw3ly/GUkw1s9MCcIQlX5SBMDjD0vPmUYwJ+sJubLsyzfoEGnXokYq75PcAMRi11ltWezr8JMJlhxv6Sr2At6gIjtI5FtaE+NRjb4RQRjqYx4rdp4emASjOINB3UQ0vK6aDp5X1wO5TtJRtA7Jlm2hfEuenFy/Cv035Tn4jbliqyeEQHhI6LFZ7TNJw//uE7YIc5htipmawhHacxDoWbqVRYKCfHnP0GsfOh1HI0Rxy6c3iqPwxhG3vrJedGYGI9fjqry3t80TwFeIr11C6moOa6YI8lQmo0lsV5osXI9zE+HdJFmE0Rukee1dX/vp45J6+nc0rb3mXQtp3J6LQHENI0+fcpc7jGiUndovr2hUA7SX14XRSZc5WJuumif6B5Jho4kWBgyi49J+jzXXH7qwESejT86UeQN+hgrXZFIqIjSla0NLkpqMoYQJzj/QaTI65rZlKwxEb4fp1YnjwiLKzObbQTvXAwYzepcvGkxDqzrzDtjOQJptykjVYzjZOzaEhvSyms8/07RVEqO4rh+XsZ9DtI4h3kor4vQSSXE/zLKYUtjmtetIIaVq+vCMXqm7+FFlrPvI9meExRoJMMQyLbtUVFoVIqRaq4X91XhHTcTR+EEtxGa1UgIkrmASo0ksxqh43E9sRNbDNRBcsnrmkf7PI7jemmpHeSA6szNtAm+OkugIGOBdJRpLgXQ67t/fOwpNJsv4Kn4BDCTex6UdsJCjHiQGQqZIaSY98npTEpUmJkUdJMw5g3Ov1egAuuG1u6zhoI5TBV5XRc5zHLvCL/YLql0Pxplkca1av3XQiyZV9n0nbvKJunaJdgmL0H6Z1TPJH06ORm9CqQfjl7HfmmEYdcf/L5ADoHnXT8MI2nQiGqp9EBoaBQTHApRa9r1MHN/MCTLwx0OSTN6HRCd3IP4Iau8XourXwB+w0Ed1nF5XRrBOtLd7518UnSFJtQW57+oo0SFwj0vrqLn02dAUcMVCgG1kdiCPQu+jAdUQ9kJ5KfqrNRQPOgpznstkGG9fDUtwz3caVzQ67FOcS0BhfDhJUOdosNSzHe2zSPaD8//jSQBzSXX9DKL1RGthZ2TzLrEcbcwX19EL5gFPsIC399zF1oSwIPUktfFksxXaJGRWOjFDravwnkm4aFrvnA/z0zLcPPMuGDQeRZugh1mloKJX+IE1Euz52zAQBcgh9na/0ZIGSUEz9eCgiME4SkZwqoT6tAwUESX8xRid4IlSzK6iUZS7HpYQi66Bhm18mqY29YRWrr+Ty2W2S2btTx3Esvktjj8F3m6mZDFyauaYJDMbzjAg3iW1xUm7KNb8EIBDokcMF8Y4q7+uaqeKiSxVBi1IymUPtzMuu6z9/JGnosTpIqsnqpeQmZD+xKLcyT9IdBBkMjrumifNnrnJwI6nPfzGGnNTY15VhRoWiT+XMZCHfhsqXxmodX4XDY9XDLVrJV3n8AjSLzv899nedmFhpj63soZyh6ucs1mbtuFyhzZkVd6Y1fnjH+neco4DCEQk4M8B/kUdU0YT0nnKQb+qOp4cl67+Pp94o9RoX64xoV0bteEZdeDldeCw10HXoEm8pjDEzfpLbyr8nrH2uAcRL68rnnuQezhCzGUb4XTOwDPbyiEA8c6c+yuvu9FQpa5cA4uV9T/HdhGMfdJtAMN8wRSCtM4qdbgPF1BVGXU2dvcPcDaK5UooQziaGBwRzMMLa5gqQxZGqaWSVyNP4MyaqJlqNT9cJV9ANRFrwnqm2asAsd7vQQpEslHxQ+4rg9ZvNBqlP/K0vHpUFf1I4+2jf7Ptd2kPNpqPw3Gza/nxxle4ALIlyD3f0jHriJVCGKXsZCtyUdFkcWQrDZDY9YFJM9wXzf/M8vjabp+1QZaE2XvN0p1J+oIb3RO1CO0KsMtWodRs357/T84wmjr6M4YqTrE4O9es4v517Q+2n6Fz8mxQ9xJVMTRmh8eRLf18AvZ9tf34xEXF0ijgGnJKwswjeE2Soy6wJQ2VDZw67p62JQPYIulRFoajZNJA5oi4nZ01THU0xHr17cUB9oxZmMqN2Wa5rn7URmzqUPEmK1hf+mMOezCDIw5HCcjxmzN7zMxZruD1cpKNqOIGfsNtTkbcrjmkvGXN2/ecchOjCRrxAQsJKPrLkg1tMmu74odbdGgYB2x6Q2+kicbcFUhEVO1+W2YSlmHgMEMGdeLNM1MtyFMW2Q8snQ1mIv9ADRpfzRXrrDvlAFZRKVsKBN0vTJtn4Cugnz6QXQSa0akm0DGGZHIpB1t+bn1uGvEN/AR5C/k1EmjYQ0yK3Q4B+VtCC3iNbqdoy2fqtZMp4UJ6WzEVVS5uTmrUwe6JUnDAlwBBYe56GfSaiyW0cXsFKS9mI5LpeNl0gT6NvAiuNRwI8vmH5lHd3gvK+vDDAy6g7vZz1Fa3UZkU5Rrd7SizCLGpPPZcKUQf47draohE/CVis67sMttmHrQjQFPaMSStKBCCrrynLZKAf+p6lqMXNT2aDoBqR1Pk6bQ5RbF2YYruqjMRPy8w6u7qh8zMfAOrvIkcg9M7z6D8j5bX18ChAaxl8UJtg0ojY/6oiImHpay4V+D+kT7KJIY0Bq5KpFSh4btETEKuG7hpY0W5RbShc5oRRZhDeaUmH9CK3swwVRWjpHRVrIuMtskbvzwtczI5BOxYefSb86ALKo564VTyyVtmIB5JDTdJbYZih7dMA9nS1gWGmILGEkpJcOzk7A9EzKVkNYm9Q8Kzncg3t6I6GP+yziAzyo8FO/uZVgcivPIAsYyY1qXvh/DJEFaS1uBtpnD/MGpwMCbk4KfXQ7UaNz9+gs2uvayBVT06HwJ7cnCVSNaxCRkn1+B0vViAomlGy+j9ZC65jeLvKrjJdSelmh7QVrehyOQ8ZCylIhfRQVsmFZdoYBz+4gKS+Nco65MwL5GY2jSDjpqySw8fJxkdxdZUer5lssp4tU2kw1/8sA7xZPS5k/Ah9Ix2Rnea15Fvu4YR8UlbGYZB3ZPLdsxIQcv4EMZgy+DD2U9mIgVZeNjUn1bZm6L/CV4hODpI0i2t1WSYp13kKAzzmuKK+z0XEkHY72udrndvund4rjarmPTGfINx9nCpl8XnI398Ytd5yXY6BUBLqeIqdtMNkzMA++UIiBt/gRcKR2TnVAEcOvbtaNjHBWXsJllHOigCAjhBXwoY/Bl8KGsBxOxomx8TKpvy8ynCGRJopeCg1zCZR1lsFrCB4A7JfmETZ9iLRaNwU5IPNzyo7R4wl4AGRdFYsgUVD4Zn9VZbLmNBhZphgIGXgavCRo+EbcJxsJIxUP5Z+W171lJTFVxlq+vKY5RsYekjIwH6ey2nCirSsKVYnZfBm9qujIRn2rGzpRn61LzcS7IN7DAQXmanrQmf9xR+dmYupiQf5kSVvyrrk1xMoQzjHCCaEYkcfbg54czEeP0GYc8ipKjqrzHvBiTRGaApLQxKi0ilaqgDfnMGrBTh45WXZpASlqNsUl7Zj+OPMvyakPMrloG57OKuLnLZcO6AmgrPg3ikCNvxAScJSfu7rARiZFnyEfDvFJGaqL2WXISAz4TK4lbMRUviQm8O8x0Tf6t39NQDjaVUcpG1hxEgwrYh2rb4tZMWfunYjzBeJhU3ZdaghCrucZAyNRdHkN81cgy5lso3wmaPq3AG4yHMd8tQP2i+EWvJVEdDq+CUeAy7hPy9UJYUNKHSXU4fnxMqicFZmPF+hE4Q09iUWYRKzbxnM3ZUAhs7kAcSH9TtWICRlJR16T6YcmZOUq7HxhmC8hFu2nzELd9Mq7byR1DG2fqG36Upn3usYsrJuMSZSlVpDK6gEs8N3GFitBjy+NSo65MwLRGY2jSjtlDlQl7Uks+K35inn4dmXuHT8fqZPZC+XbQibmYdjBuJo3oXjieb3EnbjpqHmXySZd3W78/Fnd3WFDS8qlWeH4sdonZdOFOuJwjMNwuBjSRtn5SttvB4CW/w9vyJMIOEajJ91EB1j9ged9zkIxdNOVEbNkWseFKXTUysSji/nB32w1bNQHvGQ6DEScKEWZnzIES0bGQjl+EpVRM6ao1qisUsKdsFswvQ426MiFPK8fQpB1tmWXx8Dd6itkx8qDoZNw8rHV3FFHzzszF1MLxNGnMoOC8auuXrAQme6Q+n1RlxVmsVVYKd3dYU9LyqZRVfiyWv0e6BE9o+lxkCKBo54/W9q4qJGJDQX4bhlRWt1NWepOeTMCtJuO3ExZ8UUfMFAFtSWOxJ9z/2FTkNmG8gh7hgJXk1pyaSMNsspiZttceGdTdWV7EDZ9gvorHYfmLS9vues/csoqOLwa5VUxna4sTVyCK5Sfh6uWwoLALE3KicIxM6u8A5nUvaCP2mnmsMLkDOhuwyD5B77y0ZllLptKdZTQ25antAjxXLkFZ5Sl+9QqY3IwQZxerA1ROO81ZWMWO6cyqPkyiLavGaSf05L7RhnJPViD4pb2QAtBKM8lItSdRXq9lx1W6ToDybEZRRqylDLPbaSryquTnh1QfRlssDFo2iRqiHQqTVvSlZlSRmZ5olw1pidGZcDfXD2035uDXnVxFuF7o3CpkBUbn1F30tdB1Yg423UHPi2FsPB2HCnNrIyc6h0vcScZU9mACrlSO0Q6x5GoLYnjb3P4/Kssc3lQlMGVRdWkFy4oLOrCwpgU7yNJmPZqOxc3GeBdYXtyTr1v857WEI6Wruz2WUMFQwFgpGw7NEdmYjabl/FPFo7tTaCzuvGFkWRQiLHQhUTK4m2xXk3e2JUfZLOO5Jp768884/77PvmKZ8I37/Ktx5p6F7bJ8Fd0p4sPxeRXzxmGGDJDlKhhKDhfxrMH8HuUwSstuWE6yzQ1MScZrNGPgI8hfruBGEdfWDkdBOAWEA02NGyS6e0l3fDQrrGNDp5MLtmNq0jK63NyTX9U/ww2dAcQiOH6H93cW3VrG1NjBnZ5Br/5eoRTEGd9S6DUraJxFTI1BgwTzY9DxORcDUUOXwfGiMTVpGV1uabzvugI4iP0ADL1PAn45Un2PRHnbFb2lzhRgHgZXmuO4Pu4ay89hfbMdb4tJsBhjG9u571FSdT3kuNWSNZVgFrOE4ASYKur2yKeNbtYudvYYdXi+mWTEHxazioWYe3JZbQ1M9X8XK9qiNPmZ1fW90MmvV9Fmm4AP2VOaZJE8soE4e2DuYsBFZsO2naPzlrgtE3KXmNJGpwGDkrNx2BUEOep1DAYvjkg5TJxdxGFdThsek8BbPn9C+GzuNVrdlQlYVD1UJg2gyy2AQXWGCC7nCGy5i+YFaesnZcLdNSFQ7vj1T0OLmKycYj2WX3owWJml9e0Ssxr2ZbolXjeKJi0ZFFwQO9sz8nQs7MK8IXVLeVNm4b19EJ/dTv0MJiWO7q6/pmaBYcSTTHEvFlU0xeVa2wJlr76Hc8wFPQuYy+RZr8sdR/HDeboqs/hhVd0UcQ5rnzLtrTldQdFUkJSxmQHaanfqKp1pbyZgcdPxtNm0LY6rdcqGptzEPL2LurNhX2Zk6B3UYo5ub2GCvoBrXZgBLqeIZbtMNkzKI0/+HJC0CRNwk5SwJnXPHUeFvINlt7zLi0ifOnMVf4qqZnoydO61WE/8nViFUX8f0Yz5jFg/ugPXZ3m20fKdoox4c0Vnt9tKySuanu0MWjPJNkdLfJNW0OWWwnxXmS3r9SVGZTyqmtnZjm/L9EzHk92kDX2p+ZQ0pGA/osH5lN1dU7/xYMrVNXkZoeLWZ7FS3RS1iJQ4pvGL22MY9GcKjVA/dka64bDoIthXKypFmUdi2N007Kh6MDFv7qTq+PcKVGB9uolgck3+PYrVj0jKCoi4ksprw5XSKgScOWjz4rhT15MJOFQ3XiZNoMvNFwnyHsQPWcW6mHKf5ZEhDQGEkSKFZa3iRprWLhK/XB8Xx+m23ZuA823H20hes4VnDNkbVzkixd1F9EJCWZ+nEOPqHg9QlBIH8B0WsIvhq6rMPHp+kB2YUWMm4EqjETBqB1VuMVzYvhPBsY0pj8gATHjT6YEWw+oF3KqbGvMLZdvezcD+uvE2aRJbdrbZ8CmLowT+C6zbNl5/itK7CtutZAwoLyLieC63Da8rqhJwd9fyxXG1vh8T8LF+3Ewa0ZaZkWMNjBA644Ol0cHV2LAIT/+Z7Aw7Yl84JbeoUJkSlQB5e/tlE+Xl15t/grjESeAZjXdMGhalaVYSlL99K8BJkmM2KH57XeYVLzIx9AqUgitbxetXdbL03hvHjEMwnduZqAK9u6FtpUa1mMIqwLQQH2BB9voijDZNC3ISleAuyyEQoTSJL/rewLisciFGk2RKEBXSIIcxefDDGjAHWOVQUYrKpoemIkUJMQdBusw6rhyIYR6D8ajzf47S6jYi5FJNDjqbFlqHaQXWCj4h83ULjAakcScREq7zwNE1BLOAbBaQND3R26dGPoPyPluLkIY59IBI6OIrCo9o+RG2bJDBmNhEuosnA5NFC3mcZHcXWSHEatO0IK16JQLp9VsdCNb0wKrEG4xLUJC1VwgoyKcF/wKeCpnob9P0czJLkqO0eJJMxS7VCEgGYcwEWGFRcUB9iVPXEpBvYFEgxQBt7rJcyPNsHi3o6TNaltMoOarKe6zd1ME55RWo8msrI65FMg7uEs1grrItjKU4JNUEKJIhmLXi9zyrttJWkFQtELFWiTAa05/hyks9mi2WrIKMejEreJJbjC5+Vd0QXQVoRj+ZwOgSzWDwo89SmPrpbQ0M/9irmF7CR2EN1jyZZtW/cmkIIqPY8D1ObW+p19TE3Rw8dqdt2/DJEnH72KdlzNQzcfRhhaImi3ut64MiyrGwP8pw1k6VmVdjSjwSFllKKRKP2AxIET1QUYEyCKRbxRa1mVZhgm0NKgwiZNH45lBWt4g0YTiE60YXzUQD0t11F6FQcQe0ingm3pKSBLPinyMiI6QoTbq+S/doj0pIeiPW4AYZ9Bq8+FqIUKGXXe2x2DFegc02icT8KMpnar94UQGzeYxFjAKTyaLXK0AKEK8pJySbR7/zjRO5XtwlamG+5QqYLlHP5vE9WFcJuIqKByGbU+n6rrUXTIRd62/s6HgPRAXipx8A3t0LtYFBBlO4DxCpI4VkmrB5DLZkeQ7SWGJ8qBP1mhh3SUK6B7CavEN3ZPFMGLp+a3m288UTcy3lImkORSzSarjaeVcrTjbbCN4Jidem6XewlCOWcMc68IbTshyhq1JwDrMYmTM+gRJtm3X8Is6prYDyZhMb/SmHQ92AsK48wpHhPa9sYaVKjMynTLsNEZ2gyzZeEg8JyzpMsM0MadSRqbjJgiNubWMxb64V5x7DHPohzLOiQAUTBSSbx6D3QqlkIo3Qr1Km4NVJet0uelYeA/TpenpLrVFiQxR1VCg7k7nuTl6ozMKzmS4ne5bJhe5gCnTn1l1vBAdD3AmpFrU9vqZRJXCDQ1OU04FKLavpqSS6fq/sD3PvPhCVmHv1PGoIKrWMq+IiLo+8D2xWEU3ouaQgBwc1Lru0Rs/ro+02gWB9ldHt5ImizC/vlaqYiFjUabKCVkpUAd3kQxCEcj1/mtCty23Tv7ZQSJp1mONOua5i8VG4gmTCAgY9FJULQDghrIB24n6GEvDDR8fk4l3xOJlIDIvfIDPgCD2YiERD14VQtOlkaK+fSOnD59V2iyuioJOh1Jcijy3F2Ir1+gKX1bxXeo3BkVzTKA20SVClOAjzyTsjyi4iEeProiCPEHEStXNQ89D5RspQ8jJaBpAWVbCYORm1lQhIqkQPRFf9HBVmt+uofq76EXKaKTv0cbq+BEWZI6YHa8rFiqeiQSl5Z/WFRTTl3LUU5DSoQbRs9D32Jyvt6nXduaQJKCnMqOiaKL+QXow3mopcQsyRKdQGolLQhs0i7wGTU0QPyu1QQQoWaCIiMJ6BclIMM+r7MRxab7IM4QTEUXOdix2i9c+nGiqwRPC5FAYELrPQGtH7uqqMETyWgCpSIvsQ5BgmCZJxncusgipMVoPuDEsEoA8DOBGRGsfY/jaOgkpcXn2v2CIqOvVuvAbk4oBFckg2AA70ErnZ9tfsBERTF5B3UFlORD6Jo7CChuoqBISkHJq9Cdl6USuJx2eS94bLKyIS5detIAwPNQExTrINuXnau5eL6cHlU/eDze4984SgAvpISe2+dbkEjxA8fQTJ9rZKUiwdBwmKXYyupHbDoQHwpqphNQrpJqvGgdyt679ybvKZ5L3j8oroRV1GUBCKhxp5buIKW37v70SI6cHlU/eDze7NRUJQAX2kpHaZm1mSKBllmEExBeh8QmNBfT9ENYsGECMzRn+z5bq+oSLuOp1F3XIqp6z73T0bDRFoKJFZRERJRxJ8z0qiP2BXumv6to+YHLLs6v5ISvmLXQW2hG6SIXChHnOD6Jq5wyQgoLqEop/KgkJu469AqeioxlcsW01fw+32tESU5DTYmmmJZrnVm5xIqitlyq2fWUF5l43KiyiquTSnoLJZlSNvF7ubdkrqCnLJ+8VnFtGNvv+nIJIAbAqKEFctPUmYbJpuDHNLidL6j+mowsBNQZZr+iqjhCZ0Hk0PqKwSakR6MtAgAhoMrmaGZI7mZqiSM+o8RuNY9yUMT9RYYmJwFPUSGvQFW5XYoPKZzHWqLyFEBwUn4xAxgR1oU8ep01t0hfnknRFlF9GmvUCsoIsQamQrbl2nSp4yOXTNV0lRYxpMJTtFt62v+0AxcncwcQF5t5TlVO5g7JVuA98wcR0K37DRKNncjTckoyAAqL5/w/ifIxBwGPNTy8yuU5DYW6T0YrNo5k+fUzoL9eYyFmkaEijcSvhMJo1XuJBYE2Iad5E2psL1BWrxfVSA9Q9Y3lMxEnjS6IrIO6cpKSIbFQ9CQTUdsIydQhkXRaEurvtwFXIaigvoOyosp6KfhWRS1yEgpXSMQlHyGz2gxuQclrLs76DwmIQdVjS63MMRSzSin8qikVN9TqnEa8Kn6CQehTQmCQQhVpTqqDK/vEuqYiJKSSLEKIimrGFkPVZUt3aG6gvZddZx2ljiuo6Vo98mORCTkpDJIe/XMKPM91J4O0OFM+bEpEMMXfeBi+REGGbU92GQX0USvYomhhT5zsmo7Lx1bv1gtRYFNqNuD8zk99pPs1gjGxUG8aQ00lyYUyUgRAXEUmEY9UopdYSgIwvtvvF6FpLmNTgrMmAky9OnidmJDSZ2TQcVFskjeXaVCJGWEssmLgiaUj7JweWbyUGA5fBUVM1LeWaLTqpmpyf9ppqjXMUK+4Q0r0W/FNYKT4pNY7sY+pzp7wgxGRVuAqL8YX3npiWRONSfAck0BbWdVZdXXLWShjHUE1hT58jXrxSxG69lERcF09sBRjE/7dGEQkEdx1IlIBwaIFKozZgi+DxRB+S0nTxKNFfuVlN4wmmmbIjxmEqYyiN4wCBsqXTIBtm0JKBze0cOoLHkMqpufyiqKKKomkZbMIbQUsAUSUFpdSxZ/SAYN2G6yA6qJunXb5PSXlTRr+RjjsmkSpSqIcN3dJyGYwDhRRUaaaaBGTRBMDrMu0RjDZHDDHGfFg5zwYnO83J9W7tSfTUu69x5paI6BrWViimbZ0T618/zShItxkKNY00mJZzFGLVxQO0HSt0C+aBpucRD6TRcFixlv6mAN9M7ZxLV16tos01AH0peTh0mp75LwwLeFGLgRLpfHyzfmz5dWPzBvQsRfSQ55R0SFxDRhw7cr6CQBHCCKyJ9zYolns9k0hXFMm5NlkmXasrOX//Uaz/SItpZISupmG2KIwarGqYmphUZXQg4JulmIhrzzKfmFM2muEXn5ShGpOafN7WhvKLyqU7hJC+GKA/jtGXkJNAVFUa6kL5/oortoKto5IM6Wf0KQaErYt9ZhdAIStRpBEj3pMi1wjGBzyTvDpdXGCyLeuVEFS2LwxrZCYF7J0R/CdF2ssoLSS9bmfOSAnzkqTl8DeX6LM82KtKpsqtkvbSUeGFh3nBRLiNy6GlJd5VZEI7KbNy3vkxgolHAI5OMet7mmnvqRiC/FNkV0kdeSijTBg/8qKSaAlck37jXfoKST8VswnxmHVOxmBOlpmIt6pGe6+GDRTx9pHnlXZIVEdFp+JyQgk5SUAGtmEea/B0che8EXQueJhI4PJqWVbgrGkIIvYhkjyGp3CNN6xOxqeBNpwBuuMOHjq7PU1jCKFHcCFAVkPdcWU7sm8s906R0z1XBj3s5RfgM1TX/hJSemNKy5h2XQZiQ2PQSi2GNAqprB9U1aCb9qJY+YqY4t7zL8kLSWJnsW2AKgirQRw4yplvBDVdu3YqtXanHWKF/fVsDYCfPCKYg79J+fYvf1NxEzYdf36IsMdiWVZR8ztYgKdqE9vHZvmTz5dVqG8VYnP/P1etXz5skLX57fV+W27+9fVsQ6OLNBsZ5VmS35Zs427yN1tnb97/88u9v3717u6kx3saDffCvTGu7mtDcQqPNpOLw12twBvOi/BCV0U1UIIqfrDdcttUmysuvN/8EcUm8XZ8ZWfJrR+W2wlaPoY+P+JHDaVifbEvh340rJa4Rtxq8wU17ozok6yl6hjqJBQLpL5CcY/HFEcAKTZ4ob6dO0/zzNSJHllSbtP+b5T95afzvsHz9xRzhA6CefaSBBgkWeLDYJtFLHVOXQRwmWWM2K70AskkxRzwvPoDbqEpKhvj9Zx7r17cMB7C89pZjNkYCsBxsxN96q7EPz+vQHeaBPeQ4c6NvAIszTDFH7DrCAg4SHPCIbi0F7VJt+PsiB2hhBPgNC5bHB0nL5fPAjB2Ck6di3aZiFoT6bDsNJIhcojluu9wNAfuvVuK9sTwwol34gLIJ0gVAih3SLO74FYhLn3KxXd1neSldcflUc+RatNIbbhqZTzVH/gjX4Oq+2tyk5DlnGpZJMsf8Dgt4w6oH3UeLeVLdJLC4Z0Uc9XlOJeMkB/i946/ptzIeIg5TzBG/bdcSxGHK0oR6EFHuLsAnFtu4JxLR3SZZYEY56hmJLNk9nc2BS/JYT8jzdA0f4RptL5MX4eQcZtgT0VkliRSYS7TQ7tYbmDZR6BnNbpBiz19gs8Uvqct4jEq2ou/T1/RjtgEX3LLJppmjfgZl9B/g5amOUkxjDlPsEKWjxSXa4V7Bkl2UqM8WI58k2VPnkEiu4zK9F+ewqGG7zbNHsL6McMz2FY4QO4Dnky3malZq4MU57Ft/lZVRIqaPMIdTH+SVSDNZzJqKGKyusqOY0Y2GKeaIn+AGlrhBZC1jWswlWrT0oWIaiD9YzA/qpUm03pRfkNrHKkqyPOa1/I64aYhZf7HZ+fZh8Ybb3v67RXuaMqIlnU0zR22er/5a3mM33tbFicYW57CuoVcGxPiDdIv5WyG5FeFQ8kgNOFqvGTR2LmtzW9ntOn9+xnDXfbdQ+Zsy7MjS3y2U8zSppyflrD9Q0QXpNvPvuZ5OX28l+OIc9tQ4fd7CnBxTfIheCjFl2Dz2tRCXB4IgmlvyXBYb16hgr4AMNq98soU0HZRkW8+nWrX6W4H26nc5ALzmyKda8Cdd8AqfrwzZk0+2mZfdeSk7MakEG/nVFDp5iRPwCaR35T0rwUQ5XGu4ADnMuHGU5XGohSgYBIaTxKIcNpTH8atO0wjtzjihOEiywTxDfNDGxWJBh2k2+6E1xDM5StrSJ/f4AJXdG8ly2bT/Kno+fUb7IO58h0qwwTstONqST+YYqOITtCm7y/IXls2YJCudDO3HztNHNGNR4frxahZdmslC2mGfbbG9l0myNreR8kePEUyiG5hw8PJcbjWJOyHOYTEOMFWg86kWu4Hsqe574yDIDq4o3WqXBG9fiDXiLMvb9h0DtDfldkryjBZHEVH8kGErKnZe4I4i2ETL/bb8TgK38Ta/viCvk1iD0dDCTbURj7s4h20N0bOuBjaHJd3AuikK2aVJkGw16/C6c1y9HFdlyTkXcKnWyD9gcZ/AolTAs1ksKFPLmgQgdr9A+zD+UECcw+JwAO2GSFEYMyvgMMXGOshBWWN8TdYCmP6rta3yhLzgK7BS1gkWaxAO4xQlgtYNU9wQV2WUY/8kgIPrcIcr6pxuNZ6ma6P62HwWHNaYF09T7CUiYjRRBqtVDwueAQoQsY8yo9UO2LA+VUarnRh9HZvZhqluassx0XoNCmFEG0E/tJmt+tK9j3O03SaQ3SsIM5jj/wDw7p6Z6O03C+oI9nn2O7sfcM2CNJ8s6CXoz0fr/nRrhFq8KLI51KUSLNJMcx6TBz3EBwngPJ66j7t5dK/oaxcPjrOfMWn2bj3Yi5VFZdNsNP8CNMuZwFDBpzogfwZRUeWgjoQjRB/kcKjhaMN7KXGJDrj4hxKbzmDr+EUOL3lrH5do7VBWP24kNhJJsjjWgWmgrqLL4dyLPr60sid0tsU42fQv3/l42cje/TNws5EXHcfPBv/r6fXetFh49sCkWVnYZZ6HTNJSXBrb0iLxw6bZ2AsbPYbf2QxSzBEbfYXDo79bn44WJ1m1zVJ8P0V4OjpIt9AdJKhuaO0okKN/ovzL+HWYw9LjoD5jqyPLch4HdOJipF5jMofCJ+vNxV5reXcQe/KiSxV72JuNR+m/2iifYa8NHWcl2qxLUQXJNmZOGHGmTfLJxlxXj7XM906UfnCTU2PVbquyUzE+1QIZEickDrL/bNPKO7CC/2Itq91XR7fA4iqrL+GI8XV57dv/VXQcwiVampsvo/SOXTIGCXM7oI5t+NodB8Almln2+86FnHrkjul3CJ4+c1oil7gYhauRnp53OWoQl7scspLjKFtNdccwjfIX4TLSJtmcmGxAHbppeDzSfrUQOyA7gwlIOV1ukGDj4fIFMGf+zaflsF99bhiGCwdY7heLpubJgNdCQ6pI7mJ8ZotZs9Gv47uEMJ5RgB52NCXKOKzVVi0zLrihUR25rBKweilKsOE3oAbZrQ7ZxfasQYLFzhQmCbb54nL8LoVPtdF4GwdHMbQg2cbEVZRIDye6Ih0anXdxledzqe1CeuFAlMOphjoeU+3HKK+FyeVSUzsAwp2TIpvbKBFhoBqeLoOF2fS5zCMsoBiraf95OSJ58LqVlyxWPApmIoSVxZdq2UNls/wjeCaxQli78yDJellfSCifedXOQNbmIZqL0VmLsHzVU2bxc7P1nRdnaGNf9RfKuPsKbPL+cyt9ETMIw9KA7kyrRlk+49LtF1yzGKQdGNiJgcNxrifLzsGr/mpA6GM5mgqyoy9ZnsPxlxrrcEi1e4dUh8OZfTmcmctrpZktJMStr9WxUQjquCXuapms/PIVMoEothbDde95D1n6uwUaiYfDIDXfbK4fJNvbKvkHKMitcPYeApNojfslk8N2aeGnjIa7zwv8++vtf+NctIZz5r+7uGoNIabi9bZabqdLfbfYKGy7p6sGTeo/22w62tBH7G6j//5zCnZLGXycZHdtKDd7vlSWHokn6+p4gTdIsDgfQV24yAo+tCz1ffZRGqx0jaRLBe+72S6aSqhR18+6fskq2iea4/6IiqY3Q0j6++zj+AU8Fe6zTVl63NnGKyrDlPnmLyaKKEg6/X32cb/IkuR7htWoSxBnOfdGt8m81UKMNF9RvUdp8cRrAcOUxW1RPP3qJY+xWChrU2tpv1dQoqfVKVYXgfKUM+b1Xy1OcDdc1O3mk41xpSiwBY21qrRf7ZHOsnwTCcIQs6n2yKuIfZNjmGJhyAoefjd0WKnu1Vfu5Qc6wQIPBwFiTXTNt+nNR+dF7b/TPSs4pBiTaOE1I3EicvMW+hQhDVW8r2KS5twN4aZ8yu5gKrwEx6faIbfBo6TgXIbFrFX9m8g+a5XksWeDpUpacpyVijyjxbM+9dlu5Hmo/uu0axQSx9sofeE02vqjDY7Ql87NP69E/I6+PcI05mQ0l2jRRi6Q14lt4K6a7d6x61r71RrpvRDpvQ3Sf8Et3u5HCX/zlUmy0ArusxSIIk0PEizmTxsil5k//ecppPxcej2ZA973ZeuZ5KLWy0ou9TCdHG8WjYev4OSzT7LFFEfNYNMszkGesk+gLNFGssj46cenWiDf5wCosAXpFmNUoc0VjIXIbJrDnbzvEesCPkiZ6Xh211xyWoZsfal9pMcQy0GI6ACWKktGemYz5JOYdykidR1smT1hGiYthzNpHcyTMWkoF75Ulx/LuBxO2Q2wXN7c5OCxfuCYWdgGKQexa8Tc7fmZH1+3KI5nk+Ki43Bz+1w4F+Oa+j6ta8hxtmY2avUXW2+62tQofAKqS7LAbA7om7InvHVNnMNCFvQPI8krkWayGKPojiFK/cVqi77oSFM/p2frWF6SO7flbkWXnwxvURxkuLzoUhXltsUnVUKu1guXgi7R4kguhX9UYAUEO00mycKclER35xvUHhxyQWCW5ZMtHAlLxqRKPsw/B39qnay+77gqc+JFUGRV7rvvECG6zHMjmF3Q29rW89NpmGKPKLgayyRZ3dnKs81FUt2x7xUOU6wOl7Mq5uYV9Xkxs6D1DPLj/BbF0Y9LXHQXODzEzoS8d8wovPUni9WrSpglpv4yz1qwdE3+sJNT1bM8jf/n3JHNde2882T0vNDU4Tg6eMoKj7Mu4BpFTp1T75naZ1+/ZyU795ikPdbNMeH9uc+R73ZTE/HnvNrprpGaIn+8LmkmrWKUoLJkwf+9AkWJtHM8swT6wDB5zoO9pWtWvrrDzG7yOFpaGFd5jOThLi8uvlQ7n/yFYdf3hSXO2U6u2ahQM934eK1s2tQO2q3vNBlvoVd1nTK2S/Vc6zzIN7AokDJd3+PxXPMZNJf1Xwux1DkY1pW+C0k23Kd1XxfDQafPJb6IkxxV5T2qFMbEMSEEN6mQHTjLDm4cLgt7hT2Eq3hLlfM1psktZPUkUbo9eiPidZUIspnX9RUP7FX2ABijAf3dEu0ojkFRyDAHqRYaOfZrQlqnTF6I0hcz28+yvNr4O9J0MA7zWFF2nEl7lW1hzEJ0H+ea/PyVZdu7yucX4ggYF4u4pbVzwYgIXxK2CDA3CI7r5JAUHmd2kBpZiO7jbLMDk0D0HuEgwdoLXugAb2lcrKUna1dsvpoj4TdNGJjmk40BrChF0Ubo7/ZospEUpdujk0jSQtw65SCz7GRW5C+sXOXUpCLq9zyrtkI51aXs8hWGL93aw0qW9vMcQgpPTaH6NEg4CCxDnjnEkBxJbSMiIIDaRnBcxaGk8FLtXcu/hHXg7teEZn6MXZPdnqcl5cZhZ1KZ6Db6IMESjw9hRH22OTIUvEXj8P5M2B1Sc9262etz8Qa5VJtj4vq4SQItSLYcF3w9sOJwmST79oph+VQLU1/9bpIYmEu0xq2v7UrtiLJM9hx3UuU5SOMXwTPswhw2NdTlLiP23H+YYt/m4cGibM7wuSx24FEpkjjUZ1u+rm5KHC74PI0T1DARe7M5HGs4fdbV0OWwr4GEPO6eulP1RZzTs0Zl38Q5bWtsRIKib2wOxxoUfWFzONaAyvJzT5zDUT4hOQ+xLhglZwAISWaQPUTdQmIaZA9Rt5DMBtmtnCxwEe4KaPvVkj/EXOfCae1sE8D1Sbat4yOu099trrPcVukarEVPXLJpNqhPUb6+yGBaFj9ADtDYsg54kiwW69w9iB+yqjwqyxzeVCWQ7sfUOT1qLP5zk2iqqrPYr94y10xRuoXLUOAYkZ3mvZVo5FtrR0i8L0BCF21QkTg4QSwiUjHUOW2cTPI175LVf7VD4lXQ/qslkqDPbj38HBUPYK2mpiyPXZtPHh/f8y2uv9ohnT5vYU6cUz5naXnPg3IZXPH/ASIBldl0Nw7+AHMQlx/ADWRv4cgy2ZiKumJNzNOPWSIwG8ly+dQk4iB5LqeajqP0gd9qCTM44/OTVZjBDf/8RA6N05xQ24h3MuQu3Qn9/CZizZhsov26QHQSYdRkcQ6LmVYhnTGH/yLT9CqP0iKK8U9uYVPk86+NZ1J1Tv8aL0FRsTGtdXltpOMWR0JQ0FOcw6cGUY/kuaw8FjotT9EhRTabSyZ5fB8VQGqFFWaw2VdB8R2JQYK9xa+Jeye097Vp9qh4w4WD7VZlvUYrrXTGhWwOZsLEHF/S8YnJTusSbCKYcjcgJFkWc0TTboK/FWg/8xEWpf/D2AJIlxizZjDjHOh0VkGWj6nvlkYD4YGBpfjetZhJv8NbsqcJyFwCSBfmMoMZh7naulkU+ruFVCvA+gcs74VMxiXa4QrCrFCffwLGDcOrHvw52fX3RkWiuEXwjpQ8lz33i87F2DQLjV1gQbW3nJ4XbQvIVb6I02OEGez7jjaLW9H+RZRuo43EcAsRN/F6HpPkgCm47cOmWeiqIMWaOK+OUt9t0QQNHCRYWO1AUXA3zLuPNtzUkz0jN4o4bhJk2GOp2kmMAC5BjuGmFGVHdA3CFUrcefqk6fXJi4DPN/+9igiVh1D9VwvFI4UledJdeFrLpzogC89i+VQbSsra69pWeTtd2yg8BHY46m33QPXyKuy0JItrHUIySLJYqAzao0vfI0vFUaXzEeWH7ClNsmgtiP3FJNksVG1RqdojyGDhi44YNi1Ai8E5uvPJFm1H8vMHgHf37Kt01HcLt8QqXScArzKMQyL13Vq+nuDboiIJWycsa3n+QoJLeS/PGMZ1eRaXHXN59l9Kv3CRg764BQu6yronU0Ve6HTyHmuKtNU0jN2IR3QxGxmhjMOpYf2v6y4wUqn5ZotyjFLwSykCsC7Jzu4k2s3T36ffy+3cDMInO3UQU59Z06I4vu405evsoST5VR7FD6hjooNENs0CFbsGivSUQYLlaR8QH0uyaVZnd/AR5OLXY7nEn2D2+BsraCSPWTSlyaKrU3A23X53MICI56a1/dje0DCbDlNWeXoJ/iDRIz2VFwrKSW1Rlh+JjQJdPAvNQGEVqXB2r0sQFVl6luX1aLG2dibRBpcMOyB7d9bEwSU648q9K5QZ7cdNHLidT7Xh1Oj29gsfcJn+bmH1WW9gKnS7G6bYUJqavuLrfJIsu+luM9uin5E/T5DwDbHwD9GcFn8dxLIl90WUN6oLf/uUTrG1z4kQhyk2yk5PY9GRtCh9tk17wPOaMWzTba8QC4McrMlBhbjfwxxzrLYHedfyzWoLYnjbBCTtx94vFK8K2iUurx3eOGIx5PQT9+TrVugkrclreUPmDCZo8nGOqmyalQh9+po2ZJDF5h8kW1vFd+fpCtVgeSoUCmQX5cIKbiRFQ9gGM/6fPODQfnDiGDwYjPt2KPzUrnFDI3/Jo8wBVnaE476MCwvPPdLzjsv3KIdIie1mwkm2uYFp5L9q6PHdx9EKdKQV5KFiFgv8wcIjt2Qf/ay/WJwwRml1G8X4xlyOtrXCG7qyPJbuYAI/MCs3HJDesRe622/mKD/gmgVpPpljfBQchH20d9aJCkCIIDou5hIdcPEPJTadwUILLwp4l6LdLyQccc4+XihKtz/Dw+E9uetLTJr9xhs7SbKobJqN45noGZ3+6/6YRpAEjR/E1gsmyXI397Uqv94SiCbYIr+p47MsffUbZckLt87tosGhKSTdZInSLdwbwHOJPvEPdlHf7S5O/FHBXHRfov3uMJ1PsrTMs0Rk5ZXl2f8NQdg5F2CyTT7LjhIYcWKTfJp0Q+kYz3hZgpvcDB1Fetd3ToOJcAncqHKcbYNEBIuy7Ro3o7JZvvqjinJQXN7dMAcsbKLlNuho/c+qKPnjdS7RYlNDNh8yYD7VZi27yMEKJCAW3KoYJE2x1sgQyXQQLYyDBJsbH+kDPt4T6i9c4h46f7WXWTwjaTQoLtEzpEXHkXFtffw1x2GKlSN4W/RbzuzD2DT7dnKQnnjHMI248GBMmo38TEs0qoLoiHSCOd4ZTEDKSfX+qzkSfiwxLTgthfpsIxu/gCdWJpJPi5nHVxDkZF3xm8gdjMNMVpRd/rYz9OsD+DlkmTNNmzaHO4mNfXau88D61XWv4z/80+W0T1xu7iMf6crDLTd2a8KqSE7T6CZhFT76uwUaiKsccG2iPlsY3vmHtT5av6qV3WWdaZrR7IZJc2q0H8tNcpytX9gm0t+t1+PWfv4FlE9Z/iBcmrk8yxIAzacAcqDJ5ioOpMXHkQp19SxG/9UWiZcy9PfxFsi59KB7sAHEMoHEl6cuREO56EPq8uOwD6mUsW3Xn6ZcmATx2Kxjse0Iwx1H8cN5Sk6t6CCxfqwnAXVgQmOkcdgxmId+QFU/rK/7zl0ZpX08rsAGKS2+ZyoiRAdWNYNZqi6OX5W+iFiPk/7rHp/QnaCBusvylzDcxKI5cJIe4sBFi+Oi1ucyCBMxYB4uoAcW2h0W+h2kIIdxIFcBFs0lOq4WYumbyP8AL/WDywOk/qsVEgdy2FMI+fgoTi5BnPmGde5gHDhXUXbpLBvW7jGOQf+8OF8nnDtp/W0xbPgtD8KGHYwDGyrK/lxsuEoq5h5k/WUOH2bZq4Pq1wbnMibH92BdJeAqKh48jckUkosxWVl8qdrkCs2+lHX87z5a2CU5DwFb1wDhSZXLMVWZbb+mp3me5Zwe0CfYzAYSyCQvOQPQMMUO8TRdC/Ha75btq+IYFIW4hVSaZRt5GlKfFyMBuldLPbWpFsZFm5KXHWfiH63XORpUzn28/2x1USfIQ1mi5Ue++MxlHQVRUeWg9qT0NIvSUC72UHX5xa4Y5DU2tLl7wmrbUMIOk8wxL/FdzSFW82mPrRnN+H+Am9pjLQg3dmjuDKmAOPDknvPkSZXnII19g1w3MC6RreVFl8p8bYv5Z5SGKVasx76bZP82BmavTxlK5B44HiTZWkrOsnwTlSVkw/bwqTZe+huIlI2rrHENZLz0mUSLU+XqJoHFPavbUJ/n9AxbUuQvaa8z/FIo2pQQVy3GqXyQtBgRhliw2oRz1eDgHGSaAcY4wo2uk78QwafO5WRBCMRCdR8trBDZFsYsTvdxj50+LnLyGk7zoIDv2SqN5XS0qgYYidcDuSKd5dlGxt1smg1nyjCHKVZz+58gLrkpXX+0aBl4FlyWt7M8X4JIcEU+sjwyaWwOxy/1U/YsIJfshN29dCiFp3LsscQglhmU8Cnz9MGmgJzOIRWlR7Kd9VUKQzDwybP5HoqCdzvE7d5l5sTIwRiUnEl4MakYYaQVbQSrhv921/acZjanxc02gnee2n+L4uSkKCu6VENGKI0CX2sagtRf5twa+5oYZuJi8mb0UUweGPXjZBrJgZvVxcfhaMFL2tZvaDfGGX5yDBLsLi7y9xatjFJZzsYLIV8szDwFyFOuQ/1Xi7ZERcEvLv1X21VhVbDj1X+26t8HcBtVSYlm+xpxI4ySguusKMti5m2z9w10/2II5nSyo0FY6pJ0HMe15KkPtlnBLUjexeUunOsWLaY5Jzcmbe8Xzy/gqfgEyhLk4czBYkyHKWkKNNLMFNbOm4hV+aZdyO28S6ZTLHfE5fzvFajAmlDdbwJQQA5cryw9Dqtf5DDLIRtbpf9qZxnm7cG2CPyi2H+1sSezVmS70nwr2m8Wx9Rgm7ywDek+WuPwTRokWEzyE2Zqn1iqHJySsZtqxQjCDs3pK/IEOuMv3H22wxI0jfo8ldozl4XqHsQPWRUq8i0H52Kz0mMsdaew5MjP5CFsvBG+qC+dirHluWzafRU9nz4DjgyDBAuqRs/tZWvu3HKYZKHJLTwOdrhd2lIES4BoyGLMECJm0gjIXO3cQZ0ow5Ry7BBZ2G/2zvbIelzl+MHCi+gF1/oRFojxff1oxagubrXGSOPMO7Z6Fk2Ubo5eP8DOQHYf9/icnCVbWG4LwGaTyfWXOAGid6UGCZZ4FyCH2ZpbIYZJNpvuMkpIaWb7NEiwsTyh4UMDB/g91CBlDjt0qDtl5ynERzvCCc6m7fE8J8768F8kWD3uiO+6wuK5rCgGGCMdCwe7wu920V2KRm5UiAOqsGkOqBJAeyxBoJVBwmK4/hLgw8r2gQbftY3GclrZ1ABjGa+bAIjv2I1A/90B7b0E7f1e6+gneVYUaDeSBOEoFs1lL6yF2F+umm0l9XR3dnNzntC9GVX1CTyChFvUqO82psssL9ubHIy5kk6xOAOrkkQIOEiwUBG3jfcHQ7T+81zu2xeoJ1y4/O6jzeb5FuQ5yDmsQcJ+XTw8y8EfFbkHzByVdp9t3OhJQPyPUcFuDumExYgo9KGE3pHxaxCXOEaykks9+fmJAgf2Zy6+O0Dq9MYl9L2q9FL5ZOf0VXJx1XOYMYTLAIvLjTO09WUWfoDp7zYmozip1uA8XUFUOmI24nyq1UFH4xmNEkrxcQefYQ5v7CtYsmEfmk9T+p98BmXUXFFilLNhih2i9IFaLtEOV0Az6vN0zrJyLTBF4umo+AHXd6BkFcFhmsWxICnxX1nKdJ3+bov2I4+2jWolAh0k22KjXceTYKS4RFvc4wwLf3ZCs2lzeB2SDl1Fd4LpTL5Ov1wdFUUWQxwLKeXfG4UF8bS6PtpuE4g5nVJYVC+MKsuxy1ObW6aEoDVyza/+0gquV1mVC99ENFrtOmjRsoeJ2bXFs5lXUY6nt2MzW2JZNvPXt8Lx9mGJzv5mxRB9KRk7SMxoRmRu0RfNC10j/TihgZmaEYiDG/oTLav32fr6EhRlDrGSdIJ7qZMQJqW5veygTJ3RRFjo6/JkE6aCAMxi0GRP8VFTb2qmaU1v19QdL1XgOT43H2GuztPaB/W05VE9h78DDDDwgsb5DXSDM9tAH8MkQZ87863BaLNFZENuM9ZDTE+aMmAhh52B9mNM64YhRX4N8Xi+Oi++VEny2+vbKCmAZe+9mecC5BtY4KCl9XMC1/TTDUoe0pXkglsx+ensBuylrs5z9DjwAHymabDnmkJhzSZvjFlFVkImbSxZQwy/xJVmX1jgEjxF+foig2lZNA6x198KsP4By/vGGKzyKNEW5n1IuCIGfKGtyHMEhlgB+ETf4CUuUjoyhBM4rapuo+FwZUKoOAyoJx+xaCElDou9RAbS999fzWk27s3/V9GdWrcRZeejdZLEPo+JFsMD+6ouTiYJ48YFsZcgpOArVGtrxQbxCKYgZ7N0xtzmS/d30X7AAxrdgc/ZGiRFXw6/R7SJSI+KbRSTHfwanMG8wK7h0U1UgDrL61eo7Y8QSb32yK0xuPyRnCR1HMw2w+cohbegKK+yB5D+9vr9L+/ev351lMCowH4Dye3rV8+bJC3+FpN5G6VpVpKu//b6viy3f3v7tiA1Fm82MM6zIrst38TZ5m20zt4irD+9fffuLVhv3rLFG1gjlF/+vUUpivUgyAB1cNqM89+riHz6lkKGN379D8BxWjvCl+D2lUyO/PqWLfirgLdwE357DTFlyWwkz0gS79IIh9pIcS5AWvb6FRY3+FJoJ3LeKuHr09K6gvQxyuP7CCkin6Pn+g7Eb6//8os15uBQzRi6zHkXDA55GIt+DGxKj2tIbtl7HBmWRJpqQW6gDoQ+YFEyYCNYjqt0nYBzNPO6a4dnMCnZxXxnmbLvH1MRO9H/dp6uwfNvr/83Kfi3V+f/eU2X/bdXZDT/9uqXV/+3dSMGNzrd2WF4b9UPi7keOT577Qk/XbSexbbM1BX04qSans6NYIp7NaVdwXy4sD1majHWIIabKMGrPvpVkOX73V/RKGIh/dvrPzsI4vp0DiD9EGk5d8Cc1R0WuD//Yr1UEFdmxSr33zbR83+3Ba0lOBXcw6PTH5HydXVfbW5SEjXLHeg7Gs+bxGsAqNdT3EFCLc5Dh+WGf9GXEmIusQQb+ipbgdmK5P0SxG10DvdxvIhy1ARyOa67Y+WH2HA6Er8QbV2qKElephQ7RqDjyB2UOzTm0XoD0y7wfADAlnGagKR+Q43dvr6mH7MNuPBcXIZ+iWEXGc49MQAZKQfFsI09SpLsqTPNg0cInrwWsKPtNs8ewRq/oJferaqNz3h/ycqgeC0YuYfPdNazfaEgm/hxV9lR7LX+c/6n7lCrhyr4FInS6jaKyyoHOVoRyi9I4+n1g1C1/I5YJjTmefE7vC1PotxLK2ox/NfTJnbY1/Ie5BcDbzu3lrWxyLqlWS0e3/3iIHIqvHEqYYyX6qP1mqnSq/nnxYfsKU2yyE9pbTD8huZbmtSTsIXz6hkiej1Lvt5yeE6bwQbk9HkLc7KD/RC9yBCNdn8NIIkhQgD9uftjVKwipDmAEKM6RLLfzrPlZft5E2KhjuEw/kd3OQC0ruXSrwFQ/aRXAI0DB0BsQvz4yZMGZBCfx0fe0XgXXXSeAIiD6Dw+tj58Pti9k+Mjy87QoLaHjV5K2bo+1IySFu3kHp+TuRiCDJtOBaH0ocBp4UVCJnSl+6AihQWHiUkf0RRDYLXvredmpszih0BmPWxiIXhHjxFEZWFCYXqYbYRtdFq9YBqsv5+yp7qvzWNdfsOAdHh4+0L2vWdZ3rbvGKBtkQ/scRQ/kNtE+LTW8yACb9Ew3nlNw+EbBx6DQpYvNDJwU21CDEyNFz2HwiMdB+sGCoLwqihic5z5uHo5rsqy36s7Thmc+wcs7hNYlP6AzVxOAOJLJJgHJlQXvBOkbxMoGPvZhWmA4EvH12Q9bgXNNuOEvOY0Uh2rLcKJEueOGJ1lUHV0cQCv4AZojNq22KfpOiBya2o6TUuQF9682EivASoYmYEaCTdpnWi/cAVBPXe9ZD5aPEFRUlG3NzcwJTueUZkVtb89HSyaS24+vaijGI9G7eE2JTj8D7geEf3juLTpVqXQMqcDDitwQh1AhjkNbQOjeqziCzkIFXYOPoL8BQ+cvXVlWNrHtkI7+9m3Y1japx3HUQGaFdTbFtBhfQZRUeWAODKGdaPrqjjajOkk0lWDfwyrcjA61i42dYwEgeXL19rfePCAHPtUhbDEDAAxGULire6z2rgT5WP4MHbuQ/vh0qA96H/vcNDf0sjf9P2tAGH8qOReWcF1g7YqdwliaDmjo577qQeNshEAqTnGKk6yapul2AXfa5nnUGRGD1cvPXJCTFZBP0btcfDSbWE6NxY84uAv+yt4XDyMsDOQLbARtwR2ZDnOSqSBBXdbwjdTQk+Ulu3C+C4dPI4GHoBhjmUuIPEhcfAPbwv6KPjYCW0F/wV8ejDwvyqusvqSAIvsZKZtML4ODwpMVB0zxz6kw19G6Z3GLO/mpR3IzS+w2WmBzllLMUvsm5+3iEjkcth3CJ4+C9QyAyFr7jteS6c90Xea3hzDNMq780BEtRvywW1xQgOIcTSSx36Cg+wMJiDVKVJ/cnEc/AKezGeY7T2DPWOZee99jadWjChLre04zYaVf71tZ9mm7ZmD9bkr6TtyLGkvqwTQ8a0DqOarLRjdxtJE8mpiAfpe22iczkKA9fEOrzI6pJAU1cx204JeSP2enYaKAq7fSaw9x0KDtxSW6Pqe6I2e6kXh0+cyj3C0iTH1JvoMaU9Emtam4xDm4SRLsvwjeG4ezwgLvrxID7aq1J6ZG2fWpSSGHoP6+5JeDTgvztCWsuovmEy/zbXlQPp61IELQ3AhTVH7NgxL/zTcuIdsOMrZS3DTO0X3w+FD2MOHw4GBtnW7d2BwsKdLcZZiTzc/7G9jKjcPB+3HukO/K2qpBXcl/fSO7SAIbbhpfV60IRD2wavVmk33jD9/r2BXUZXCPyoACeQtxGLI3oUt1x6luFzbOt1Q0bGCofbv7wXQYFqwsyzfRJ4RjlqsVdTHplxWlJ9A932Pbm9hAr21XXIx1EsVCrCAnxe11f8oHsTcc3I7CHt88CkqSuly4IQYVHrj5n3K7mAayC8T47VXhQ0gbU5H6ADgdqs6B+CzrRDFI7drDo9g3h7jJVP4DNPOrpgkvnbQORkMTLg8uk3sbLON0pdAWKLTPxMNuC3oNUcQPA7F+gjT2GH7zxT3ackJdSvecxUn8+ldSLD3QcD+C24vsgJHU3FzlBKoQPdZCsRRwtxiJ7bRlOZe/8w3HKI3DXdWemrtsO8c7LDENFY0S6uXHkqAQtz3unrKPgFMrPMi017meG/Ld1f3OQDm+H+yxUdTBOQwZrC9whp+j7xU4RljEhvP1OEDnT/LhF3CwUmwlyDuUkS9OkaWhTXZnENobWJPGGQ89W7GBeXmJgePMNI+iOJwu2gXJNlxkt1hRW5PWPRTlN5VOIybNY/2Jb2YVHikyUk6MwtEttbvIiwOFGtjYJDQzw3WieJqv9mU7qMrh4Ls31HzdcFd5BXkwzF/+APhKfZ2rXjZEzk7ylrc0uikSshFF/Uxpv1ZFTnsWoERdmlnSXR3vkFNx1eLtLSx94q9LPfrMuK4Ok3tmLwqccTfS1AI3rLc2Xk3u37T0lPL5C6hVVpwode6m+qDIyxnm4ukuoOySJ2Gp59ZFcvZ3uPM5At4KvboPbfZOTSgBk7e9AkDhYN3hEEKIoGXqdz+TPuUZeivhw1GC2t8fyNLkqO0eNobvzDcIQdnaVIquD3PTSS2r6Z8z0q/hx3GvvuDiLYnXDP7Mh+Oe2rfq0bmhTnKDrJEBwuRQ5bV3ytQlEiLxXNkCV7ry9RAnFdla69ifKF9T2SBfi/4l7/YK8yBHsYJ4rWKQBq+xxgStjfScL39VlunU8I/o3icqobOR3EC+QYWOG7FJYixpN1f5vdZh0aZTN19dz9g47E+fcZUjZKjqrzHdI2JMrBX4z77dZ9wLqDtaJ1T90ACwjaSMzD6V8xbV9kDCLONJHBHcQyKIhwo8f9AA+zl3W886c6yvNrs0Vn+VbaFsf30aor5RbSYe3IbvfFopi9cmNwKdImdu2v3+cj8INyxJxOEdMieRZtiOz5B8Djimj3jltVOq2rboUOIpC+1JPYyRn33fWgc3zjBrfAM/t6gSIbcEY3E7/LcrO+mBNon4fN7nlVbRwnUlB3XJdTpgY/AJxRfmlXHay6HECh47omUqoNYGcXCuZviiUzLPZFROyEeflp2q3u8H5xG+hLwuiHBCxqpQx3fdTeU/uGVet8r+Z6NYS7Ue9/I97tyTEriS0cV2xLHboXAagIiB4SqL9wFjpbRxaOq8hyk8UuwK80t4GXUn7lqwnj/1XlWDs+o/Kj9PSpDCzO00y7RXEjO0zhBTTUkx5/dJHFb2enzNJVd4cq6APMT9XBQ6TQ9bWTDND1sKpu0Z6gii8lqX9lAjCHBD/HaECVnAIxNU3nNYxNYXvPY1G7ww9wHI3wyOiN2L7COWgsRHaNVgTT1Cmk765Hfr7gET1G+vsjQAlf8ADlArOUZofAexA9ZVR6VZQ5vqhKE3upxFRT/uQkUxadRAiRuebYOY2GCtHVa/zZIH4kbG97G4HdGcoDkyAka/6Ge4jTsCIV26/EbCNykYIop1z/PoNPFA1jLSOfd0pPHxzCxhTDY6fMW5mTn9DlLy/sRcP8BojB9p/nyA0TCrfwAbvq3292cLDuYJrjgxyxZBxorHjwgI1Dgx1H6EGwrxeAGm2I07vlJaMg2olNg2PObKNCC1EhoohSEjBaKvWnQhPhX/Wh0HqVFFA/foR4FPhi7ySq4BEUVKELrSbTFt5zDE4cHDthqtO/sdKLwTb+ocNkChDanXkQwlIc4E/opCE0bSLxXweEYq9pkOIqtK0DY26UcNKh3BJdgE8GUch7Xemtbv674rUCa9keIROjeBO2b/XlFUtS+9qaYn0/TBGFAfoe3RPXeQ9Zpu2Y/eH1Jr/H7VoD1D1jeO7IQU9y7KYMgBsGNH1My654waKtcUKOMVlaHq68SHJ/ju5bU/kc3Ixvdzou2qeQqU+SpS7RgaKO0Dai+X6LObiHik2B6U4cY7trFCqRYyw3VwhouXPM+g6KgLp56Ph7Rj0hGHBjm0kDtvFP2KC5L15+gXiUzqmsX8z43+fcqagbCXVZ/S2FJ3kwb+/Cvq2j0s74pejNJT8Y+m2z3S/V6PTbJhrWNTbvRDtDCH5x9yJ7SJIvW3vF8zosWKohi9AmxeFqAFtPrYXYs9X8AeHfvpBaa1HBcpesEOL79LhXtJ/guYxgetVv2v+AYGfu07M+yQH+hIo343WFs/dqy7km+hSuPtCFyv6w8s/sA10T1ciUlCMdREuHQ/D7qWzG6j0vITVhYuz1t/3B4jowtP8JjZPhYZbM/78vOKMmv8ih+gOldwBM54oE2rlJCjtVAqHO/DyCBjyA3eV9wKe9JtRNgj6wYXZecblvUJQOcT/F249GtBxa6R1nl6SX4Awd825dh97+8FGjgZtd/QtidLkFUZOlZlte8EkZJbzgOkP2vwX7fBdTUW8Dq1g8Tb9j30ZDb2y9ZKJ9z1SvObHxb+0cFaEER4o7WUhxDLBbIjDjenET5Xi2S/tLyIsobxcHL8lTbhdyOWemyfk8n94Psf7w6u/yf+dhjvKsLaHaCHLE/Nu6Ptp8OsXrunJhrRn61RTS8bWIxduO4JzJv5mkhpu3X7dA31nivokTzm7/4ssIZTBC5PQPr1vGpG/L5hqge+71WBUH3ZAKIuxiK9xYTrH0WRtkTFtmZUWiEylV0t8eUt3xa2ZZ436McIl2n42G0i7yBabRPEu+hsqGpkRtmCdWvALs8ehOl1W0U4ys5OdpWSS7b+dbirDYbvYpA2jUW+g+4Hg/847h+D1EBCOXdjwGtqsE/hlW5vEFQFPAuRTMQEqY817zz9M7BtNQeluBwfA5XSQalffb+7XYP+7/Zt2NY2qcd54X/ww97t/FH60H8EGJDTvY0X6vy6y2BJJ0c4+kWycq6J8upL3+1/3d0uf4cEcNbz4DXtN7YceM7dyuYxz6HR/AO2I4+Ue/MeDvM/1HB3M+JkFL+0jLPEn/zp1Txn4BP3muHxXYm79sUPkpgFDzOf8Bdo5c3sK+Yru/j7cdAS7roLAWFtJIlhpTj83Gs0dFElmT56o8qykFxeXcTup31zmH9TzRM9AFv8EOP2s1qgorOC4S0AgmIPb3fAywzQVhav+TwbScV+a+0n2D6gE/DRErZcpyN2vsJeyJX2+4EvSP3rb/G8S1PvKYFDxPk8ssxTKM+ShFCvCEfHGVmWiJSYaggbTyDCUhDCXT8JFlahLqNdF58AU8j7DOvEJfV9rz9mFVzn82Gi3yOH9109/yoS4ewZ3m59gR0sDA/V8NjsCfsHOAwR7BI8CLdG3NVJKcpzu+li61AXOXAsn1Gpnn6PZtgpudP2V3WWbZHMXVY4nwsN8lxtn4JFd2tXl9bG/kXUD5l+YPf2NhN48Zcsyezue6jH6vUGKPIBevFy1zLuAcbQDZCN3vzEjnpU2g5ZTuuJpiD4FCyp4msUcdjluMofjhPyfEKHQFyT9gmhMP0vDru7G7JU9xnoz0prsAGrc57Y7kfZe3AD4iipnHBs72BR3b/al+rP4zx/o5xI/YOQ7y/Q0zoCuN9O2QNsWFAXajf2Qw9pAg4PP+JdFW3E+DxFNSjOLkEcbY3wUoXvi1VGEStzwzP14mFY5wxR3zLDxwxpaEiqe6Cg4bwm3R5EsncIBbfg3WVgKuoeNgTPgvn77NC0y9de8VqMjpGNGR9f0t4mW2/pqd5nnkFIMNvla/KKC8DBDHAWKfpOhDSqopjUBSh2kUTytG+b77+t0+W7cksdH5VOMzjxgGeCbGV3OZmIBAVVQ6a4FH7MdzaJfmdy5JMno5BPXvCWlBo9EvMgYbnyPavGo+8N2146AMSbbU/yIGNDmzkYKlsHvj+WdjnLy47Rfmj6kNsF9YxdWRx5pxPGQawIIp5yJOzLN+gIaPCMASC/wQ3EPHDVUb4w+ImlOBYq7pJYHE/kjfvdGdMPFjQh8c+ZPixMaSIo8H0dWkxFj6If6rNPp7GUl0K6og7+yEpGTE/C85VtoXxbGHaLI53SAj8NnzxnjCmv5vAWZ5t3NlwWNrvvmLm3gy6rJ9nbnXzTxBrIhs66Bz4MmYYqxG+hxl5LX7nRbOXPn6pH5UNBNY9qOSDN4UoIHYA9OlTti8ug1SPcC0ONhoWYMd9fwwfjl4iSxID836w5bj7dO3e8L0DqLVR3sKHaLON4N2+6MQBD2XEa64bGPZrn33aB9wAG7MXeWbxKCZvau0JiwlfjhzO8L/Y22iavb9eegyxTe+PBG/vRZbLYiuZXvbMU8vOmjUsKgqtYHcBrmXwqvC6mYrvuILbqEpKNJEJB6JSo5z9kA3lz+Y26LK2HsdxLaXqA0E+Ir2qBqPbBga7N5cbXEaLylRRt2hB73uhf+xF6Qt4Kj4BzIx7aBUUdy6ofVCyAhpspHDjEq5x1wTwujEbDeOkeAptf8YOqnCJLGOhyRYw/NPfK1CBdT3c+zE7LnKY5Z4Xw7F1UXMSZb8KYVCDY0XrReIqC97Uq2yMhl6CbfJi11oL2DFafHISGhGpIqEhF698hJWvOKJKDoGXLyUGCfJykkInMhD6w9IWhkdz2889iB+yau/C+2knu4uL9SIDV5KnG3H+i/qaWAjM8+Iqej59BlRPXWAQSHs/0W8/MGFwztG97t2n5j5FZeQ653Aiw0MEf3jDW2Qc4haGnYEWT3vGVY7f7bmIXjBB9utpcbZ39rOHRwjw1uvifW3YXu8JO5y8xAkYvjzhNAIY5gKg/fHa1/OqjBKC5qeF45s/Bo9IT3Y/LsBVj/MUYoO/4wvlw9KLj0JCPILhv0hsVlL3nsw34f1Sg+Hj6HF9gnYTaHe39ooMLbm2GaZBf3ZpEPEEl9/cD9OyPzHGYRf9rGvpWI18H6yRzrEFLFbHhMyPeiO5J3P1og0a9c5nKepQ3k+/6zTfMeZZUSDdPDmMYJgRtFjo9sVhE/XkE3gEiZ8CuLrP8rL1aA/i9HSGspsCGhnfts1hf+ij/jFdTI1eSkAUChX0HHEqyHOQh8Kb5VaSUeT0HPxRkduJPtunOtzsx6jQxZ8KdKtpBeorcfsheUbx7NmJgEyUnfxnGcsFxmAj18X2hP61f7toFNwWyzROqjU4T1cQwUbdjs7tbKl1z0QJpbcZWebr6dTPK1jyF5idkIKdjX8GZdTcU+BUFWdAv9fFxJjhaBfwTvYlQFpWflT8gOs74HWWWSP8V5aG6WMN9yOPto3WIGmbORZSuZ8GY+Dey+MMi1jPeRnAy4p0B79sbk9x81eZYEHcK3wW4Bbjml8JhPnbugT59SvHoDK7DQZV1M90TLffco/TFx3DnaXtoYcBxHYwm6p2dSzp5tu1oC85xkgStxa0uUFLy322PsFddZucQyDjKVpX6DSqghrtKMsBeF457btiOVm7kqO4njXmjtYO4zC2LYTpqDZVOY3qoC43w47vSNLNt2tBX3KMkbwA+QYW2LmkjiJLR7x1GVYWz3jSUvU6jbGwYkupyEMEubHe9cmN9ZriY85j31G3nczeo72AGb380W1W+eY/rIy7TGipjqTKjipzm8XTqjTSBrTtd2pDXXjEEb0EjxA8fQTJ9rZKUscFOIDpy8TrJchw1B12HpC2uFdTfkRFQ/ERHIaPk+yujS+yr2PZ9M8oapGRnQ7R7CIrHBwo+5JjTFJ83e9nGctghsPQzIEH4bwEbBg6w9uadclRJHiWJN8zfPQ366sNE0hu1NGjtHhyOU2ny46+jO7vCMz7Vl842VCPUzDRgEMx98Fond66rfWAf4CCeEsHgPqSWSLJeP2oKLIYkpFldgH1Y8HXzf8M15+m61d4J4KyN+lNS1YguX3TfftcJSXcJjBGNf/2+t1rdqZ8TWs361fYXxvfVzmJijha81RArV9LWzBo8LAhTNKwPf+Dq4Y4wdRhWk6ytCjzCFGWn94wjeE2ShgKMPkMBQHuXIfIpnwAW3yqlZbirprUSD36zFfc4TPU1tHh17cU39ixk9Bazw2maBR/FnYSUWg57NQvErOwU3eoFFAu/fLmzTtu7Hqk7riQhuo/7rpMEZ+GyoZ/ZmnSHYUdbRHJseNA03wIiutVVuWxnCPasoNx7D9OIly4I2hRW0bmra7LEzCX+shdUqf6WHZ2NruKcuxgEkRc7DGbWQ35HGymcM2Yjc2a5XWnZJlIZePS9kaS2ahnSxRkHYdpxNj8ivj8/DWhKu7CXsqjtkm5q/3RBKTBlln2jGmZsotqsLA5g/S9kWF0r2zk2NxGhGs08vAR5C9X+N6PdGzpTINBHSRY7QEtpGEovpB2dRzesFrXqLbNzROdFifx6l2WYs40WsRPe6SWsz2y0coXw1c7Y7Gckammt1naWa1mNlp+jtLqNiKWs3xCwyVd7QBumLDrBsxBb3bBiNmw44Ar5J3wHdBxhY60ScL0UVjNevzDSCDraukCi+K9nVnhFsBs06909uJt5tWOudJyCRAaxHd/6zsmBic1Q4TBSLNJk7Cf+IKSvFldjlFYkCHBBJxocEFLUrHJjayF8KTuWEcw6pJx/im40IYLZmM/+dW+qfiuRPsU1K5H1FVwLWz6sniMbu+Qt4YpO89Tg+5Y8NJsrNTe6wvIRJr9IxM1iyB133adAYRXURc+9Aqm9Z/Di2MG+yk6IUsMGjefN1x7N7F7zU6nWrclhuKh+zjJIsPeBhc1ZWQ503Z4Aq5S3n2XVKm+HzsTZ2kUZAsBsbd8ZTPGM7CVPCbC1Fx1DJMEafTieBBOPKVz4rWRe7vLDUZ1DYk/Pzc0YbGuxZ1Y6OLFxPIaNKVL25vFq+2RzeI1G1/VMdRXZU5uTRZEIbpuA+dLmavLQI9o/3ES5hK1fNgeYYZR2Oz/b+/KetzGkfBfWcwfCOa9d4HOuQFyTboz8yiobbZbiG01ZDnH/vqVqItHVbEkk5TUyUuQNousgx/JIkVWwQYLAzNQLQ5fNR3CLFjrnoavA1+dtJoMw4+rx1Gvyiqw070cXwd2Omk1GYYfV4+dXpVVYKd+8r4O3NSS6h9r5Q+rx4tUYzVYacIjJLbQY3sqIEoaGS0Jup/DfM/n9qIHtLSKcLjV5PPhxYxrp8Yec54wmrX1/rQK46CLilpIyacThkGgaZIYaGRHccTwyQrbuACwur7qY0CgOv4XBOlogMwNUEfEyNjnnaNm0EUdc7kGSETwRT3wmgq4xYFtRTPgEkEXa8a7GHjzznSvftRpbNL99bl8qFvcyEJjSV70jEdpoMlFE65+BiTVGwPI2bD4Oi/OB3ko6xt4+PfFnqfWkvLr6nEx6LIeEMg8X7FR0CQXs2DQ/vw0cAAkUFs0EBL5b5Oimuw8LYv10Hntz1EWIsnQFiEQdDDDBAQPi9Eg1xKmEEDu8T0WEi8zTDr8vow740jyBTgfmNiXdF1ICEX3Xkb2a1T/RYo1G4hkGOUAl+Qo30XLtSobaX9Z+924Rg0OI93gM/f+KjbNcUET083lo2Z2D7cLz/TllO7Ef7NKmuJnAseVWmgYLlVyUB6d4MkE4tLU4vCdPRIXiDVgrIyfI34BiPHnlLnwJSWcd+GTN+KWjKheSluCgFfhomFnUGRNgFl+DJN5YBMxXsk44MwdpeRNdl++SItt8ulcp4Y5ie0/WfmA6DC9Gx3PkjoptMaGH8PNJOx+uhAUvS4sTIBdMTtENF8HVmhqlwaaayDJQXkiOD2jEOAJbaP9nq7isrD2RR0KC/WGlgS1aD7SZJxpPTqv2/QhL8Xy/exaSluC5td1Y2hQZPl+9mfxvUL7p7xq4NRNTqs4nwQE18QBy1d/dglptYqTTAhnXhdBhzu+HLhEm4amYkXrlnkD+9V5DBa9knVCWsH6AuaViAagXo/lL2OdqPLECJZ7aq8FRo514KAXBIszyu5YTyBiH0v0Feb9SNuFpowXyOb3Z/o/DKPP6K2U5+JYJ9MRAe4aB/KHFZEN10YreQI+sKrPKrzf/gFP/Fnld3gsdHqZ07fNpSAv0qJxWZ6fj9u9ID9PtJ9VFErgi5daOgompkSGS2IWhvyihdkiDIAs3Tg8BxGXA6FVrFJLgFnMtWoSumZfrixsLf8b/BKAFfGL/CRczf1hvuV/8yg22X37+vC6LIvs7lyKFWCMlB+SC6NcO/po9dYARVj0j4/1nwlHL3XqceOBB4RQUyOhK0PKjjDMSdR0CF06g1LKsjYTYAMLnVz52oYGzi85N18MttknbEqDuTHfuQ636c4dw2wpXsQgMySLWvpEvAVFpREewozhxwBwOSLu0H0KdOavADFuh8+Jsop8dqD9nRZZeiz7ufVFfrjLjpIwwVPGq+9hVCLtHYxWMOpY0C0bBC2SPMwTL9RAQdFGKcqRQhV7yfhbzd588UCNv+5eitG5t+oMeP51rkqqXvpyzHCMakQqFvSCJzk94gZaNPRUsZeGv/XOiRxIPtnZb81TXgc595GkRQh0eexjyMWiMeqxowdYLuas0VTi73R/7kFKaxgGF3GRK9XliNkShsTwJDyFgXKj7Qg8mw3MDevZ3UmOy7Bux3B13l9ykx4e9+Jl/v24z1P8iXBPoJ2/9D8uHA2wevMjQTf+bFi4zURR6bcRWhx2530rScSNvy4xEWQV66XXZFF+fRLB1gd9OMxU6RYAq+VvKecBUcRt4zj8LGSjOFxHbS8SLx9IluTjbjOvD1i2ShymWncuCGZeAeZ0gX6jhGa6OHz0e7nX2b6s490Gf94Qa5oyNKOFtIifzrMJp6782W3WZxTP083Xt8ebMt98vTnfnTZF1twBXcVrCkR4PQc0RrP6txWYZmMc/8Xhbvme2/JAF3F9vgRzc6/S1/f32b76RSSR4s73DPWGhl/X/vh0UIXDbO5H7TJNQtBFDoeCxdtOR/HEVidbp1WsS5V+3ypAv6+Amu5E8rrID/FQojM31ji9aPX4MBTicFQ7YykAuc1/w2Mh8Bi6Yj4Xo/Irv1Umf5fvEuX/dUfhzoZBp/kKZlkUD1fhikkTynehbBYGaKpSLC9GF3ERUFvFZn0+VMWct8bCafYp66+zOIvtq0Oa7RP57/WGzq6jEWnZg7WCKJhShDfuoCi/B8EUbqowuFI14rBT5Zsv7NOD2HzNz+YNOetnfN6yKLUJzC6Nk8wdVIsWLeQdOIc9wwAS0ZA155lVZ4xztzkXlca7T+lPGWv17TGr2/UUchV35U3GRsA6s3DdcVQtfVg8lZ5YDD66yM20Rr76OVikRFApUrSwIaGnAcQvKMdEiDbrzobPd/km3Wf/E9tOxuRdetydrR2z0vs9gdrdw49REGiJrQtjlwZBHWyqMGizdeIw7SScEV++N5fh7nea+8qnsZ9czT6y/br3WXzLxPf/iv3j/Xl/rIOM1l0yNC5P1HtMVb+Xtdt1mydYfSfs2jZA9PVlo69c2XIAX5wBIsYN94sw12kU5QiW0yOjeM+JQ63gTxRWGhne6bNByu+dgVaZpaNJqzzfnZV9vnuRH+RGkTmrKVViT2Qqa+1uivr7E5quUFOPYhcZRvX/P+WnEp+POgqrD5sf41x5Gg8lT9NUr+gCAdTJNhuQPojvp5HzkVIl9nykslab1H5/QvMRaupR7CLDqP5/fR8Zn486CqsPI95KnwAlT/NRr+gCAdTJNp/rne/3f+dldtx9Fpu82LK3fka96Fs+k7/ml1uFT2iOoi0/iucc+Kp/uD6evouC2M/1NFa3dj/HeUlzCch8bfoGpZcKrkHEZZwgTDq+mvXMin9mse6py9MBRnRAtX85T6Dmf5w8Fk/re84yHkRxH7G8kgFg6lFV1RBFd9afb8XrrDiVL9MyvUtP9se+utaNKIFAMn/8qylFA/bcbB7EIf33H9u7vOr59M6O9WNNQzo39ytQSwJ3FUgqd62xknJEY8rCZY6zJBk5m3+ZndqLeFb7QxHEYCh1cHiRlmIn7wRYHIYiiMNQ6jJRtinPhYBM1JWAJuoKeT1AcDEIiP5IWqLkfSrzErH7p07jmBWi2bKiXaVRUb2mEbpE0MJi27y1YpCpRsGzNoEai4Kyd0fFNnhb8X16PN+nsq+ImUinouRQKdmyOIRwc9cpXAPVdCzt8WpSgMPWJGKyJfjRjJwc+ieZFoO+BGq/L3QpUA8oeH7rSkDxu0JH810yzveifMi3ABeTAGJm0rh4ltWwqaD7rfKPoDFvlIMcdRIHw+FDhcVrKILYDKUODsPVJYvDUARxGEpdHOq7O+KmLORO9tTkVwG4gWQgZ5DSIcVwxGpxHoogbnWpe3ZUjkPsSVEpBOdCpZzBB+GAt82eapqAcuh00xRTU05D4dJBFIfsdKpc9e6wxtbHIgF1s6gcnF/9qNzaY7q/PpcP9XaiyUaESkGTQxLRNRzSyRe/yGBXyiC+sviUsAa8pL3NH7MNxqctJBi1FBxOGBOy/ZaC0/6bIj8/YkzaQoJTS+Hg1N5Pt5i0v0Ptt0VMN/ZL/UqzvySM+rE6GeXI6pQOKd5k95XbWGwdUsBkkBQwJVMKgjPNjdeNyCKglKHdydqYSsoPeQnNpEoZyqQpdjD5LL5XCn/Ks2N5wjsMpIIYg4QM/wvZdg1FmM/F2mB1hEiP6cUUJ1a/fRbVduBYbwAFOAUb5bAZNRKnemZWYUBFkwRW06Ti7eWwFIbYrg6jp/Z3SD5J7k6Pzgxp24skB21H1pgkHVuuMRJx+1SmY8M6UBYSvSXLeXzIHDQYf7ISIRdZb5q8fCEd51ZGToWx5ykMgXiSjEYKkmWAa5aWfESvtTVcHlEf7Nz2gvoi0PPpSx0clGjDFgulDOKhFDtPCnLwTLT9HT4ZyBmnrpKqBxncfl+MsuGi9LaqL2RH3oFbQ6MctJlO4jrwwCK/2ecfGCV4HIIRjzhpvBWHx30KjlSYzHXyOFAyPxUQEtgk1KcDNuduYcAZWxTU+sJl+0YcRTXMqMnRJgG3BhaV60x0s0fPA5Qy8Fx0KHYw+VLgTJQyiIlS7Jooqtrb817cpqev0EShFYMThUbhstsQlc+221AG2m0odo1GkZ6qgfOPyHYPkItulIPjTyfhMXyZVbuIEzwN2SQEW4XKeSRXFOK4AU/P+yL4KK4r5ZyeOOZYgIY4Sxk1s5oRt4CJRSegzr0MUucYVyLmAKNcKYXHuULAZ9UEoKLYNRQOlg2Rc8E4PKbZDurSoQheILpS11GqFqzGPjrVisGjUo3COQ5lz1IrsEkBj0KDiPHN4J0oS1E4xglGiH1PgGgdsmgheSwBtFL4CopC4EKPHRfGhpFNA+LJJhvLHNuXYIQsMXh7ETSKA3A4hFDCx0QI8UhxGHLwBOB9wtNDClisARr0451O5lS7Hqxb/MKPSQCrrNO4UFjkp1PV+B7napOAyLOonKaGFiV0MeIsQtX/SmS32JWA/l9X6Nojpj+IWylaKbg/VAlcrJDPVsQXK96nqu6zCaGITUJ9f0muHx/3mdje5i19JtxfkbvKOOgsCp4MLblbAv0+BH6DA6FzX7H4LE7VFCzv1jJukLSflfGLKhYF9XW6JWJ0hPll2fGBnCbnfLpOalL22WF/lYj33Z7//V5LTDf2KJNzAs08iE4GurFSAAEAMGEAUkImgJpxead/TmefX6ml2BWenoDhqeKstFLMJ+Wyst/a2Pa1SLCrKTrVmB52dSujLy2Gyi1y7Jpr0t9mVYjB+649pXnNXXknoF+drXiit2+h9wVgdaPIfLWgqzhB/W7hcasPP0OAHmrY8i9G/f6eLNHvFo3/Hjf9ElkRdZvGq0m5LclNe8PO1pxTDVfKvGMvlcLuz4M1QbPYhWHNc5sWOwGOCE41T32+MPN0Duco7JiVQiIHmnusspCGGYMas5L/KXUmswDPLk6kScAKIXECvC/RGqEejExebBP9ZQi61Op0hDLAS5VGC+IFylRcTVe7X0eHqQlV3aYNMZEij25U9T1Oo6Zqbl/LIg3mbEUygfYciHC5QDr/bhf00EhWJh8QTe554zGUygKFAF7Hs1oQKtCGwPIgZnIPEpA82ECJbBL9dC3pjtdEeyZHu+/8yrja8DMsqTH9uIpoxTh3BBrDHokFMSDuqvErE6sToCyi3lJMpj5hS/p2AfOAhAFMAT3La0xAvrYbr3p7hEwpbZL4V9c4Dpf1sEeZ01U0TIcrqhN676WIqvfH4P2XAmLyxImJHjfe9DZdjrzXBWtCBrDKQhgCnwRxYi/9uAQzPM/2+2oi7VsmjGCQhjMBC0MXKN0FZxqaxrW2aEMOAeMdvtYA9rh+vBmgR8ZDdgjAFnQFXC3zAbbUB3tcrdckHlY3rXAeSo83Tfe2nDSHTRTCBOb7eFkTffs+XtXuaTipqk0UQlXzAbusOfx4+eYq3+9JNXWCECqqr9ubPRP0en2aas0D+6RpEVZOJblQSKuGHhygr4c8+5+gonmhQP2ST7kxvIqEcshr/kZFxwt9siXodgPYKhl9wLspcUeIV9G9wFkKUwou0oTgfRKWMz0SfR68CZfR45iG4V5HR9NcJqKiaJDuKK9iCDRxIovI1kZFCBlvuj5OCGkngCqEUayAJrKq8qsfdeUNT7e+BlkwhbU7qoPG8D3UiSonanQTRF+VxiGyFkNlEBmMjWLXtiv67dm2SbJbG5qLBZ2nN+VIVJulRqxC51H0yONWBjphnKqAdP6PVLTwPrIWHL1nqqLU3GRQhJiWwqkHRRtKhvsV+EUOuAJxg+HiixxQbCOtFTJWkSfTtHZn2gVMMDy+Xxdhjj5+Em4Dk8Sv4lbsp6GepwOUQX7im7RN5P9LdHhVu4hbyadqf/aQnsT2n6x8UDjYiruqeFNHq2vGFJNV0Xhh0w2hjduhfdwMcAVPioA10RHPCtbmyTRf1H5n20ev5XdemMs8faw3x4SokASYENU4dUM9MATdeBWBqHKkK0TSh3CMiCh6shFOWDw/ZnGOC3clv+iYxzRdwD58TBgUfpU2QxredLdoPF3vVeMRJkO7uJY6oSfBwZrW+krGTpy6EepuJDm3fCbhmvZ8WhRIx4QHUoaZ6oDolu1IpoJWXnDAzehslNZ/f4e+NmEG4UzUXCbQCMfJnTsCO+OKujfAk6gYIx8OP9qOfkdIUQ8GokYGThxicMxuCmLDiNL63zfGNgMZVpZxsdtR0b+BWHFz1daYsVwnIIgIW4vE3AXBNaEZAj9uu/AMwmhVDwFMtI2E8vWNVpr5WAiTrUU21UJHgB0pgrzlglMHmybseBhqC2ppEGMQ91RwaqdSiDaAGsswCRE6mvvKkt0Eceru6wEmI+y2atExgbODmNi9iHNqBxuiazOnnkhykk21JnADQTkvpSmoXJZrNfEEmMbHJsda4U1EupPsuk7lSa15fuMizSjjTiYoW7ZJ6XaiGIXTrha7k2ocjsc53RFiTpTRZ8OwARSSm/TwuBdDJgRccYOScF2MfAyN24LkWoiocp+KQbsXC6mMULoPkqbfnLVSTMjqePaIS9Qn1i6byP9qFV5V61izPSt1L9polWCL9tSTWS9mGWWQJ20KI5O54yx+TPU4Z/MuieimXXncJ7z4g9OnkEf4zjohTvId6WNkQ9xcMP7MRAxMVxX/w3NeE/XJPhLis6BN5P97oJWwpKmJ5iKZeLebPVwI6hADBc3+cdPf9fbc83qSjuR1kR8oe1DkIQwC5yNpRxWZXuRiU9zmIwyhEK/dDEpWk8TKcALMCgQ5Mc7hDCzNaKfTqmDtYG14NgkFCpAuBB7iqK1kJ0n0xDC26igtrgSUrEZqQSWhMQ4ArOwr7f4fT6sy4eoMmKYkAZKoAFdpuHUJlGB5Xxq4uFK50G3ZR0HMpCxTLmDp6U2St8eszNI9cbmSqoArOe1iJZzE5aa5i+VIzHK5MbrLozYrt13QuoHUJVuCb6XyMutMDAejpq1xx4KBqXEFLwwEA6XnaZpwZtyZYg56fQq+LpnrkZ91qN0/Wakekto9eJEfT2WRZtX8UHujvX5d8KPbPMHqE6cO/nm5zT0hghK09URTbah7UXfaDI/dpBX8OcrqRlWm8rjGizagklqECzctGwlwjnFZi+ExCyRjaY46iCwrlxm2C0MF4hAmpMVnB7qaQXElgwy3/7WkM7aFLmwxPKKAnDuyDSqZzmWG7aJ9gYiCCWnx2fHEZlDcTA/EXhettELAKuCr7QhrLZJuqVkiXCmUPJh9CFMGL6gUPa3WqJho8xpFXYonOWhOX2Raq5F9Pb67c7GR27/cTtxAGexTpkflr541jdSmr/pZFH3Z1bMmYVn7Q/VntXWtdn7v863Yn+SvV88+n6vaB9H89VKcst3QxFXV5lHI1IpDox3N2+N93u3/DIk6kq64z7lcptu0TK+LMqsD1lfFm8r7lKn45ElNfXx2J7Zvjx/P5eO5rFQWh7u9tru8ekbzv3pmyXzV3EI/+VChEjOrVBAfj8/P2X7by/063Z+MHTXWxIvK+m9E9XvTl9XgLMXuZ9/Sh/zIbKg130vxKI7batB1SahPH4836TcxRbYvJ/FO7NLNz0916Gx58IQ14u4I3exXL7N0V6SHU9vGUL/6s8Lw9vDjP/8HKnQByFXzBwA= + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.Designer.cs new file mode 100644 index 0000000000..ddaae8eeae --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.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.0-30225")] + public sealed partial class V22Final : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(V22Final)); + + string IMigrationMetadata.Id + { + get { return "201504171629262_V22Final"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.cs b/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.cs new file mode 100644 index 0000000000..23b0f56a14 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.cs @@ -0,0 +1,101 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + using System.Linq; + + public partial class V22Final : 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); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Common.Date", + "Date", + "Datum"); + builder.AddOrUpdate("Common.MoreInfo", + "More info", + "Mehr Info"); + builder.AddOrUpdate("Common.Download", + "Download", + "Download"); + + builder.Delete( + "Reviews.Date", + "RewardPoints.Fields.Date", + "Admin.Customers.Customers.RewardPoints.Fields.Date", + "DownloadableProducts.Fields.Date", + "Order.ShipmentStatusEvents.Date", + "PrivateMessages.Inbox.DateColumn", + "PrivateMessages.Sent.DateColumn"); + + builder.AddOrUpdate("Admin.CheckUpdate", + "Check for update", + "Auf Aktualisierung prfen"); + builder.AddOrUpdate("Admin.CheckUpdate.UpdateAvailable", + "Update available", + "Update verfgbar"); + builder.AddOrUpdate("Admin.CheckUpdate.IsUpToDate", + "SmartStore.NET is up to date", + "SmartStore.NET ist auf dem neuesten Stand"); + builder.AddOrUpdate("Admin.CheckUpdate.YourVersion", + "Your version", + "Ihre Version"); + builder.AddOrUpdate("Admin.CheckUpdate.CurrentVersion", + "Current version", + "Aktuelle Version"); + builder.AddOrUpdate("Admin.CheckUpdate.ReleaseNotes", + "Release Notes", + "Release Notes"); + builder.AddOrUpdate("Admin.CheckUpdate.DontNotifyAnymore", + "Don't notify anymore", + "Nicht mehr benachrichtigen"); + + 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."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.resx b/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.resx new file mode 100644 index 0000000000..8ebb7814c7 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.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/201504232202590_AutoUpdateRes.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201504232202590_AutoUpdateRes.Designer.cs new file mode 100644 index 0000000000..a94627f448 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504232202590_AutoUpdateRes.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.0-30225")] + public sealed partial class AutoUpdateRes : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(AutoUpdateRes)); + + string IMigrationMetadata.Id + { + get { return "201504232202590_AutoUpdateRes"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201504232202590_AutoUpdateRes.cs b/src/Libraries/SmartStore.Data/Migrations/201504232202590_AutoUpdateRes.cs new file mode 100644 index 0000000000..84dd3e8dbd --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504232202590_AutoUpdateRes.cs @@ -0,0 +1,60 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class AutoUpdateRes : 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); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.CheckUpdate.UpdateNow", + "Update now", + "Jetzt aktualisieren"); + builder.AddOrUpdate("Admin.CheckUpdate.AutoUpdatePossible", + "AutoUpdate possible", + "AutoUpdate mglich"); + builder.AddOrUpdate("Admin.CheckUpdate.AutoUpdateFailure", + "Unknown error during package download. Please try again later.", + "Unbekannter Fehler beim Paket-Download. Bitte versuchen Sie es spter erneut."); + + builder.AddOrUpdate("Admin.CheckUpdate.AutoUpdatePossibleInfo", + @"

This update can be installed automatically. For this SmartStore.NET downloads an installation package + to your webserver, executes it and restarts the application. Before the installation + your shop directory is backed up, except the folders App_Data and Media, as well as the + SQL Server database file. +

+

+ Click the Update now button to download and install the package. As an alternative to this, you can + download the package to your local PC further below and perform the installation at a later time manually. +

", + @"

Dieses Update kann automatisch installiert werden. Hierfr ldt SmartStore.NET ein Installationspaket + auf Ihren Webserver herunter, fhrt die Installation durch und startet die Anwendung neu. Vor der Installation wird der + Verzeichnisinhalt Ihres Shops gesichert, mit Ausnahme der Ordner App_Data und Media sowie der + SQL Server Datenbank. +

+

+ Klicken Sie die Schaltflche Jetzt aktualisieren, um das Paket downzuloaden und zu installieren. Alternativ hierzu knnen + Sie weiter unten das Paket auf Ihren lokalen PC downloaden und die Installation zu einem spteren Zeitpunkt manuell durchfhren. +

"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201504232202590_AutoUpdateRes.resx b/src/Libraries/SmartStore.Data/Migrations/201504232202590_AutoUpdateRes.resx new file mode 100644 index 0000000000..cc80a45d18 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504232202590_AutoUpdateRes.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/201504270946381_TempFileCleanupTask.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201504270946381_TempFileCleanupTask.Designer.cs new file mode 100644 index 0000000000..faa97538ed --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504270946381_TempFileCleanupTask.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.0-30225")] + public sealed partial class TempFileCleanupTask : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(TempFileCleanupTask)); + + string IMigrationMetadata.Id + { + get { return "201504270946381_TempFileCleanupTask"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201504270946381_TempFileCleanupTask.cs b/src/Libraries/SmartStore.Data/Migrations/201504270946381_TempFileCleanupTask.cs new file mode 100644 index 0000000000..adcf8c1589 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504270946381_TempFileCleanupTask.cs @@ -0,0 +1,39 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Core.Domain.Tasks; + using SmartStore.Data.Setup; + + public partial class TempFileCleanupTask : DbMigration, IDataSeeder + { + public override void Up() + { + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.Set().AddOrUpdate(x => x.Type, + new ScheduleTask + { + Name = "Cleanup temporary files", + CronExpression = "30 3 * * *", + Type = "SmartStore.Services.Common.TempFileCleanupTask, SmartStore.Services", + Enabled = true, + StopOnError = false + } + ); + + context.SaveChanges(); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201504270946381_TempFileCleanupTask.resx b/src/Libraries/SmartStore.Data/Migrations/201504270946381_TempFileCleanupTask.resx new file mode 100644 index 0000000000..cc80a45d18 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504270946381_TempFileCleanupTask.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/201504290937512_SendEmailsManually.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201504290937512_SendEmailsManually.Designer.cs new file mode 100644 index 0000000000..1a16386517 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504290937512_SendEmailsManually.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.0-30225")] + public sealed partial class SendEmailsManually : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(SendEmailsManually)); + + string IMigrationMetadata.Id + { + get { return "201504290937512_SendEmailsManually"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201504290937512_SendEmailsManually.cs b/src/Libraries/SmartStore.Data/Migrations/201504290937512_SendEmailsManually.cs new file mode 100644 index 0000000000..d06f7cea83 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504290937512_SendEmailsManually.cs @@ -0,0 +1,100 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class SendEmailsManually : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Order", "HasNewPaymentNotification", c => c.Boolean(nullable: false)); + AddColumn("dbo.MessageTemplate", "SendManually", c => c.Boolean(nullable: false)); + AddColumn("dbo.QueuedEmail", "SendManually", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.QueuedEmail", "SendManually"); + DropColumn("dbo.MessageTemplate", "SendManually"); + DropColumn("dbo.Order", "HasNewPaymentNotification"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Common.ErrorSendingEmail", + "Error sending an email", + "Fehler beim Senden einer E-Mail"); + + builder.AddOrUpdate("Admin.System.QueuedEmails.Fields.SendManually", + "Only send manually", + "Nur manuell senden", + "Indicates whether the email should only be send manually.", + "Legt fest, ob die E-Mail ausschlielich manuell gesendet werden soll."); + + builder.AddOrUpdate("Admin.System.QueuedEmails.List.SendManually", + "Load emails manually send only", + "Lade nur manuell zu sendende E-Mails", + "Load emails manually send only.", + "Lade nur manuell zu sendende E-Mails."); + + builder.AddOrUpdate("Admin.System.QueuedEmails.List.LoadNotSent", + "Load not sent emails only", + "Lade nur noch nicht gesendete E-Mails", + "Load not sent emails only.", + "Lade nur noch nicht gesendete E-Mails."); + + builder.AddOrUpdate("Admin.System.QueuedEmails.Requeued", + "The queued email has been requeued successfully.", + "Die Nachricht wurde erfolgreich neu eingereiht."); + + builder.AddOrUpdate("Admin.Common.SendNow", + "Send now", + "Jetzt senden"); + + builder.AddOrUpdate("Admin.ContentManagement.MessageTemplates.Fields.SendManually", + "Only send manually", + "Nur manuell senden", + "Indicates whether emails derived from this message template should only be send manually.", + "Legt fest, ob E-Mails, die von dieser Nachrichtenvorlage abgeleitet sind, ausschlielich manuell gesendet werden sollen."); + + + builder.AddOrUpdate("Admin.Orders.Payments.NewIpn", + "New IPN", + "Neue IPN"); + + builder.AddOrUpdate("Admin.Orders.Payments.NewIpn.Hint", + "A new notification from the payment provider has arrived in the order notes.", + "In den Auftragsnotizen ist eine neue Benachrichtigung vom Zahlungsanbieter eingetroffen."); + + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.SubCategoryDisplayType", + "Show subcategories", + "Unterwarengruppen anzeigen", + "Indicates whether and where to show subcategories on a category page.", + "Legt fest, ob und wo Unterwarengruppen auf einer Warengruppenseite angezeigt werden sollen."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.SubCategoryDisplayType.Hide", + "Do not display", + "Nicht anzeigen"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.SubCategoryDisplayType.AboveProductList", + "Above product list", + "ber der Produktliste"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.SubCategoryDisplayType.Bottom", + "At bottom of page", + "Am Seitenende"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201504290937512_SendEmailsManually.resx b/src/Libraries/SmartStore.Data/Migrations/201504290937512_SendEmailsManually.resx new file mode 100644 index 0000000000..22e46cf0d4 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201504290937512_SendEmailsManually.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/201505052021483_PdfConverterRoleFix.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201505052021483_PdfConverterRoleFix.Designer.cs new file mode 100644 index 0000000000..c10a58454d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201505052021483_PdfConverterRoleFix.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.0-30225")] + public sealed partial class PdfConverterRoleFix : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(PdfConverterRoleFix)); + + string IMigrationMetadata.Id + { + get { return "201505052021483_PdfConverterRoleFix"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201505052021483_PdfConverterRoleFix.cs b/src/Libraries/SmartStore.Data/Migrations/201505052021483_PdfConverterRoleFix.cs new file mode 100644 index 0000000000..1bd58e0760 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201505052021483_PdfConverterRoleFix.cs @@ -0,0 +1,47 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Linq; + using System.Data.Entity; + using System.Data.Entity.Migrations; + using SmartStore.Core.Domain.Customers; + using SmartStore.Data.Setup; + + public partial class PdfConverterRoleFix : DbMigration, IDataSeeder + { + public override void Up() + { + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + var pdfUser = context.Set().Include(x => x.CustomerRoles).FirstOrDefault(x => x.SystemName == SystemCustomerNames.PdfConverter); + + if (pdfUser == null) + return; + + if (!pdfUser.CustomerRoles.Any()) + { + var guestRole = pdfUser.CustomerRoles.FirstOrDefault(x => x.SystemName == SystemCustomerRoleNames.Guests); + if (guestRole == null) + { + guestRole = context.Set().FirstOrDefault(x => x.SystemName == SystemCustomerRoleNames.Guests); + if (guestRole != null) + { + pdfUser.CustomerRoles.Add(guestRole); + context.SaveChanges(); + } + } + } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201505052021483_PdfConverterRoleFix.resx b/src/Libraries/SmartStore.Data/Migrations/201505052021483_PdfConverterRoleFix.resx new file mode 100644 index 0000000000..22e46cf0d4 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201505052021483_PdfConverterRoleFix.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/201505061813010_CountryMultistore.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201505061813010_CountryMultistore.Designer.cs new file mode 100644 index 0000000000..a39bd3bf2f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201505061813010_CountryMultistore.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.0-30225")] + public sealed partial class CountryMultistore : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(CountryMultistore)); + + string IMigrationMetadata.Id + { + get { return "201505061813010_CountryMultistore"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201505061813010_CountryMultistore.cs b/src/Libraries/SmartStore.Data/Migrations/201505061813010_CountryMultistore.cs new file mode 100644 index 0000000000..9ba99664ba --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201505061813010_CountryMultistore.cs @@ -0,0 +1,46 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class CountryMultistore : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Country", "LimitedToStores", c => c.Boolean(nullable: false)); + AddColumn("dbo.Forums_Group", "LimitedToStores", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.Forums_Group", "LimitedToStores"); + DropColumn("dbo.Country", "LimitedToStores"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.ContentManagement.Forums.ForumGroup.Fields.SeName", + "URL-Alias", + "URL-Alias", + "Set a search engine friendly page name e.g. 'the-best-forumgroup' to make your page URL 'http://www.yourStore.com/the-best-forumgroup'. Leave empty to generate it automatically based on the name of the forum group.", + "Legt einen Suchmaschinen-freundlichen Seitennamen fr die Forengruppe fest. 'Tolle Forengruppe' resultiert bspw. in '~/tolle-forengruppe'. Standard ist der Name der Forengruppe."); + + builder.AddOrUpdate("Admin.ContentManagement.Forums.Forum.Fields.SeName", + "URL-Alias", + "URL-Alias", + "Set a search engine friendly page name e.g. 'the-best-forum' to make your page URL 'http://www.yourStore.com/the-best-forum'. Leave empty to generate it automatically based on the name of the forum.", + "Legt einen Suchmaschinen-freundlichen Seitennamen fr das Forum fest. 'Tolles Forum' resultiert bspw. in '~/tolles-forum'. Standard ist der Name des Forums."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201505061813010_CountryMultistore.resx b/src/Libraries/SmartStore.Data/Migrations/201505061813010_CountryMultistore.resx new file mode 100644 index 0000000000..8a4e44f84b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201505061813010_CountryMultistore.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/201505272236124_V222.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201505272236124_V222.Designer.cs new file mode 100644 index 0000000000..eaf7caaa90 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201505272236124_V222.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.0-30225")] + public sealed partial class V222 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(V222)); + + string IMigrationMetadata.Id + { + get { return "201505272236124_V222"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201505272236124_V222.cs b/src/Libraries/SmartStore.Data/Migrations/201505272236124_V222.cs new file mode 100644 index 0000000000..0798205fb5 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201505272236124_V222.cs @@ -0,0 +1,68 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class V222 : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AlterColumn("dbo.Customer", "SystemName", c => c.String(maxLength: 500)); + CreateIndex("dbo.Product", "Deleted"); + CreateIndex("dbo.Category", "Deleted"); + CreateIndex("dbo.Manufacturer", "Deleted"); + CreateIndex("dbo.Customer", "Deleted"); + CreateIndex("dbo.Customer", "SystemName"); + CreateIndex("dbo.Order", "Deleted"); + } + + public override void Down() + { + DropIndex("dbo.Order", new[] { "Deleted" }); + DropIndex("dbo.Customer", new[] { "SystemName" }); + DropIndex("dbo.Customer", new[] { "Deleted" }); + DropIndex("dbo.Manufacturer", new[] { "Deleted" }); + DropIndex("dbo.Category", new[] { "Deleted" }); + DropIndex("dbo.Product", new[] { "Deleted" }); + AlterColumn("dbo.Customer", "SystemName", c => c.String()); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Products.Availability.IsNotActive", + "Not in assortment", + "Nicht im Sortiment"); + + builder.AddOrUpdate("Address.Fields.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("Admin.Products.BasePrice.Hint", + "Please keep in mind, the base price is depending on several factors and will therefore only be calculated reliable in the front end.", + "Bitte beachten Sie, dass der tatschliche Grundpreis von verschiedenen Faktoren abhngig ist und erst im Shop zuverlssig berechnet werden kann."); + + builder.AddOrUpdate("Admin.Help.Forums", + "Community", + "Community"); + + builder.AddOrUpdate("Admin.Help.Documentation", + "Documentation", + "Dokumentation"); + + builder.AddOrUpdate("Admin.Common.About", + "About SmartStore.NET", + "ber SmartStore.NET"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201505272236124_V222.resx b/src/Libraries/SmartStore.Data/Migrations/201505272236124_V222.resx new file mode 100644 index 0000000000..671dfc5bbc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201505272236124_V222.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/201506181858349_AclRecordCustomerRole.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.Designer.cs new file mode 100644 index 0000000000..387e91d128 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.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.0-30225")] + public sealed partial class AclRecordCustomerRole : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(AclRecordCustomerRole)); + + string IMigrationMetadata.Id + { + get { return "201506181858349_AclRecordCustomerRole"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.cs b/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.cs new file mode 100644 index 0000000000..dbc7b21d2b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.cs @@ -0,0 +1,153 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class AclRecordCustomerRole : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + CreateIndex("dbo.AclRecord", "CustomerRoleId"); + AddForeignKey("dbo.AclRecord", "CustomerRoleId", "dbo.CustomerRole", "Id", cascadeDelete: true); + } + + public override void Down() + { + DropForeignKey("dbo.AclRecord", "CustomerRoleId", "dbo.CustomerRole"); + DropIndex("dbo.AclRecord", new[] { "CustomerRoleId" }); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Common.Error.PreProcessPayment", + "Unfortunately the selected payment method caused an error. Please correct your entries, try it again or select another payment method.", + "Die gewhlte Zahlungsart verursachte leider einen Fehler. Bitte korrigieren Sie Ihre Eingaben, versuchen Sie es erneut oder whlen Sie eine andere Zahlungsart."); + + builder.AddOrUpdate("Admin.Configuration.Category.Acl.AssignToSubCategoriesAndProducts", + "Transfer this ACL configuration to children", + "Diese Konfiguration fr Kindelemente bernehmen"); + + builder.AddOrUpdate("Admin.Configuration.Category.Acl.AssignToSubCategoriesAndProducts.Hint", + @"This function assigns the ACL configuration of this category to all subcategories and products included in this category.
+ Please keep in mind you have to save changes in the ACL configuration before you can assign them to all subcategories and products.
+ Attention: Please keep in mind that existing ACL records will be deleted.", + @"Diese Funktion bernimmt die Zugriffsrecht-Konfiguration dieser Warengruppe fr alle Unterwarengruppen und Produkte.
+ Bitte beachten Sie, dass die nderungen der Zugriffsrechte zunchst gespeichert werden mssen, bevor diese fr Unterkategorien und Produkte bernommen werden knnen.
+ Vorsicht: Bitte beachten Sie, dass vorhandene Zugriffsrechte berschrieben bzw. gelscht werden."); + + builder.AddOrUpdate("Admin.Configuration.Category.Stores.AssignToSubCategoriesAndProducts", + "Transfer this store configuration to children", + "Diese Konfiguration fr Kindelemente bernehmen"); + + builder.AddOrUpdate("Admin.Configuration.Category.Stores.AssignToSubCategoriesAndProducts.Hint", + @"This function assigns the store configuration of this category to all subcategories and products included in this category.
+ Please keep in mind you have to save changes in the store configuration before you can assign them to all subcategories and products.
+ Attention: Please keep in mind that existing store mappings will be deleted.", + @"Diese Funktion bernimmt die Shop-Konfiguration dieser Warengruppe fr alle Unterwarengruppen und Produkte.
+ Bitte beachten Sie, dass die nderungen an der Store-Konfiguration zunchst gespeichert werden mssen, bevor diese fr Unterkategorien und Produkte bernommen werden knnen.
+ Vorsicht: Bitte beachten Sie, dass vorhandene Store-Konfiguration berschrieben bzw. gelscht werden."); + + builder.AddOrUpdate("Admin.Configuration.Acl.NoRolesDefined", + "No customer roles defined", + "Es sind keine Kundengruppen definiert"); + + builder.AddOrUpdate("Admin.Orders.Fields.OrderGuid", + "Order reference number", + "Bestellreferenznummer", + "The internal order reference number. In contrast to the order number it already exists during checkout that is before order creation.", + "Die interne Bestellreferenznummer. Im Gegensatz zur Auftragsnummer existiert diese bereits im Kassenbereich, d.h. vor der eigentlichen Erstelllung des Auftrags."); + + builder.AddOrUpdate("Admin.Configuration.Payment.CannotActivatePaymentMethod") + .Value("de", "Das Plugin erlaubt keine Aktivierung dieser Zahlungsart."); + + builder.AddOrUpdate("Admin.Orders.Fields.PaymentMethod") + .Value("de", "Zahlungsart"); + + builder.AddOrUpdate("Admin.Orders.Fields.PaymentMethod.Hint") + .Value("de", "Die Zahlungsart fr diese Transaktion"); + + builder.AddOrUpdate("Admin.System.Warnings.PaymentMethods.NoActive") + .Value("de", "Es existieren keine aktiven Zahlungsarten."); + + builder.AddOrUpdate("Admin.System.Warnings.PaymentMethods.OK") + .Value("de", "Die Zahlungsarten sind OK."); + + builder.AddOrUpdate("Checkout.NoPaymentMethods") + .Value("de", "Es sind keine Zahlungsarten verfgbar."); + + builder.AddOrUpdate("Checkout.PaymentMethod") + .Value("de", "Zahlungsarten"); + + builder.AddOrUpdate("Order.PaymentMethod") + .Value("de", "Zahlungsart"); + + builder.AddOrUpdate("Admin.Configuration.Payment.CannotActivatePaymentMethod") + .Value("de", "Das Plugin erlaubt keine Aktivierung dieser Zahlungsart."); + + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.Fields.SupportCapture", + "Supports capture", + "Buchung mglich"); + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.Fields.SupportPartiallyRefund", + "Supports partial refund", + "Teilerstattung mglich"); + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.Fields.SupportRefund", + "Supports refund", + "Erstattung mglich"); + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.Fields.SupportVoid", + "Supports void", + "Stornierung mglich"); + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.Fields.RecurringPaymentType", + "Recurring payments", + "Wiederkehrende Zahlungen"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowManufacturersOnHomepage", + "Show manufacturers on homepage", + "Zeige Hersteller auf der Homepage", + "Specifies whether manufacturers are displayed on the homepage.", + "Legt fest, ob Hersteller auf der Homepage angezeigt werden."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowManufacturerPictures", + "Show manufacturer pictures on homepage", + "Zeige Herstellerbilder auf der Homepage", + "Specifies whether manufacturers are displayed as images or textual links on the homepage.", + "Legt fest, ob Hersteller auf der Homepage als Bilder oder textuelle Links angezeigt werden."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerNumberEnabled", + "Customers can enter a customer number", + "Kunden knnen Kundennummer hinterlegen", + "Specifies whether customers can enter a customer number.", + "Legt fest, ob Kunden eine Kundennummer hinterlegen knnen."); + + builder.AddOrUpdate("Account.Fields.CustomerNumber", + "Customer number", + "Kundennummer"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Tax.VatRequired", + "Customers must enter a VATIN", + "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("Account.Fields.Vat.Required", + "Please enter your VATIN", + "Bitte geben Sie Ihre Steuernummer an"); + + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.resx b/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.resx new file mode 100644 index 0000000000..de6d366a44 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.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 + + + H4sIAAAAAAAEAOy923IcOdImeL9m+w4yXc2M9UgldZdZ/2VVO0amSIn2SxSbSUl/9w0tGAmSaEZGZMWBhxrbJ9uLfaR9hQUQJwTOp4jIzMobKRkAPgAOh8PhcDj+v//n//31fz2vk1ePIC9glv72+t2bn16/AmmcrWB699vrqrz9n39//b/+r//z//j1ZLV+fvW9zfdXnA+VTIvfXt+X5eaXt2+L+B6so+LNGsZ5VmS35Zs4W7+NVtnb9z/99B9v3717CxDEa4T16tWvl1VawjUgf6A/F1kag01ZRcmXbAWSovmOUpYE9dV5tAbFJorBb6+X6ygvl2WWgzcfojJ6/eoogRFqxhIkt69fRWmalVGJGvnLtwIsyzxL75Yb9CFKrl42AOW7jZICNI3/pc9u2o+f3uN+vO0LtlBxVZTZ2hLw3V8bwrxlizuR93VHOES6E0Ti8gX3mpDvt9f/qCLy6VsKy9ev2Ap/WSQ5zjyg8IKQGWWD6ZsPMAcx+vryhgb6yysm+186Lnn/5v2bn9789JdXiyopqxz8loKqzKPkL68uqpsExv8JXq6yB5D+llZJQjcdNR6lDT6gTxd5tgF5+XIJbpsOna1ev3o7LPeWLdgVo8rU3TxLy7++f/3qHFUe3SSg4wyKJKRXH0EK8qgEq4uoLEGeYgxA+s/VztSF/21rQ6yIptTrV1+i588gvSvvf3v9M5pDp/AZrNoPTQMQWdEERGXKvAKCBqor/QCKOIebmod869ZUBYtNEr18zuIoCdBRo8q+5iuQ64ZQjXRWfAC3EeLJFuY4yxIQpRqgX9/2M0o5z1B1qyouj6t0lYCzEqyPSkSTm6oEpzApceut594CSboku3ujgz7MRnldPdH0taqROqIHA/oeJZU32llxkQO0DCIpDVZ2vI3AzqNHeEcILaXb61eXICFZinu4qZdcLUte0+VP82x9mSUGk4Qqdr3MqjzGAiazL3sV5XegDDWTQ07dw1yV19UQy3dG1MQOBNZqPX4oaBmLM6T9tigfQAzXUfL6FZq7MWy08L+/frXEa+pvr/9mincB0BRJy+gOWM98O63lbz+FWM2X91lemqkq6GeACmvJ0AmKwpdKn+AKXN1X65s0gokv2Hc08DeJ98gR4VDcOwj/0XStRQ6wAPiafivjjuXRlyu4tldtv21WHmDSBY5ZPYq5lrl2qXJZ5tol0m5Rb2ow6/A1U0bRyWFW/frN5Bet2arO2HXDqAPmTVc12lbR8FcvDkqFQjzWJMLV+eoCF1GO6vyYZ9UGNSSMftEsAmfpCj7CVRUlycuWLOWWtU6/tqP2TVnf0WoN00W2XoNejxutspZrwRotyf5bTjQ4T1/TT9kaXATQFb+AMkJT/CnLV8XoOiOubMphxvVdwVJp2grTs6MkyZ4WRPSD/BI8QvDkraAebTZ59ghWl0h2pnfLau3HN+dZGRixhbtC4j1hOu3dxnCgy+rm3wDNv+wo9tbzP8M1LHHjyErmPcTLh2r8SRel1W0UY4UhR6teeY62PP2mYLRqPyIWG72Ss+IjvC0XUe69YWpxQugWl+D3Cubga3mPKF4Lf29OaTB7VUUlrd/9FEaoVViTLWGMNZmj1Yppg3efzooP2VOaZJH/drfB8R25b2lST/AW0LuPaFTqCff1lsN0tD81MCfPG5gTtfpD9MJimkEcxSV8JBAh2P5TVCwjpOOAUKM6ROOap23NtwLkR3c5ALSa59qYAdgVeB5fbzwrLkFc5TW6r/RogBYvcQLqRvnKOBrxAuQw8559HSZZ+wmw51w5K5b3cHOS4iLe7HhWnKLhx4CbAEOC5CnEMy9KWsTFPTZAmJuVrZt/FT2fPKNdiPdcOCtOCm9yotYs0HboLstffBkHqThoK3SWPqKpieCQ2n/vz41Iw4sfgh0ZYGssQTx6jCAqCxMK1dPKK2yp84IF04A9/5w91b3GKw1C8x0UtEGAty9k+36a5W0bjwHafXkeNEXxQ4at5dh/yftUGO8GMeJZTUu0A+k2vd4aBbHpo1GC62odZpBqxOg5HCIhAFg1YBBMoKyiuYAbeFy9HFdl2RsXPOYWzv0DFvcJLMowoM3kTwBiXiTXB8czrpgLpJ0TOBj7nz/RIMFXoK/JatwKmo3JIissTmdtl4UNwokSy46YYy5LtEdvz8SEx2TmWCfpygOpNWedpPhMLQh/NWJrgAxGZopGsE1aJ9p8XEFQz0lvgY/WUFCU3dHhIlvfwLQ5RQvIhKjNrStCcbTZJNBfY/4B4N39eFNxuI8JDv8DrkZE/zQubbqVxleedEB+wiScQ0I4HwmQABc/u+11kOA7CB9B/oILW1pPaAdxy6LHUQGa1SfItrvD+wKiospB7fw+rqtyV+fROqSrVweLfwyhzUhLnBbIiZbGAhXGvN44dYAc+0mEMnwMQDE1QmMu77PanoIEXzgn2mZJ7JZIoa9Km3rd5L7Kelt576wiz8V5ECmy2joM0dJA5WhzPczI+dgM0mXuNcNMju5AvY+OoUeXu4OT1nNL4hBl2aPG4EU2xPIOtWYxhaMTm0XWAS6fY8PpQ0Rl2+mMivaLssn6IMzr2I8LSHCUXWjyKFrP5JA1nM3m2ObuEFze5DqLosXDDLIGM7kc20t2oLcwJllof115+8VFFP1RF5D1T1PKsb9X0Z2yc9eDfLw8pZKl4pTO4yhNv0c5REqdaB+pbL6inGKADErJRsmkqONQsdBWHbforW0Xnfs1vBGq4MFhRp4J6XQpFw4y2bIhezasaCyblW/uMIe0wUw22ybTNhxBc7tkEWdwiRwv8DlEo2/sgdzfS3G4GNwotd2vgxeyvC6NT+77cXxy25EJ4anxrcB7pUC3jeT3l4IbtdqqbHfl1ie/jcnMwc7VWMkcSjbeVcUiqzZZukCc4m2Z4pDEx29hTt/IyBD/SDLLfHm0R8LEs/UD0W3jrzLNVozfeNMFVHv5Ph+3LCkz2y5OAhPDGBYJfR8444WxRaKBajgfO1ZpOiEuIeiHKKO8K8LcXteB2n23x32gFuKwFMvr0izFI12PwbdVAtWsM+BPd1fiOCsRn056CQeH/Rl9ZWqnUajrN4cLM0F6Vt+/C+UA2FjaLE+q8P2pJfwD+NU9uPpTXGV1/A4W29kjp8H5OnQe89h3GHgAXUbpndJnK1gIgYA32UbwX9jim0Pbdgq+lwEHOFKRoFPfIXj6ot5UOUrj0IePDlsW/QGkYH9jbIWtxbSw9f1JVZupbzGbxmnyXAa/u/xtC6x19y9gBaM3TfmD4q4QXzWJjmEa5b3LevOXyUTS+RmuAcZULmGj3JwH2SlMQKreHfw10OXHc/BkLbeDnpDyU1WcQ3tCysxrt2k7wPSPxHGYxlNF+ZKp79PpIE5LlvNskBxXySaN5QwO69uga2uI4DkC+1uQUywK7zCLlfOGkCvA/GPJflklYPlSlGCtMZiFsV8sN2D006FjmCTYuxBX4m+56C5/hoG7BAWibUw2ml04EjRvLS0jPUwn4VXmgDCDR1UavWDmqe9xTllxOxp6a0vomhu7gOU4nTwjQVUHUZ9sL9r7GYx3UiPdhSrPddzWnoG3q0dMdRrosNrI69IsBOPEVF9kSZZ/As8kTPPkte9EmHXbfVaAI04G6TBr5HUF2mmFOnA5K05BhAeiVRBmtB9LV6qeQw187fvMcmf77odkU8RnHGEL53I7QNfeILu4C95d31820GgH+TC6fKDJ/aeQEUNuNbzXMiykvtwy+EMyD+UFRpAfPjd0TNofRJYEEiIH6bHNflSTerZQnHBwBdouV6CD884OOu8c/Fl2z5/F6ZRruLjzZ1yidE5REGYKsduor6z67zNqnGA6wisc4aGpn3i3NCJmkaUlPt2aQpfXSe9QJmxMuEkCotZhy/2I8gkkm9sq+ScoSJDRIGDnmQuWjw5ve99bordLboVbNrRGaWiRgqJAc1UX4oArc83MZ0mPpCXUV9rlxbzODtpZ7SJ+Wp2j+3XYn8jramn0sYJdrfVvhyjOGuesQPEoT9bUs2LjVXMRFQXegIwufNuKTrN8Hfl7/DRoyygZf92Y9LWXgGGWj25vYQIDbFRJ9F3v3UwgzfqsqB1CjuLBM4rOPvom3iU/h9nyREV5tjlardB+Z3zfgKD7Btz0z9kdTB0vJ+PybQxnKYT7pdyapBKVoRX+11Q2yjecS+W9w/kstqpO4/TTjb2qmWxeQVuHWeQNZvLZtprddaia3eS57nUavt1sHk7pkma0vSlA+y5pyM1kFVB7kENO7GE2W1qfPGOVJkqOqvIeKzV1kKFLENfWQUEPVCWEw2BUgBsTs1K2A4TUgGp9kRWSuyhdsrAjfCrXakEWpyZeZRsYK9pI0uWNHCaLW8nksW0mMTGJW0iShI0bpnDtYpJtm3QJ0C4jxT5WQDbAgyzCJopzcE2VZLNv8lOUry4ymJbFJ1iUsmN/QT5J6+X5BH1QZLbtSeuDaLTWcJkF4o/JIxeAbEbrCE9U4E151EY2l5D80kwc7eU5vbb2HUHtd/Zoo5GlbxqAw65eXtcpzItyEr9wrL5OUpFu1x9oa5CtN1H6MkE9Yqd0zY4McQcOlPoI09j2VG9BPT0yoj2ATM13U1X0fvSK/gU3WFeKEk2wp0CmoPssBdpnJAO9DNy+n7fl+235TqaeQ8KlsF1ouzz9AsgkcYs2m269VtOTVNk4JiffxEEGaUOHufyCK7Xk8vDWbzAOy7O8Ls2CaRqex8Ujo2gMH94WVAIWKnL91VP2GWAinhWZRtC+DyCQru5zAEwr/GuACpGgBTmMmco8H13+HnmbeMP5hgRz6vD1flHsY9m7aN0tOOFeis5z3ReuxRsT+EOfW7C70hax3t/SC4GkW4O1QrA6CjPwjRfm8toWDunhsAK1AMzQHdYhhVjae+fUcHLp7C5FtK5fkh1FKlGCZQp5xGqTFiLMbX4PlV0PBXOAdJje8roC3XyfSVu9ucnBI4w0YiJMKMtt0IOc9rmO6zk3+ZWrvpcHV3dCGsCRq8E6zHnFnG9I5X3FzMAbI4xr/RmOKYlYb6/eC9Qev4f3GJAdPkldC5ym9XGS3XUOX9ZTGpcu3lAY2+EW3jRmEldr3HlsUbaeoHIPmgZRyFIUra/7jD07idI5VhJm8majuikePIQBDmuBvK7PUXpXRXfeTo6hrjvYhoPKViFOrJzurdXM7n33ql3ZGryF4F1SW2U8K8OD1u+njSz45nn75XCTdBvvJI50IkitU2IritNqyBo/lUumaVNb2SxtJkHtcw3bOEgULtfDHF5rdd8I67WaxGWCf5DMb1qcw5Itr2seW0s7Mg3VlVu+MepHpX+vwBJMcfZ3mkR3Z2vUWRxNWnu5IcTdynJ/XzgYwdpVh3KrB+MSFES0iYW5KKdQYiozcuJdndvrjEsE7S1VBZgHCauXdQHiwRJaa0TISG9WtrXroi6OtEk6K/DsukiqO5hyQkR7iy+rYpHwCaI6BRIKrEplJkKchMKF5Dp1qMgLFOR2WNsGrfOdhj+ioulguDPZi+GF+Snv2ivDCeiv6Dsx4Dl4KtxNurj0GwpiO3istehOEqhjSvMxpjS+jBHOfNwiChmdGtjrPmPP1qJ0jomFmbxZtm6KO7/i8gdFSV7XjluPl/dZHmI6WlZ7SkTX1LUG29HNY6T9M9rct8qiejCaB+mZ25alWxJF2xQuUbi6DnPY3lih1mexpcVJC2DtKkpVwW3nlCXJ96wkW7KYROqx1gYwRPGGBdoOHRa36igtnhyceeRbmw5TvK9hCHFN56c2M/Js/A5Gkddv30w1zWPca4iDIqjmw5F9SEdSi+r7jV9vEf/1K+wWvYCXYREhmYbNFKnzDKcelSScbnS6deRBZrpKYg36ywkuqqCBTHGWE14S4iAb5HWF2iTOIx/qCGeNpjv+5e5gm7Sgr7uTzddHEo/mKsOScnvuAs6zGx3tBqJyF0CktWgHMEgQynt3zb+XsXI5b7gQieS5aKHyukyAoYPcJMBAB7kur0t3qvnzz6MYz3IAgl0pDxUrFAE1Eg3jMEOgEa1BooO2YT1r5vdcOkxiepqOrrGUAfkaFoUmaB+bSx5+UJ2Tl0Tq7H7KJYPtcm8ZxFWOUDkCHOSTs3waVV+cRzb2r+iNXrNH7NBgk5jTc8zmvNMkVsXx9NI2VMCH2a3g9UCXGKeJGdeOct27WzhBRK22ykYxmbDmr5iXCfdNU9VRHIOimKbCC3wBGm1ajcR86Me/lRdDRwpozApZuzDITrK2iz7sIFhJ2aKPgHwQofK6SOhk7zeNA8nhQJ5RtvumC/0V9jDhI3b+4rl1UHFWcijijps2sI/4rWkinVHUSCp0uLyZVCZ/edY0xkugEYyDRJPXRai0NRINjxYG8AVqovmpfFEMbyHZn402jwz4NP87BE+eEDiCtFNABAlOqCFu8Wq5a3eEsB8Lgt0LDkJZK3nlwWpN0DWwySRsHfmpalqdwX8J8JT+B8Evr4sQ6GOeVZuRT5lHuqs1pXPjef8uixehgiwOWIIG2Yfs4xLx53oGuZ/Dcml+TWdipDmVJpbmdAZ/ad40wkukE4yDXFcImb2Xxns5x6fylqnnkr+4EL4sJpInTuKiGVtrSVG/TVb/dxAS8roIgSZ6p4TU5f0QMunwttgphs9cehsrhs+YWb5ARMiLI81WwRoSBu0iesG3UoKC1WG0xzxJknDMospzkMYvk7xX1FZ2GfVuox9ADNdR8vrVRY5+ESn2+t3fX79a4ggKv73+u/NcGDpi+Y7T96icUqwsq5sSicTkLI2TKwxrRKy/WXdrUNnJ8zSVXeHK0NiQZ6en6uGg0ml62kidaXrYVDZpz1BFFlPZvrKBcESrCMQqQpScAjA2TeU1j01gec1jU7vBHz+0J+Gh0Zm0nevj1kLEymhVIMW7SldgdbQetSf0s7k/QA4Q2wUIr34P4oesKo9KxEc3VQmm3EpzlRf/tZ7gOdBG9Qh1zeno9hYmEM3JYPuDzeg0IPeD8KYGPwWcAyS/Foi3hrqTM0shJNqrfryBxE2eRA3naDNeIIOoeAAr2ZCM2sPF4+P4T6/iik6eNzAnhoQvWVreT1znP0E0Pj3p+VU/cvQB3EDvaysU1FFM1s1PWbKagD/4iidiTKri4yh9mGQDzNQ5iYih6zxbTFld977QdFWe3UQTKBfNakoUwC4U3tjzvkIbgrwJ43qVR2kRxfhnrxpMXvUkU0ZW+SUoqmR8si+iDTakT0twvtKJerusbjodfdouX1R5fB8VYEoD/kUEXS99s8+Pjj0uTXV4f44EzqaqT0smtxp/AAko9+rlLcVO+BLgozfqbrHRscWnqDgHT40V5zzD92ziwauE/kedw1Ma4ZEnmUbXbMb+2FOUznlKCDPZ+uQp/QbrGkQug8MUSdOcHQVb29C3Au3SP8GizCQPJooyXjeHuH1j5bm442RFVts3QT7CW7KR03ZClPH6WwFWP2B5z3VGn5vrlEER286RUjjmm4K/SUg4rv1MEtdYNt2pZed1iChZy3CypGV9krhlVLptyy7BCoA1WNFC7ET6FiidS8sU2sxcZ/QlrN8rR22XBx1sU3myD1OEL6tTyS6t2hhJYy4nK+2YDBKhx+bycl8TC0KH15ZrmEIoMA8OK/K6umNQTwtvLdV8zcTjvLPVn8SYrrB9Cc0i237k5oo6t62+0MiUMRQEo4YPJZfTTBdrC66OaCK0wzSX19XSy3eGDlbQEGCDF2GCHyyOJFBaappru30JjaLbfuTmpTq3rUBhFK0plHajDol1Oi+B4y9kDoJFXldrN6Okgui5DzPpFMJRbmRXhbOibSyJlhYFsEa1gIus2kxk1L5ExNhAbCCawmrX1TZNgJslSPE+doqe1VVN060vaLNFxU8drZ6zoucOYj6c2946kgHKfkk2MjtxC7hpb8SSVNmfa1kZvkeSrNJVWZbfa0mmWuh1A+XwFpLJZhwRyft2SBA9/6J+/M0X5h9V1PTea9ORwvIih/HoLrxdRaN77E7Rm0l6MraHcWvbqHXEsUk2rG1s2s3i5jqte+uH7ClNsmgV4Dmrs6IFC6bGf0YTJC1Ai2u5BcLS+geAd/cWWxfNXboqXSUAqVnR+I4MtYBfkDh5ofhbqigdFUUWY6/kVausiE89AmpJMs1Pp1X523ctDhqFxzWCg0hjhbQeVU2zulyihjWJiqa1Ofz1ynPyFoSXXokhDnqlvK4g2uA59WbHdNEV26uYWe/ssZV7Wo0kMDvYF043wcG/02QTHKx7RZ0W4B3moILvAl21rwkeAuM4SqI0Bp7bsmL0W2ZTGdBGkgxKRzaRt4vIrU2VjxMbyszhT5wCOwSZ9CbgcVPrxuPypEbjUdP5EB3kn7yuIDrIVR7FD4jiEzl6k0u6YXd3hGeAq/v4B5DAR5C/OBafXPcxdq5jJ73E987W5U/uCErnuO5lAN/AQQapE+Awl1eoJxoyhFA6GN31M5LQKYgnnegQfRp7uXYyhJwHsvkqni2O+xXEjukl+B2/GeluIBjAHOaBYh6ECCgWbBKE2i6FOXy6BFGRpadZXnPT9GaQhn8BMXsHOSDwa4D68pTZ0DY3YMePG7Mso9vb82yKEDVHqzVM9Vd7//ZTkAdBBrItTFy5Lbpi57qjpmgi2UuLcgj2ncJsnpvNjChnC7QI+flTsEiHlU01/wOsbBdR3ug3lieE9XmeQ0F6iEN4WAazQIZxC5knxBKakiBHPITdIkazWIZROnZBFPfMLdldDKXUNZ2f3mNIswl2GvK8Qe8YcxWJ1hJpJn27nS2yqvNdrhbBMa8sj77JQQ59G5DlBs219o57JwtcTqSiMkqyuzdK3MPyOLo8FxP+62YYCMRxrcDBq05hgjoW4M12xNdPX9Om2xfU0ZJ7ECznAPxOs1zJ6qIpb1SAfwzZqJS1bVbBJw69VcOZkkCFYkkXJZTf7kHZVWfBqYI9yE1bgTfy41HjutmMIcHEZJK86z73VGfPdwJIDT+5KNbylPVKcCgF0L44rxs6YIwg/IKLvYPA+zOIJhX3Ss6Lp59y3Fmzx7T12aldRXf+2zIEcphazlPrb4EeKdOt1cpV+bofykKw3NLJsnV0kCcEb36Pchilfbj2Rba+gWnkqRHrwQ+8LK9r+VAFYGXN3fsSqg5Bw1TyJUqr2yjGg5hfoNHWep2FqdbSNq253UQaEgrtB1yFA/sU9tpUVABCOVtvZENY/GMIbXSIc1QU8C5FswgSPjpbqc6g34U5Fm69BLFt3/LMqT25wJdgbW/CFeRKnrcxbSePl5DIjB/CHPsQW+fXqvx6S0Dr815rosqDqlHMoVrtFYvf9RCD0wRMi8oMa8blQ56cGNQtN6walHLprKuJlZ7Frp0dYlj1mC7q0u1B+RBnTWxl4VXDgz44ukBvYIIZWq/Ac4k+rTfjh8fHoXl+r2Duf1mdmi9pmWdJCL+QmU6v2ClkIV5tZaqzIGV5zqU/QnuQaRnbHqqNrQZdZYFJnEelWUBY4loqebU0UJaXGRbsQEJYHgIuJodVxGAVOUpgpNk7BZDUM5m4Rwv94qspNWFeQ6tLBPbA7fK6JHTz96PZoUmk8Y7Mkixf/l5FOSgu725G71RtW1r9G80E2rk/uO9ofd10gorOCoSEFnIQBwii5KFDMkhENIRQaz/D9AE7+s4Zw85W29oePctQ7zRT1pxWpDYgl8Pq8wWsYPSmBTisM4p529DIO+Dktz6C2rc88RYnPNTo0emOYRr1j062f5lUp1uo0hINBoYdvS+nMAHpFKvvyTPqUxFeXxUtU+fgyY6fjEXMFQR5c9rmrOF2GAcxI68r0Coc5CJXe+8Cr2uOp2O+hsygd4+0t1twR4WKRce618OsveogzsEpB5JsIY9n+ioEZkIuUdFClSnQ3BkRM6JLcA78paj/O8gLeV0TuUOxOoxS2xip0mWRnKQ4v/cebAlixCthOqHx3MgK5bX9QH4Mn7O7rPObmO2Mg+17uU6Os9XLFM8515pje+R9DsqnLH8IPLp2Mu9LRG4Eeoq+BuUgAeV11eT3ZfsaZR5p6qCsmSvu92ANiLnhJnFZh0n54s0A5sCN8roIoUZfVgLxqYF9UVHL+zBr15jcfxzFD2cp8Q5bVjfU0ZHzJlaCeJgSQQd4tH1xqMgec0TAlbCeMNqCLi+37dMWCLlFlVUm2LBqshr3I8hmlnZzvwJrpCd7OTWI4A6CRF7XPNrZdwieUFPvp6/ZdStmzM+IEcFdlr8E4GUW6sDHBz6ejI8b4R6AjRmkAxcfuHgyLiZDAWMvd8lsvc7SNyzSgY3ldYUxH6EefcyzajM9W6KaZ5iFGtPASN6fY5oKjuLkEsSZ02vq5DwBwb3pQA4zbuwZN6fBVnkwbu1ceLZKuj7431rUnmV3PCo9yxbn4LbakmxeO+xvuc8szN505Q8TcJ8n4DKp7qavNdS97c9ReldFd2OtZMv4HqyqBFxFxYPLiQ8qVryhQQ5zSV7XPNdgkMKRpSvPt+0C+Vlai40wLhxltvmanuR55v3O5eeIPAmQlw4vNOCyJ+nKseSyimNQFK710p0P5Vtiri/f3sIEullburJF//MgZOR1Ha1WOeITb9cekIAAN1gcF0H5a9d178TKasse110uSk9lE3kVlcvhd/4DogLxX/vOnTXff4A5iPGzjG8GSAfWl9elWV9Nb6zZri4vRQnWiA5PZCsydfWXmHSGfs9/3z5DZsPcH9A61tx88J8pHdhhshwmyz5NlkWV5yCNXV4Y7ydJC3KYHM6T4+eRLIX1wCwQhKrykSYGGHtefM4wgD9ZTeytp1m+RoNOPWsxVn2f4RoiFrvKaldsX4WZTLDifpvujm/Ri0lsH4lsQ3tqNLLBb04YS2XEa9Xa02WUYBRvOKiDkJbXRdPJ+6Z1KGdPMoLeQdSyDYx30e2U41+hw6k8F7cpV2T1DGEIHxEtvqBtPnmpyHXCDnEOs1UxW0N4dmMeCjVTr7JQSIg//w1i5cu042iMONji9FZ5HHcx8tZPzorGxHj8clSV9/6meQrwEumtG0iF+XHFHEmG0mwsCU5Di5XrYX46Bo00myDUjDyvrb9+P3VMWk/nlra9z6RrOZXTawkgpmn06XPmcvESlbpD8+0NhXKQ/vK6KDLhKrfmao72TfGRZOhIgoUhs8Snp8tzzeWnHXuk2QTePfK8QV+PpSsSCRVRurK1wUVJTcYQ4gTnP4gUeV0zm5I1JsL349Tq5BFhccdnvYngnYsBo1mdizctxoF1nXlnLEcg7TZlpIpxYI9dW2JDWnmN598JmkrJURzX7+G4z0Ea5zAP5XUROqmE+M+jHLY0pnndCmJYubouHFRo+h5eZDn7oJPtOUGBRjIMgWzbHhWFRqUYqeZ6cV8W3oE+cdhQcBuhWY2EIJkLqNRIMqsROh73KTuxxUAdJJe8rnm0z+M4rpeW2kEOqM7cTJvgq7MEiooWSEeZ5lIAvb77B/QOen69BOkKx6qIkuRlJIlzDp6KzwBPGM9D107wiBEP8kchf4QU8z6FnUkhCzMrg244xry++o8KVGDV0Np91lAwh6kir+sih1nuHd4Y2ziVrkyjLPi4Vq0vXIjl9yqbvnNX2SRduwSb5CVI/4zqmaRPi8XoVSBdc/Q69ku7DLv+4McVcgg87w1iGEmDplNx3XVS1UHV0FgnOKyi1sfrYeb+wEqWhzu0kmb0Orha3IP4Iau8nt2rn1J+w0EddAJ5XRohPdKd9J18m3WJJtQG57+ow22Fwj0rrqLnk2dAUcMVCgG1Ie2Cva++HS/RhrJfyE/7WamheBlVnPdaIMN6+WpahnsB1big16un4loCCuHDk5A6pYmlmO9sm0e0H95RHEkCmkuu6WUWqyNaCzsnmXWJA5hhvr6IXjALfIIFvlfoLrQkgAepJa+LJZmv0CIjsaUXTti+CueZhIeu+cL9PDMtw80z44JB51m4CXaYWQomfokTUC/NnrMBA12AHGYr/5sqZZQQPF9rDI5chKdkCAtRqMPMQJFmzlKI3Ry2WZLRTTSSYtfDEnLRNciolVfD3LYO2tL1f2qxzG7ZrOW5k1gmt9jhH+QNbEIWJ29vgkEyv+EAD+JZXleYcJRuQRUFOCSiwXzxnLv656p6qtjOUmHUjqRQ+nAz67rP3ssbeS5OkCqyeqp6CZkN7ZM2zk8SDIEOgkRe10X7RtQ7PxHQ4byfx0hrbmrMs6JA0yLx5zIW6sBn28pnFlqNzyXYw+VXzVp59xk8gsT7HPs+y8suZMXU92lOUfZwlWs2c5suhOfIDsbSm8S6SwJ3mjehwxACMTnIc5BPUdeEcZ50Xmfg96qOc+e1i68fev4UFeoXgFxI53Z9WXZtWXldOdw15SVoIqI5vBWU3sK7Kq93rA3OQeTL65rnfsYePrVD+VY4vU/w/IZCOHCsM8fu6kNpJJSaC+fgckX934FtFHOfRGHQME8gpTCNk2oFztIlRFVGnb3N3QOsveqJEsogjgYGd0fD0OIKlspQqmFqmcRt+QsooyaKh0rdD1fZB0BdGpugvmnGKvA9vkuQIpF8VPyAq/qQxQutRvlXlo5Ph7qqH3m0afR/ru0m5dFW+2kwbn49P87wAhdAvgS5S0Q6dhWpQiO7jIVsTT4qiiyGZLUZGrMuIHnP/Lr5n1keT9LVqzYAnCh7v1GqO1FHnqNzoh6hVRlu0DqMmvXb6//BEUZbR3fGSNUhBn/3ml3Mv6b10fYrfE6OHeIWURFHK354EN1Wwy9k21/f20dcXCCNAqYlryzANIabKDHqAlPaUNnArevqYVM+gA2WEmlpNE4mDWiKiNvRVcdQT0esX99SHGjHmI2p3JRp6j/HZcymDhFjtob9bWfMYRdmYMzhOBkxZmt+n4kx2x2sVlayGUXM2G+ozdmQwzWXjD+9efOOQ3ZiJFkjJmAhGV13QaqhTXZ9V+xogwYF64hNb/D1PtmAqwqJmKrNb8NUyjoEDGbIuF6kaWa6DWHaIuORpavBXOwHoEn7o7lyhX2nDMgiKmVDmaDrlWn7BHQV5NMPopNYMyLdBDLOiEQm7WjLz63HXSO+gY8gfyGnThoNa5BZocM5KG9DaBGv0e0cbflUtWY6LUxIZyOuosrNzVmdOtAtSRoW4AooOMxFP5NWY7GMbs1OQdqL6bhUOl4mTaBvA28FlxpuZNn8I/PoDu9lZX2YgUF3cDeLg3HcRmRTlGt3tKLMIsak89lwpRB/jt2tqiET8JWKzruwy22YetCNAU9oxJK0oEIKuvKctkoB/6nq2hq5qO3RdAJSO54mTaHLbRVnG67oojIT8fMOr+6qfszEwDu4ypPIPTC9+wLK+2x1fQkQGsReFgtsG1AaH/VFRUw8LGXDvwb1ifZRJDGgNXJZIqUODdsjYhRw3cJLGy3KLaQLndGKLMIazCkx/4RW9mCCqawcI6OtZF1ktknc+OFrmZHJJ2LDzqXfnAFZVHPWC6eWS9owAfNIaLpLbDMUPbphHs6WsCw0xBYwklJKhmcnYXsmZCohrU3qHxSc70C8vRHRv0Ug4wA+q/BQvLuXYXEoziMLGMuMaV36fgyTBGktbQXaZg7zB6cCA29OCn52OVCjcffrL9jo2ssWUNGj8yW0JwtXjWgRk5B9fgVK14sJJJZuvIzWQ+qa3yzyqo6XUHtaou0FaXkfjkDGQ8pSIn4VFbBhWnWFAs7tIypsG+cadWUC9jUaQ5N20FFLZuHh4yS7u8iKUs+3XE4Rr7aZbPiTB94pnpQ2fwI+lI7JzvBe81rzdcc4Ki5hM8s4sHsC2o4JOXgBH8oYfDv4UNaDiVhRNj4m1bdl5rbIX4JHCJ4+gWRzWyUp1nkHCTrjvKa4wk7PlXQw1utql9vtm95tHVfbdWw6Q77hOFvY9OuCs7E/fv3rrARrvSLA5RQxdZvJhol54J1SBKTNn4ArpWOyE4oAbn27dnSMo+ISNrOMAx0UASG8gA9lDL4dfCjrwUSsKBsfk+rbMvMpAlmS6KXgIJdwWUcZrJbwAeBOST5h06dYi0VjsBMSD7f8KC2esBdAxkWRGDIFlU/GZ3UWW26jgUWaoYCBt4PXBA2fiNsEY2Gk4qH8s/La96wkpqo4y1fXFMeo2ENSRsaDdHZbTpRVJeFKMbtvB29qujIRn2rGzpRn61LzcS7I17DAQXmanrQmf9xR+dmYupiQf5kSVvyrrk1xMoQzjHCCaEYkcfbg54czEePkGYc8ipKjqrzHvBiTRGaApLQxKi0ilaqgDfnMGrBTh45WXZpASlqNsUl7Zj+OPM3yak3MrloG57OKuLnLZcO6AmgrPg3ikCNvxAScJSfu7rARiZFnyEfDvFJGaqL2WXISAz4TK4lbMRUviQm8O8x0Tf6t39NQDjaVUcpG1hxEgwrYh2rb1q2ZsvZPxXiC8TCpui+1DUKs5hoDIVN3eQzxVSPLmG9L+U7Q9GkF3mA8jPluC9Qvil/0WhLV4fAqGAUu4z4hX28JC0r6MKkOx4+PSfWkwGysWD8CZ+hJLMosYsUmnrM5GwqBzR2IA+lvqlZMwEgq6ppUPyw5M0dp9wPDbAG5aDdtHuK2T8Z1O7ljaONMfcOP0rTPPXZxxWRcoiylilRGF3CJ5yauUBF6bPu41KgrEzCt0RiatGP2UGXCntSSz4qfmKdfR+be4dOxOpm9pXw76MRcTDsYN5NGdC8cz7e4EzcdNY8y+aTLu63fH4u7OywoaflUKzw/FrvEbLpwJ1zOERhuFwOaSFs/KdvtYPCSj/C2XETYIQI1+T4qwOoHLO97DpKxi6aciC3bIjZcqatGJhZF3B/ubrthqybgPcNhMOJEIcLsjDlQIjoW0vGLsJSKKV21RnWFAvaUzYL5ZahRVybkaeUYmrSjLbNdPPyNnmJ2jDwoOhk3D2vdHUXUvDNzMbVwPE0aMyg4r9p6npXAZI/U55OqrDiLtcpK4e4Oa0paPpWyyo/F9u+RLsETmj4XGQIo2vmjtb2rConYUJDfhiGV1e2Uld6kJxNwq8n47YQFX9QRM0VAW9JY7An3PzYVuU0Yr6BHOGAluTWnJtIwmyxmpu21RwZ1d5YXccMnmK/icdj+xaVtd71nbllFxxeD3Cqms7XFiSsQxfKTcPX2sKCwCxNyonCMTOrvAOZ1L2gj9pp5rDC5AzobsMg+Qe+8tGZZS6bSnWU0NuWpzRZ4rlyCsspT/OoVMLkZIc4uVgeonHaas7CKHdOZVX2YRFtWjdNO6Ml9ow3lnqxA8Et7IQWglWaSkWoXUV6vZcdVukqA8mxGUUaspQyz22kq8qrk54dUH0ZbLAxaNokaoh0Kk1b0pWZUkZmeaJcNaYnRmXA31w9tN+bg151cRbhe6NwqZAVG59Rd9LXQdWIONt1Bz4thbDwdhwpzayMnOodL3EnGVPZgAq5UjtEOseRyA2J429z+PyrLHN5UJTBlUXVpBcuKCzqwsKYFO8jSZj2ajsXNxngXWF7ck68b/Oe1hCOlq7s9llDBUMBYKRsOzRHZmI2m5fxTxaO7U2gs7rxhZFkUImzpQqJkcDfZribvbEuOslnGc0089eefcf59n33FMuEb9/lX48w9C9tl+Sq6U8SH4/Mq5o3DDBkgy1UwlBwu4lmD+T3KYZSW3bAssvUNTEnGazRj4CPIX67gWhHX1g5HQTgFhANNjRskuntJd3w0K6xjQ6eTC7ZjatIyutzck1/VP8MNnQHEVnD8Du/vLLq1HVNjB3d6Br36R4VSEGd8S6HXrKBxtmJqDBokmB+Djs+5GIgauh0cLxpTk5bR5baN911XAAexH4Ch90nAb49U3yNR3nZFb6kzBZiHwZXmOK6Pu8byc1jfbMfbYhJsjbGN7dz3KKm6HnLcasmaSjCLWUJwAkwVdXvk00Y3a7d29hh1eL6ZZMQfFrOKhZh7clltDUz1fxcr2lZp8jOr63uhk18vo/UmAR+ypzTJInlkA3H2wNzFgIvMhm07R+ctcVsm5C4xpY1OAwYlZ+OwKwhy1OsYDF4ckXKYOLuIw7qcNjwmgbd8/oTw2dxrtLorE7CoeqhMGkCX2wIG1RkiuJwjsOUumhekrZ+UCXfXhEC549c/DS1isnKK9Vh+6cFgZZbWt0vMatiX6ZZ43SiatGRQcIvY2Z6Rp2NhF+YNqVvKmzIL7+2D+Ox26qcwKXF0d/01NQsMI55kinuxqKIpLtfatlD26ns4x1zQs4C5TJ71utxxFD+cpcsyix+W1U0R57D2KdPemtMVFE0FSRmbGaCtdqeu0pn2ZgIWNx1Pm03b1nG1TtnQlJuYp3dRdzbsy4wMvYNazFGcCN4nlnKxOLuIebucNuwqgbc0kM3Pqup+TMCh6nHaGevY0e0tTNAXcK0LgsHlFPJkm8mKJznkyR+rkjZhCk6SEdak7rmj/JBX2uyUT3kR6UN8rouzoqqZHrSdW1PUE38ndETU30c0Y74g1o/uwPVpnq21fKcoI97609ntNvryiqZnO4PWTLIJ1xLfpBV0uW1hvqvMlvX6EqMyHlXN7GzHt2V6puPJbtKGvtSMW4gSPqLB+ZzdXVO/8WAq9hHSMuLNRJfFbjshr0WkxDGN38JthbY/k+wttGNnpBsOi24F+2pFpSjzSAy7m2ZHVQ8m5s2dVB3/UYEKrE7WEUyuyb9HsfqJU1kBEVdSeW24UlqFgDMHbd467tT1ZAIO1Y2XSRPocvPFKb0H8UNWsQ7Q3Gd53FJDAGEcU2FZq6imprWLxC/Xx63jdNvuTcD5tuNtJK/ZwjMGlI6rHJHi7iJ6IYHWz1KIcXVPWyhKicNLDwvYRZhWVWb+tkOQHZhRYybgSqMRMGoHVW5ruLB9xYRjG1MekQGY8KbT80GG1Qu4VTc15hfKtr2bgf11423SJLbsbLPhcxZHCfwDrNo2Xn+O0rsK261kDCgvIuJ4LrcNryuqEnB31/Kt42p9PybgY/24mTSiLTMjxxoYIXTGB0ujg6uxYSvuocxkZ9gR+8IJueOHypSoBMjbu1nrKC+/3vwbxCVOAs9ovGPSsChNs5Kg/PKtAIskx2xQ/Pa6zCteZGLoJSgFFwqL16/qZOmtTI4Zh2A6p0hRBXpnWNtKjWoxhVWAaSE+wILs9UUYbZoWZBGV4C7LIRChNIkv+t7AuKxyIUaTZEoQFdIghzF58LMvMAdY5VBRisqmh6bimAkxByHkzDquHIhhHoPxqPN/idLqNiLkUk0OOpsWWodpBdYKPiHzdQuMBqRxJxESrvPA0TUEs4BsFpA0PdHbh3C+gPI+W4mQhjn0gEjo4gs0j2j5EbZskMGY2ES6iycDk0ULeZxkdxdZIcRq07QgrXolAun1Wx0I1vTAssQbjEtQkLVXCCjIpwU/B0+FTPS3afo5mSXJUVo8SaZil2oEJIMwZgKssKg4oPag1LUE5GtYFEgxqN0Kha1i8mhBT57RspxGyVFV3mPtpg4dK69AlV9bGXEtknFwl2gGc5VtYCzFIakmQJEMwawVH/Os2khbQVK1QMRaJcJoTH+GKy/1pLtYsgoy6sWs4MF4MbroZXljdBWgGf1kAqNLNIPBT5JLYeqH4TUw/FPEYnoJnyw2WPNkmlX/BqshiIxiw9ditb2l3voTd3PwFKO2bcMHdcTtYx8+MlPPxLGxFYqaLCq7rg+KGNzC/iiDrTtVZl6NKfFI0G4ppUi0bDMgRWxLRQXKEKVuFVvUZlqFCbY1qDDElUXjm0NZ3SLSBIkRrhtdrB0NSBeJQYRCRcXQKuKZeEtKEsyKf4mIjJCiNOn6Lt2jPSoh6Y1Ygxtk0Gvw4ktLQoVedvHMYsd4BdabJBLzoyifqf3iRQXM5jEWMQpMJoterwApQLymnJBsHv3Ot73PI9z69letNDDfcgVMl6hn8/gerKoEXEXFg5DNqXR919oLJsKu9Td2dLwHogLx0w8A7+6F2sAggyncB4jUkUIyTdg8BluyPAdpLDE+1Il6TYy7JCHdA1hN3qE7sngmDF2/tTzb+eKJuZZykTSHIhZpNVztvKsVJ+tNBO+ExGvT9DtYyhFLuGMdeMNpWY7QVSk4h1mMzBmfQYm2zTp+EefUVkB5s4mN/pTDoW5AWFce4cjwnle2sFIlRuZTpt2GiE7QZRsviYeEZR0m2GaGNOrIVNxkwRG3trGYN1eKc49hDv0Q5llRoIKJApLNY9B7oVQykUboVylT8OokvW4XPSuPAfp0Pb2l1iixIYo6KpSdyVx3Jy9UZuHZTJeTPcvkAsswBbpz6643goMh7oRUi9oeX9OoErjBoSnK6UClltX0VBIFh1D2h4kKEYhKTNQHHjUElVrGVXERl0feBzariCb0XFKQg4Mal11ao+f10WaTQLC6yuh28kRR5pf3SlVMRCzqNFlBKyWqgG7yIQhCuZ4/TejW5bbpX1soJM06zHGnXFex+ChcQTJhAYMeisoFIJwQVkA7cT9DCfjhk3hy8a54Ok8khsUv5BlwhB5MRKKh60Io2nQytNdPpPTh82q7xRVR0MlQ6kuRx5ZibMV6fYHLat4rvcbgSK5plAbaJKhSHIT55J0RZReRiPF1UZBHiDiJ2jmoeeh8I2UoeRktA0iLKljMnIzaSgQkVaIHoqt+jgqz23VUP1f9CDnNlB36OF1fgqLMEdODFeVixVPRoJS8s/rCIppy7loKchrUIFo2+h77k5V29bruXNIElBRmVHRNlF9IL8YbTUUuIebIFGoDUSlow2aR94DJKaIH5XaoIAULNBERGM9AOSmGGfX9GA6tN1mGcALiqLnOxQ7R+udTDRVYIvhcCgMCl1lojeh9XVXGCB5LQBUpkX0IcgyTBMm4zmVWQRUmq0F3hiUC0IcBnIhIjWNsfxtHQSUur75XbBEVnXo3XgNyccAiOSQbAAd6idxs+2t2AqKpC8g7qCwnIp/EUVhBQ3UVAkJSDs3ehGy9qJXE4zPJe8PlFRGJ8utWEIaHmoAYi2xNbp727uVienD51P1gs3vPPCGogD5SUrtvXS7BIwRPn0Cyua2SFEvHQYJiF6Mrqd1waAC8qWpYjUK6yapxIHfr+q+cm3wmee+4vCJ6UZcRFITioUaem7jClt/7OxFienD51P1gs3tzkRBUQB8pqV3mZpYkSkYZZlBMATqf0FhQ3w9RzaIBxMiM0d9sua5vqIi7TmdRt5zKKet+d89GQwQaSmQWEVHSkQTfs5LoDyT8Nn3bR0wOWXZ1fySl/MWuAltCN8kQuFCPuUF0zdxhEhBQXULRT2VBIbfxV6BUdFTjK5atpq/hdntaIkpyGmzNtESz3OpNTiTVlTLl1s+soLzLRuVFFNVcmlNQ2azKkbeL3U07JXUFueT94jOL6Ebf/1MQSQA2BUWIq5aeJEw2TTeGuaVEaf3HdFRh4KYgyzV9lVFCEzqPpgdUVgk1Ij0ZaBABDQZXM0MyR3MzVMkZdR6jcaz7EoYnaiwxMTiKegkN+oKtSmxQ+UzmOtWXEKKDgpNxiJjADrSp49TpLbrCfPLOiLKLaNNeIFbQRQg1shW3rlMlT5kcuuarpKgxDaaSnaLb1td9oBi5O5i4gLxbynIqdzD2SreBb5i4DoVv2GiUbO7GG5JREABU379h/M8RCDiM+allZtcpSOwtUnqxWTTzp88pnYV6cxmLNA0JFG4lfCaTxitcSKwJMY27SBtT4foCtfg+KsDqByzvqRgJPGl0ReSd05QUkY2KB6Ggmg5Yxk6hjIuiUBfXfbgKOQ3FBfQdFZZT0c9CMqnrEJBSOkahKPmNHlBjcg5LWfZ3UHhMwg4rGl3u4YglGtFPZdHIqT6nVOI14VN0Eo9CGpMEghArSnVUmV/eJVUxEaUkEWIURFPWMLIeK6pbO0P1hew66zhtLHFdx8rRb5MciElJyOSQ92uYUeZ7KbydocIZc2LSIYau+8BFciIMM+r7MMivIoleRRNDinznZFR23jq3frBaiwKbUbcHZvJ77adZrJGNCoN4UhppLsypEhCiAmKpMIx6pZQ6QtCRhXbfeD0LSfManBUZMJLl6dPE7MQGE7umgwqL5JE8u0qESEuJZRMXBE0pn+Tg8s3kIMByeCqq5qU8s0UnVbPTk35TzVGuYoV9QprXol8Ka4UnxaaxXQx9zvR3hJiMCjcBUf6wvnPTkkgc6s+AZJqC2s6qyyuuWknDGOoJrKlz5OtXitiN17KIi4Lp7QCjmJ/2aEKhoI5jqRIQDg0QKdRmTBF8nqgDctpOHiWaK3erKTzhNFM2xHhMJUzlETxgELZUOmSDbFoS0Lm9IwfQWHIZVbc/FFUUUVRNoy0YQ2gpYIqkoLQ6lqx+EIybMF1kB1WT9Ou3SWkvquhX8jHHZFIlStWQ4Ts6TsMxgPCiCo0008AMmiAYHeZdorGGyGGGuE8Lh7ngROd5ub6tXam+Gpd17rxSUR2D2krFlM0zIv3r53kliRZjocaxJpMSzmKM2jig9gOlboF80LRc4qF0Gi4LlrLfVMCb6Z0zierrZbTeJKAPJS+nDpNT36VhAW8KMXAi3a8Plu9Nny4s/uDehYg+kpzyDokLiOhDB+5XUEgCOMEVkb5mxRLPZzLpimIZtybLpEs1Zeevf+q1H2kR7ayQlVTMNsURg1UNUxPTiowuBByTdDMRjXnmU3OKZlPcovNyFCNS88+b2lBeUflUp3CSF0OUh3HaMnIS6IoKI11I3z9RxXbQVTTyQZ2sfoWg0BWx76xCaAQl6jQCpHstRav1SHLKOyYuIIydRb3nogqeJQacQOvpHl65Vrhv8JkUXWHzCslCvQWjIguHNbKrBveaiv6qpq1IkxeSXkkzn3EK8JEF2PDNmOvTPFurSKfKrloRpaXEyy/z0o1ysZVDT0u6q8yCcFRm4771ZQITjQIemWTUI0DX3INAIgEvz64SytJSYlFPP4OkFPZyXJF8495ECko+FbMJ85l1TMViTpSairWop4yuh8868fSR5pV3SVZERKfho0sKOklBBbRinrLydwMVvqZ0LXjASeAWalpW4dRpCCH0tZI9GaVyIjWtT8SmgpevAjgrD5+Duj5LYQmjRHFvQlVA3nNlObEHM/eYldKJWQU/7hUe4WNd1/xDW3piSsuad1wGYUJi06s+hjUKqK4dVNfQovTTY/q4ouLc8i7LC0kjirIvpikIqkAfORSbbgU3XLl1K7Z2pR5jhf71bQ2AXWEjmIK8S/v1LX55dB01H359i7LEYFNWUfIlW4GkaBPaJ3r7ks2XV8tNFGNx/j+Xr189r5O0+O31fVlufnn7tiDQxZs1jPOsyG7LN3G2fhutsrfvf/rpP96+e/d2XWO8jQf74F+Z1nY1obmFRptJxUHCV+AU5kX5ISqjm6hAFF+s1ly25TrKy683/wZxSXyCnxlZ8mtH5bbCVo+hD9n4kcNpWJ9sS+HfjcMprhG3GrzBTXujOkrsKXqKOokFAukvkJz28cURwBJNnihvp07T/LMVIkeWVOu0/5vlP3lp/O+wfP3FHOEDoB7HpIEGCRZ4sNgk0UsdeZhBHCZZYzYrvQCySTFHPCs+gNuoSkqG+P1nHuvXtwwHsLz2lmM2RgKwHGzE33rbug/P69Ad5oE95Dhzo28AizNMMUfsOsICDhIc8IhuLQXtUm34+yIHaGEE+KUPlscHSdvL54EZOwQnT8W6TcUsCPXZdhpIELlEc9x2uRsC9l+txHtjeWBEu/CZaROkC4AUO6RZ3PErEJc+5WK7vM/yUrri8qnmyLVopTfcNDKfao78Ca7A1X21vknJo9c0LJNkjvkdFvCGVQ+6jxbzpLpJYHHPijjq85xKxiIH+FXor+m3Mh4iDlPMEb9tVhLEYcq2CfUgotxdgE8stnFPJKK7TbLAjHLUMxJ/s3tgnAOX5LGekGfpCj7CFdpeJi/CyTnMsCeis0oSKTCXaKHdrdYwbWL1M5rdIMWev8B6g9+bl/EYlWxF36ev6adsDS64ZZNNM0f9AsroP8HLU+0YQGMOU+wQpaPFJdrhXsGSXZSozxYjnyTZU+fAQC4tM70X57CoYbPJs0ewuoxwZPsljqM7gOeTLeZqVmrgxTnsW3+VlVEipo8wh1Mf5JVIM1nMmooYrK6yo5jRjYYp5oif4RqWuEFkLWNazCVatPShYhqIP1jMD+o9TrTelOdI7WMVJVke81o+Im4aYtZfbHa+ffDA4ba3/27RnqaMaEln08xRm0e+v5b32Nm5dQSjscU5rGvolQEx/iDdYv5WSG5FOOA+UgOOVisGjZ3L2txWdrvu1gNjuOu+W6j8TRl2ZOnvFsp5mtTTk7rSMFDRBek28++5nk5fbyX44hz21Dh53sCcHFN8iF4KMWXYPPa1EJcHgiCaW/JcFhvXqGAvygw2r3yyhTQdlGRbz6datfpbgfbqdzkAvObIp1rwJ13wCp+vDNmTT7aZl915KTsxqQQb+dUUWrzECfgM0rvynpVgohyuNVyAHGbcOMryONRCFAwCw0liUQ4byuMoXydphHZnnFAcJNlgniI+aKOHsaDDNJv90ArimRwlbenFPT5AZfdGslw27b+Knk+e0T6IO9+hEmzwTgqOtuSTOQaqeIE2ZXdZ/sKyGZNkpZOh/dhZ+ohmLCpcP/HNokszWUg77NkutvcySdbmNlL+6DGCSXQDEw5ensutJnEnxDksxgGmCnQ+1WI3kD3VfW8cBNnBFaVb7ZLg7QuxRpxmedu+Y4D2ptxOSZ7R4igiih8ybEXFzgvcUQSbaLnflt/c4Dbe5pc85HUSazAaWriu1uJxF+ewrSF61tXA5rCkG1g1RSG7NAmSrWYdXneOq5fjqiw55wIu1Rr5ByzuE1iUCng2iwVlalmTAMTuF2gfxh8KiHNYHA6g3RApCmNmBRym2FgHOShrjK/JSgDTf7W2VS7IO8cCK2WdYLEG4WBXUSJo3TDFDXFZRjn2TwI4BBF3uKLO6VbjSboyqo/NZ8FhjXnxJMVeIiJGE2WwWvWw4BmgABH7KDNa7YAN61NltNqJ0ZfWmW2Y6j67HBOt16AQxv0R9EOb2aov3StCR5tNAtm9gjCDOf4PAO/umYnefrOgjmCfZ7+z+wFXLEjzyYJegv58su5Pt0aoxYsim0NdKsEizTTnMXnQQ3yQAM7jqfu4m0f3ir52UfM4+xmTZu/Wg71YWVQ2zUbzL0CznAkMFXyqA/IXEBVVDup4QUL0QQ6HGo7WvJcSl+iAi38osekMto5f5PCSt/ZxidYOZfUTUGIjkSSLYx2YBuoquhzOveijcCt7QmfbGieb/n1AHy8b2euIBm428qLj+Nngfz293psWC88emDQrC7vM85BJ2haXxra0SPywaTb2wkaP4Xc2gxRzxEZf4fDo79ano8UiqzZZiu+nCE9HB+kWuoME1Q2tHQVy9E+Ufxm/DnNYehzUZ2x1/F3O44BO3Bqp15jMsVnKR+y1lncHsScvuq1iD3uz8Sj9VxvlM+y1oeOsRJt1Kaog2cbMCSPOtEk+2Zjr6rGW+d6J0g9ucmqs2m1VdirGp1ogQ+KExEH2n21aeQeW8A/Wstp9dXQLLK6y+hKOGF+X1779X0XHIVyipbn5Mkrv2CVjkDC3A+rYhq/dcQDcRjPLft+5kFOP3DH9DsHTF05L5BK3RuFqpKfnXY4axOUuh6zkOMpWU90xTKP8RbiMtEk2JyZrUIduGh6PtF8txA7ITmECUk6XGyTYeLicA+bMv/m0PexXnxuG4cIBlvvFoql5MuC10JAqkrsYn9li1mz06/guIYxnFKCHHU2JMg5rtVXLjAtuaFRHLqsELF+KEqz5DahBdqtDdrE9a5BgsTOFSYJtvrgcv0vhU2003sbBUQwtSLYxcRUl0sOJrkiHUuVdXOX5XGq7kF44EOVwqqGOx1T7McprYXK51NQOgHDnpMjmNkpEGKiGp8tgYTZ9LvMICyjGatp/3h6RPHgDzEsWK55OMxHCyuLbatlDZbP8E3gmsUJYu/MgyXpZ35JQPvOqnYGszUM0F6OzFmH7VU+Zxc/N1ndWnKKNfdVfKOPuK7DJ+8+t9EXMIAxLA7ozrRpl+xmXbr/gmsUg7cDATgwcjnM9WXYOXvVXA0Ify9FUkB19yfIcjr/UWIdDqt07pDoczuzL4cxcXivNbCEhbn2tjo1CUMctcVfLZOW3XyETiGJrMVz3nveQpb9boJF4OAxS883m+kGyua2Sf4KC3Apn7yEwida455kctksLP2U03H1W4N9fb/8b56I1nDP/3cVVawgxFa+31XI7Xeq7xUZh0z1dNWhS/9lm09GGPmJ3G/33P6dgt5TBx0l214Zys+dLZemReLKujhd4gwSL8xHUhYus4EPLUt9nH6XBStdIulTwvpvtoqmEGnX9rOuXrKJ9ojnuj6hoejOEpL/PPo7n4Klwn23K0uPONl5RGabMN38xUURB0unvs4/7RZYk3zOsRtXPR7rMWy3ESPMV1XuUFk+8FjBM2botiqdfveQxFgtlbWot7WMFJXpanWJ1EShPOWNe/9XiBHfNRd1uPtkYV4oCW9BYq0r71R7pNMvXkSAMMZtqj7yM2Dc5hikWhqzg4XdDh5XqXn3lXn6gEyzwcBAg1kTXfJvefHRW1P473bOCQ4oxiRZeMxInIjdvoc8R0lDF+yomac7dEG7K5+wOpsJLcHyqHXIbPEoKzmXYmrWqfxPZZ62SPPZssFRJS46zUpFntHjWpz7bjTwP1X+ddo1C4ngTpS+cRlt/tMER+tK5+eeViN/Rt0eYxpyM5hIt2sgF8lrYBu6q2e4du661X62R3guR3tsg/Qtu8HY/Svibr0yShVZwn6VAFGl6kGAxf9oQucz86T9PIeXn0uvJHPC+L1vPJBe1XlZyWw/TyfFm0Xj4Ck4++yRbTHHUDDbN4hzkKfsMyhJtJIuMn358qgXyfQ6ACluQbjFGFdpcwViIzKY53Mn7HrEu4IOUmY5ngx+r+h5KzySPWkZvfbR9pNIQy0E46QC2VUaN9HxnyKc271JE6jqIM3tyNUzaHs6kdTtPxqShXPhSXX4so3U4JTrAMnxzk4PH+uFkZsEcpOyaOJ+JudtzOT++blEczzzFRcfh5vYZci52NvV9WpeT42zFbADrL7ZeerUJU/i0VJdkgdkc/DdlF7zVTpzDQhb0Dy7JK5Fmshij6I4hSv3Fauu/1RGs/pwes2N5X+7cVr4VXX4yvEVxkOHyotuqKLctXlQJubIvXAq6RIujvhT+XoElEOxgmSQLM1US3Z2tUXtwKAe+64JkCwfFkjHVkg/zz8E/tU5W36NcljnxTiiyKvfdd4gQXea5Ecwu6G1t6/npNEyxRxRcuWWSrO6C5dn6Iqnu2HcQhylWh9ZZFXPzivq8NbOg9Tjy4/wWxdE/TFx0Fzg8xM6EvKPMKLz1J4vVq0qYJab+Ms9asO2a/GEnp6pn+zT+P+eObK7r7J2HpOdFqQ7H0XFUVnicdQHXKHIWnXrP1D4n+z0r2bnHJO2xbo4J7899jny3m5qIP+fVznyN1BT5+XVJM2kVowSrJQv+xwoUJdLO8cwS6APD5DkP9rZds9rRY3E6ClsYF3yM5OGGLy6+rXY++cvFru8WS5y+nVy+UaFmuvFxYNm0qR2/W59sMt5Cb+06ZWxX7bnWeZCvYVEgZbq+H+S55jNoLuu/FmJb52BYF/0u1Nlwn9Z93RoOOnku8QWf5Kgq71GlMCaOCSG4SYXswFl2cONwWdir8SFc0FuqnK0wTW4hqyeJ0u3RGxGvq0SQzbyur3hgr7IHwBgN6O+WaEdxDIpChjlItdDIsV8T0jpl8kKUvjWz/TTLq7W/I00H4zCPFWXHmbRX2QbGLET3ca7Jz1+Ftr0DfXYhjqxxsRW3v3YuyBHhS8IWAeYGwXGdHJLC48wOUiML0X2cbXZgEojeORwkWHvXCx3rLY2LtfRk7YrNV3Mk/FYKA9N8sjGAFaUoign93R5NNpKidHt0EqFaiFunHGSWncyK/IWVq5yaVER9zLNqI5RTXcouX2E479YeVrK0n+cQUnhqCtWnQcJBYBnyzCE25UhqGxEBAdQ2guMqDiWFt9Xetf2XsLaJu/fsvIaMhd+EqYfTfq5Iyo0zTUhlotvzgwRLPD7kEvXZ5ihS8HaOw3s5YXdezfXwxobAxUfkUm2On+tjLAm0INlyXPC1w4rDZZLs2yuG5VMtTIj1O09iYC7RGre+Diy1T8oy2XPcospzkMYvgmfjhTlsaqjLXUasP8Ewxb7NwwNL2Zzhc1ns7KNSJHGoz7Z8Xd2UOLzxWRonqGEi9mZzONZw8qyrocthXwMJ0dw9zafqizinZ43Kvolz2tbYiARF39gcjjUo+sLmcKwBleXnnjiHo3xCch5iHTNKTgEQkswge4i6hcQ0yB6ibiGZDbJbOW/gItzV0varJX+Iuc6F09rZJoDrk2xbx0eIp7/bXJO5rdIVWIme5GTTbFCfonx1kcG0LH6AHKCxZR37JFks1rl7ED9kVXlUljm8qUog3eepc3rUWPzXOtFUVWexX71lLp+idAtXpMAxLTvNeyPRyDfWDpZ4X4CELtr4InGwQCwiUjHUOW2cV/IV7+rVf7VD4lXQ/qslkqDPbj38EhUPYKWmpiyPXZsXj4/v+RbXX+2QTp43MCdOL1+ytLznQbkMrvj/BJGAymy6Gwd/gDmIyw/gBrK3e2SZbExQXbEmRuunLBGYo2S5fGoScZA8l1NNx1H6wG+1hBmc8fnJKszghn+2kEPjNCfUNkKfDLlLd0I/u4lY8yibaL8uEJ1EGOVZnMNiplVIZ8zhH2SaXuVRWkQx/sktbIp8/rXxTKrO6V/jJSgqNga3Lq+NdNzgCAsKeopz+NQg6pE8l5UnRKflKTqkyGZzeSWP76MCSK2wwgw2+yoovnsxSLC3+DXx9IT2vjbNHhVvuHBw4Kqs12illc64kM2BT5gY6btwLENvoy7BOoIpd7NCksW8jk9RcQ6ems36eYY9XmNBvDdFtq05Dmo33N8KtHf6BIvS/9FwAaRL/F0zmHEOjzoLJDtnqO+WBgrh4YTlUrFrcZ8+wluyfwrIXAJIF+YygxmHudq6WRT6u4UELcDqByzvhUzGJdrhCkLFUJ//BIwbhlc9+HOyK/yNOkZxi+CNLXkue+4XncGxaRa7A4G11t5Ke1a0LSDXESNOZxJmsO872phuRHslUbqN5hPDDcSqBqdTMkkOmIIbS2yahV4MUqz186ov9d0WTdDAQYKFhRAUBXdLvvtow0092Ynyx3OTIMMeS9VOYgRwP3IMmaUoO6IbEq5Q4jrUJ02vT14EfNr6H1VEqDyE6r9aKB4pLMlz98KTYT7VAVl47sun2lBS1l7Xtsrb6dpG4YGzw7Fyuweql1dhpyVZXOsQkkGSxUJl0B6T+h6PKo5FnY9DP2RPaZJFK0H8MibJZqFqi0rVHkEGG0/ZGKQFaDE4Z30+2aLtSH7+APDunn2xj/pu4QJZpasE4FWGcX6kvlvL1wW+8SqSsHXCdi3P5yRAlvfyjGFcl2dx2TGXZ/+l9JyLfnTuFvDoKuuekxV50tPJe6wp0hbaMHYjHtHFbGSEMg6nhvX1rrvASKXmmy3KMUrBr70IwLokO7uTaDdPf59+L7dzMwifItWBWH1mTYvi+ELVlC/Xh5LkV3kUP6COiQ4t2TQLVOyGKNJTBgmWJ4tAfATKplmdE8JHkItf1uUS/wSzx99YQSN5zKIpTRZdnYJz8Pa7gwFEPDet7cf2hobZdJiyytNL8DuJgOmpvFBQTmqLsvxIbBTokltoBgqrSIWze12CqMjS0yyvR4u1tTOJNrhk2AHZu7MmDi7RGVfuyaHMaD9u4uDzfKoNp0a3t+d80Gj6u4XVZ7WGqdDFb5hiQ2lq+oqvDkqy7KZrz2yLfkb+XCDhG2LhH6I5Lf46iO2W3BdR3qgu/E1XOsXWPidCHKbYKDs9jUVH0qL02TbtAc9rxrBNt71CLAxysCIHFeJ+D3PMsdoe5F3LN8sNiDvPxH7s/cIJq6BdYgvb4Y0jFkNOP3FPvm6EDtmavJa3cU5hgiYf5xTLplmJ0KevaUMG2fsCg2Rrq/juPL+hGixPhUKB7KJcWMGNpGgI22DG/5MHTdoPThyDB4Nx3w6F0No1bmjkL3lYOsDKjnDcl3Fh4blHet5x+R7lECmx3UxYZOsbmEb+q4Ye330crUBHWkEeKmaxwB8sPHJL9uHS+ovFCWOUVrdRjG/n5WhbK7wNLMtj6Q4m8AOzcsMB6R17ebz9Zo7yA65YkOaTxf0pwUHYJ3tnnagAhAii42Iu0QEX/1Bi0xkstPCigHcp2v1CwhFn7AOMonT7MzwcopS7vsSk2W+8sZMki8qm2TieiZ4C6r/uj2kESdD4QWy9YJIsd3Nfq/LrLYFoAjvymzo+y7avfqMseeHWuV00ODSFpJssUbqFewN4LtEn/tEx6rvdxYnfK5iL7ku03x2m8yJLyzxLRFZeWZ793xCEnXMBJtvks+wogREnNsmnSTeUjjGZt0twk5uho0jv+s5pMBEugRtVjrNtkIhgUbZd42ZUNsuXv1dRDorLuxvmgIVNtNwGHa3+XRUlf7zOJVpsasjmQwbMp9qsZRc5WIIExIJbFYOkKdYaGSKZDqKFcZBgc+MjfcDHe0L9hUvcQ+ev9jKLZySNBsUleoa06Dgyrq2Pv+Y4TLFyBG+LfsuZfRibZt9ODtIT7ximEReKjEmzkZ9piUZVEImRTjDHO4UJSDmp3n81R8IPPqYFp6VQn21k4zl4YmUi+bQ18/gKgpysK34TuYNxmMmKstu/7Qz90gF+0lnmTNOmzeFOYmOfnes8sH6JxOv4D/90Oe0Tl5v7yEe68nDLjd2asCySkzS6SViFj/5ugQbiKgdcm6jPFoZ3/nGwT9Yvg2V3WWeaZjS7YdKcGu2ncp0cZ6sXton0d+v1uLWfn4PyKcsfhEszl2e7BEDzKYAcaLK5igNp8XGkQl09i9F/tUXipQz9fbwFci496B6sAbFMIPHlqQvRUC76kLr8OOxDKmVs2/WnKRcmQTw261hsO8Jwx1H8cJaSUys6IK0f60lAHZjQGGkcdgzmoR9Q1Q/r675zV0ZpH48rsEZKi++ZigjRgVXNYLZVF8cvY19ErMdJ/3WPT+gWaKDusvwlDDexaA6cpIc4cNHWcVHrcxmEiRgwDxfQAwvtDgt9BCnIYRzIVYBFc4mOq4XY9k3kf4KX+tHoAVL/1QqJAznsKYR8fBQnlyDOfMM6dzAOnKsou+0sG9buMY5B/6w4WyWcO2n9bWvY8FsehA07GAc2VJT9c7HhMqmYe5D1lzl8mGUvHKpfNpzLmBzfg1WVgKuoePA0JlNILsZkZfFt1SaXaPalrON/99HCLsl5CNi6BghPqlyOqcps8zU9yfMs5/SAPsFmNpBAJnnJGYCGKXaIJ+lKiNd+t2xfFcegKMQtpNIs28jTkPq8NRKgeyHVU5tqYVy0KXnZcSb+0WqVo0Hl3Mf7z1YXdYI8yiVafuSLz1zWURAVVQ5qT0pPsygN5WIPVZff2hWDvPyGNndPWG0bSthhkjnmJb6rOcRqPu2xNaMZ/w9wXXusBeHGDs2dIRUQB57cc55cVHkO0tg3yHUD4xLZWl50W5mvbTH/jNIwxYr12HeT7N/GwOz1OUOJ3GPKgyRbS8lplq+jsoRs2B4+1cZLfw2RsnGVNa6BjJc+k2hxqlzdJLC4Z3Ub6vOcnmHbFPlL2usMv0qKNiXEVYtxKh8kbY0IQyxYrcO5anBwDjLNAGMc4UbXyV+I4FPncrIgBGKhuo8WVohsA2MWp/u4x04fFzl5Dad5UMD3bJXGcjpaVQOMxOuBXJFO82wt4242zYYzZZjDFKu5/W8Ql9yUrj9atAw8Cy7L21meL0EkuCIfWR6ZNDaH45ejqrxnLU+CZCfs7qVDKTyVY48lBrHMoITPmacPNgXkdA6pKD2S7ayvUhiCgU+ezfdQFLzbIW73LjMnRg7GoORMwotJxQgjrWgjWDX8t7u25zSzOS2uNxG889T+WxQnJ0VZ0W01ZITSKPC1piFI/WXOrbGviWEmLiZvRh/F5IFRP06mkRy4WV18HI4WvKRt/YZ2Y5zhJ8cgwe7iIn9v0cooleVsvBDyxcLMU4A85TrUf7VoS1QU/OLSf7VdFZYFO179Z6v+fQC3UZWUaLavEDfCKCm4zoqybM28bfa+ge5fDMGcTnY0CNu6JB3HcS156oNtVnALkndxuQvnukWLac7JjUmb3z6/BOkK3wqKkoSNRTlI2ZppfQ6eis+gLEEezsAsxnSY5KZAI811Ye280VmVb1rVwM5fZTpVdUec2P9RgQqsCNX9JgAF5MD1ytLjsPpFDrMcstFa+q92tmbewmyLwC+z/VcbCzVrl7Yrzbei/WZx8A02yQvbkO6jNQ7fpEGCxSRfMFN7YanEcGrLbioqIwg7NKevyKPq7PrffrbDEjSN+jy3IrVzCs/iHsQPWRUqOi8H52JX02Ns625mm6NTk8e68Wb9or4YK8aW57Jp91X0fPIMODIMEiyoGj23F8K5s9VhkoVuuOWxusPtJLdFsASI2CzGDCFiJo3SzNXOHSaKMkwpxw7Rj/1m72wPwcdVjh9VvIhecK2fYIEY39fXV4zq4vprjDTOvGOrZ9FE6ebo9SPxDGT3cY/P8lmyheW2AGw2mVx/iRMgevtqkGCJdwFymK24FWKYZLONL6OElGY2ZIMEG1sWGj40cIDflQ1S5rCVh7r3dpZCfPwknOBs2h7Pc3KhAP5BAurjjviuKyyey4pigDHS0XWwMANul/GlaOTWhzjoC5vmgCoBtMcSBIMZJGwN118CfKDaPiLhu7bRWE4rmxpgLHN4E6TxHbsR6L87oL2XoL3fax19kWdFgXYjSRCOYtFc9sJaiP3lqtlWUk+XbDdX7AldsFFVn8EjSLhFjfpuY7rM8rK9bcKYK+kUi1O1KkmEgIMECxVx03ioMETrP8/lYn6BesKF9O8+2myeb0Geg5zDGiTs1+XI0xz8XpG7yszha/fZxtWfBO3/FBXs5pBO2BoRhT6U0Dt6fw3iEmtJVnJbT37+RMEN+zMX3x0gdXrjEp5fVXpb+WTn9FVyudZzmDGEywCLy40ztPWFG36A6e82JqM4qVbgLF1CVDpiNuJ8qtVBR+O9jRJK8XEHn2EOj/ErWLKhKZpPU3q0fAFl1FyjYpSzYYodovQRXS7RDldAM+rz/A69lyBF4umo+AFXd6BkFcFhmsWxICnxryxluk5/t0X7kUebRrUSgQ6SbbHRruNJMFJcoi3ucYaFPzuh2bQ5/BhJh66iO8F0Jl+nX66OiiKLIY7XlPJvosKC+G5dH202CcScTiksqldQleXY5anNLVNC0Bq54ld/aQXXy6zKhe82Gq12HbRo2cPE7Nri2cyrKMfT27GZLbEsm/nrW+F4+7BEZ3+zYoi+lIwdJGY0IzK36FvNC10j/TihgZmaEYiDG/oTLav32er6EhRlDrGStMC91EkIk9LcXnZQps5oIiz0dXmyCVNBAGYxaLKn+KipNzXTtKa3a+oemio4Hp+bj4JX52ntg3ra8qiew98BBhh4QeP8BrrBmW2gj2GSoM+d+dZgtNkisiG3GeshpidNGbCQw85A+zGmdcOQIr+CeDxfnRXnVZL89vo2Sgpg2Xtv5rkA+RoWOLBq/eTBNf28hJKHdCW5AFxMfjq7AXupq/McPQ48AJ9pGuy5plBYs8kbY1aRlZBJG0vWEMNv40qzLyxwCZ6ifHWRwbQsGofY628FWP2A5X1jDFZ5lGgL8z4kXBEDvtBW5DkCQ6wAfKJv8DYuUjoyhBM4rapuo+FwZUKoOAyoJx+xaCElDou9jQyk77+/mtNs3Jv/r6I7tW4jys5HFCWJfR4TLYYH9lVdnEwSxo0LYi9BSMFXqNbWig3iEUxBzmbpjLnNl+7vov2ABzS6A1+yFUiKvhx+M2kdkR4VmygmO/gVOIV5gV3Do5uoAHWW169Q2x8hknrtkVtjcPk9WSR1rM42w5cohbegKK+yB5D+9vr9T+/ev351lMCowH4Dye3rV8/rJC1+icm8jdI0K0nXf3t9X5abX96+LUiNxZs1jPOsyG7LN3G2fhutsrcI669v3717C1brt2zxBtYI5af/aFGKYjUIW0AdnDbj/I8qIp++pZDhjV//E3Cc1o7wJbh9JZMjv75lC/4q4C3chN9eQ0xZMhvJU5fEuzTCwTtSnAuQlr1+hcUNvhTaiZy3Svj6tLSuIH2M8vg+QorIl+i5vgPx2+uff7LGHByqGUOXOe+CwSEP4+WPgU3pcQ3JLXuPo9eSaFgtyA3UgdAHLEoGbATLcZWuEnCGZl537fAUJiW7mO8sU/b9YypiJ/ovZ+kKPP/2+n+Tgr+8Ovuva7rsX16R0fzl1U+v/m/rRgxudLqzw/Deqh8Wcz1yfPbaE366aD2LbZmpK+jFSTU9nRvBFPdqSruC+XBhe8zUYqxADNdRgld99Ksgy/e7v6NRxEL6t9d/cxDE9ekcQPoh0nLugDmrOyxwf/vJeqkgrsyKVe6/raPn/24LWktwKriHR6c/IeXr6r5a36QkDpc70Hc0njeJ1wBQL7y4g4RanIcOyw3/oi8lxFxiCTb0VbYCsxXJ+yWI2+gc7uN4EeWoCeRyXHfHyg+x4XQkfiHaujSxf6YTO0ag48gdlDs05tFqDdMuOH4AwJZxmqCpfkON3b6+pp+yNbjwXFyGfolhFxnOPTEAGSkHxbCNPUqS7KkzzYNHCJ68FrCjzSbPHsEKv/KX3i2rtc94n2dlULwWjNzDZzrr2b5QkE1EuqvsKPZa/zn/U3eo5UMVfIpEaXUbxWWVgxytCOU50nh6/SBULR8Ry4TGPCs+wttyEeVeWlGL4b+eNrHDvpb3IL8YeNu5tayNRdYtzWrx+O4nB5FT4Y1TCWO8VB+tVkyVXs0/Kz5kT2mSRX5Ka4PhNzTf0qSehC2cV88Q0etZ8vWWw3PaDDYgJ88bmJMd7IfoRYZotPtrAEkMEQLoz92fomIZIc0BhBjVIZL9dp4tL9vPmxALdQw/NXB0lwNA61ou/RoA1c+OBdA4cADEJsSPnzxpQAbxeXzkHY130UXnCYA4iM7jY+vD54PdWz4+suwUDWp72OillK3qQ80oadEW9/iczMUQZNh0KgilDwVOCi8SMqEr3QcVKSw4TEz6iKYYAqt9bz03M2UWPwQy62ETC8E7eowgKgsTCtPDbCNso9PqBdNg/f2cPdV9bR4U8xsGpMPD2xey7z3N8rZ9xwBti3xgj6P4gdwmwqe1ngcReIuG8c5qGg5fTfAYFLJ8oZGB62odYmBqvOg5FB7pOFg1UBCEV0URm+PMx9XLcVWW/V7dccrg3D9gcZ/AovQHbOZyAhBfIsE8MKG64C2Qvk2gYOxnF6YBgi8dX5PVuBU024wFeXFqpDqWG4QTJc4dMTrLoOro4gBewTXQGLVtsU/SVUDk1tR0kpYgL7x5sZFeA1QwMgM1Em7SOtF+4QqCeu56yXy0eIKipKJur29gSnY8ozIran97Olg0l9x8elFHMR6N2sNtSnD4H3A1IvqncWnTrUqhZU4HHFbghDqADHMa2gZGpSDklgd7L4FtOSUV9hw+gvwFj6q96WVY2sfwQnsC2rdjWNqnHcdRAZrl1dtQ0GF9AVFR5YB4OYb1seuqOFqP6UHSVYN/DKtysEjW/jd1AAWBWcz3KKBx7wE5drgKYaYZAGIyhMRb3me15SfKx3Bw7HyL9sPfQesF8N7BC6Clkb9d/FsBwjhZyV22gisObVXuEsTQrEaHRPfTHRpNJABSc8ZVLLJqk6XYP99rJ8+hyCwiri585PiYrIJ+jNrj4KXbwq5uLHjEkWH2V/C4uB9hTyFbYCNuCezlcpyVSAML7tOEr62Enigt24VxbDq4Iw3cA8Oc2VxA4mDi4DzeFvRR8LGH2hL+AXx6MHDOKq6y+gYBi+xkw20wvg5PEUxUHTOvP6TDX0bpncZm7+bCHcgHMLBNags9t3bCZrFvHuIiCpJrZd8hePoi0NkMJLC513ktuvZEGWp6cwzTKO9OEhHVbsgHt5ULDSDG0Ygl+9kPslOYgFSnZf3VxeXwHDyZz2DbGwp7xjLz3hgbT+cYUZZaG3ma3Sz/7tvOsk3bMwfTdFfSd+RY0l5WCaAjYwfQ25cbMLoBpokB1kQR9L3w0birhQDrIyVeZXQwIimqmWGnBb2Qekw7DRUFXL+wWPuchQZvKSzZCHiiN0qsF4VPnss8wnEqxtSb6AOmPRFpWoOPQ4CIRZZk+Sfw3Dy7ERZ8+2JE2KpSe2aLnFmXkliBDOrvS3o14Kw4RVvKqr+aEsCjdmQOpC9WHbgwBBfSFLVvw7D0n4Yb95ANRzmYCW6Xp+h+OJkIezJxOE3Qtm73ThMOxvbdNrabuwm0oZqb94j2Y1Ginyu1VJG7kn5KyWYQ2zbcnD8r2sgKXo4yu8qme8afHyvYVVSl8PcKQAJ5C7EYsnd+y7XnLC63wU7WVNCtYKj9s34B1JsW7DTL15Fn4KQWaxn1IS+3K3hQoGvER7e3MIHeqjC5b+qlJ429up8V9XnBUTyI8+fkzSA9eNA0eWCms9fHoqKUrilOPBR0CcDN+5zdwTSQWyjGa68xG0DanL/QwcntVAMOwGfjIoqVbtccHsG8PcbrrvCJqJ1ddkns72CHhphJg4EJ11i3iZ2tN1H6EghLdL5ooka3Bb3mCILHYWIfYRo7GBiY4j4tWVA39j1VATKf3oUEex8E7F9wc5EVONKLmyuWQI+6z1IgjmDmFtexjfQ09/pnvmsRvbe4s9JTa+l952DpJca3ollavZRZAhTiutnVU/YZYGKdFZn2Lsl7W767us8BMMf/qy0+miIghzGD7RVy8XvkpTJvVbxkZ8Om8aQfvkP6Z5n723DKE+zBi7sUUa8OBTYKh9CKyZ4wyHia4oxr081NDh5hpH33xeGe1IxC0ZhPj5PsDuuEe8Kin6P0rsLR6qx5tC/pxaTC81dO0pkZM7KVfkNicfpZGyeDRLhusBaKIAVmU7oPIh0Ksn8uztdfeCsvUx98EsKfXk+xTWzFy57I2VHW4pZGiyoht3LUx6r2Z2fk8G0JRtjwnSbR3dkaNR3fg9LSxt6F97Lcr2uV4+o0tRf1ssSBjS9BIXiyc2fn3ez6TUtPLZO7BIlpwYUu9m6qDw4kna0vkuoOygKSGp7GZlUsZ3uP45dz8FTs0bN1s3NoQA2cPF0UBgqHIQmDFEQCb6dy+2fap2yH/nrYYLSwxpdNsiQ5SounvfFTwx1y8OwmpYLb89xEYvs4zPes9Hu/YuyLSohoe8I1sy/z4bindvdqZF6YU/EgS3SwYD9kWf1YgaJEWiyeI9twSLedGsj4R4f07fs9kQX6veDPP9srzIHe/wniRYtAGr7HGBK2N9Jwvf1oWz9Xwj8jObnKh85HcQL5GhY4yMYliLGk3V/m91mHRplM3eV8P2DjsT55xlSNkqOqvMd0jYkysFfjPvv1o3DepO1onVH3UgLCNpIzMPpXzFtX2QMIs40kcEdxDIoiHCjx/0AD7BXJyHjSnWZ5td6js/yrbANj++nVFPMLvzH35DZ6ytJMX7gwuaXoEgV41+4XkvlBuGNPJgjpkD2LNsV2fILgccQ1ewZZq/1f1bZDh3hO57Uk9jJGffd9Tx1fXsGt8Axj36BIhtwRjQQb89ys76YE2ifh8zHPqo2jBGrKjusS6vRUSeATivNm1fGayyEECp57IqXqIFZGsXDupngi03JPZNROiId9Y7c5jOo18faDaUlfAl6CJHhBg5Co49ruxv5heNHfN1CAZ2OYa/7ecQL8LkKTkvj+UsW2xLFbIbCaQNABoeq7e4GDjHehtqo8B2n8EuyidQt4GfXHt5rw5X93npXD4y4/an+PytDCDG3aSzQXkrM0TlBTDcnxNzdJ3FZ28jxNZVe4si6w/kQ9HFQ6TU8b2TBND5vKJu0ZqshistpXNhBjSPBDvDZEySkAY9NUXvPYBJbXPDa1G/wwV8sIn4zOiN2ztKPWQkTHaFUgTb1C2s5q5Hc7LsFTlK8uMrTAFT9ADhBreQZfvAfxQ1aVR2WZw5uqBKF3jVwFxX+tA8UWapQAiYefre9ZmPhznda/CdJH4hGHtzH4fZUcIDmyQOM/1FOchh2h0B5CfgOBmxRMMeX65xlsu3gAKxnpvFu6eHwME/EIg508b2BOdk5fsrS8HwH3nyAK03eaLz9AJNzKD+Cmf9DezV+zg2lCI37KklWgseLBAzICBX4cpQ/BtlIMbrApRuOeLUJDtnGmAsOe3USBFqRGQhOlIGQgVOyYgybEH/VL2nmUFlE8fJx7FPhg7Car4BIUVaDgs4togy9MhycODxyw1Wjf2elE4Zt+UeGyBQhtTr2IYChncyaKVBCaNpB4r4KDRFa1yXAUW9fYEX2390CD3i5cgnUEU8pJ3cEr/FOEnzltNrfnGTbLx4OIRAGPStpd4bcC6fafIBquvQleOPtDlqSofe1NMT+HrAlimHyEt0TZ30PWabtmP3h9Sa/x+1aA1Q9Y3juyEFPcuymDCAzBzS1TMuueMGirzlCjjNZyh3u7EhyfA8OW1P6HRSOb+c6KtqnkHlbEai+OQgNtzTYBNwyXqLMbiDWBUJpahxjuzsgSpFivDtXCGi5c876AoqBuzXq+xNGPCNHNZnuXx84fZo+CynT9CerHMqO6djHvw57/qKJmINxl9bcUluR1urGPG7uKRj9dnKI3k/Rk7NPQdr9Ur9djk2xY29i0G+3ILvxR3YfsKU2yaOUdjOisaKGCKEafEYunBWgxpTqh0eqLpP4PAO/undRCkxqOq3SVgA9RGYUxEDbvoeOLmGF41G7ZP8cBPvZp2Z9lgT6nwqT4XcBsPemy7n3DLVceaevmfll5Zvc6ronq5bxKEI6jJMLvCviob8XoXjUhN2FhDwNo+4fDs2xs+REeZcMHOev9eax3Rkl+lUfxA0zvAp4BEp+3cZUScpAHQp00fgAJfAS5yTuL2/KuVjsB9siK0XXJ6X5HXTLA+RRvNx7demChe5RVnl6C33G0un0Zdv/rUoEGbnb9J4Td6RJERZaeZnnNK2GU9IbjANn/Guz3XUBNXRCs7hkxwZJ9Xzy5vT3PQnm5q57EZoPz2r+IQAuKELfCtsXbxGKBzIirzyLK92qR9JeWF1HeKA5elqfaLuR2zEqX9XtCuh9k/+PV2eX/zMce412WQLMT5Ij9sXF/tP10iNVz58RcM/LLDaJh6xbXjeOeyLyZp4WYtl83Q29c472KEs1v/uLrEacwQeT2jApcB9duyOcbX3vkqPEqgu7JBBB3MRTvbU2k+VkYZU9YZGdGoREqV9HdHlPe8l1oW+J9j3KIdJ2Oh9Eu8gam0T5JvIfKhqZGbpglVD9h7PJiT5RWt1GMLwHlaFslud7nW4uz2mz0pANp11joP+BqPPBP4/o9RAUglHc/BrSqBv8YVuXygEJRwLsUzUBImPJM80jVOwfTUntYgmMJOlwlGZT22fu32z3s/2bfjmFpn3acFf6vVuzdxh+tB/FDiA052dN8rcqvtwSSdHKMEHmSlXVPllNf/mr/7+hy/SUihreeAa9pvbHjxnfuVjCPfQ6P4B1tHn2iHsnxdpj/vYK5nxMhpfylZZ4l/uZPqeI/AZ+81w6L7Uzetyl8lMAo+CMFAXeNXt7AvmK6vo+3HwMt6aKzFBTSSpYYUo7Px7FGRxNZkuXL36soB8Xl3U3odtY7h9W/0TDRB7zBDz1qN6sJKjorENISJCD29H4PsMwEYWn9ksO3nVTkv9J+hukDPg0TKWXb42zU3k/YE7nadifoHblv/TWOb3niNS14mCCXX45hGvVxkRDiDfngKDPTEpEKQwVp4ylMQBpKoOP31NIi1G2kMxxyZIR95hXistqetx+zau6z2XCx1vGLoe6eH3XpEPYsL9eegA4W5udqeAz2hJ0DHOYIFglepHtjLovkJMX5vXSxJYirHFi2z8g0Tz/GE8z0/Dm7yzrL9iimDkucT+U6Oc5WL6HiydXra2sjPwflU5Y/+I2N3TRuzDV7MpvrPvqxSo0xilywXrzMtYx7sAZkI3SzN8+okz6FllO242qCOQgOJXtXyRp1PGY5juKHs5Qcr9AxJ/eEbUI4TM+r487uljzFfTbak+IKrNHqvDeW+1HWDvz6KWoaF67bG3hk968Fouldlr8cxnh/x7gRe4ch3t8hJnSF8b4dsobYMKAu1I+Ehh5SBBye/0S6qtsJ8HgK6lGcXII425tgpVu+LZ3CIGp45Hi2Siz86owZ6lt+YKgp7RxJdRccNITbpcsbTub2tPgerKoEXEXFw57wmZHzhanhOUtXXqGejE4hDVnf35BeZpuv6UmeZ17xy/A77csyyssAMRAw1km6CoS0rOIYFEWodtGEcjweMFcf2jfW9mQWOj+DHOY1ZtG7JrY9sJTc5lYkEBVVDprYU/sx3Nol+Z3LkkzeukE9e8JaUGj0S8yBhsfQ9s8wj7y1bXjoAxJttTvJgY0ObORg6GxeJP+zsM/PLhtN+SvwQ2wX1jH1g3HmnM8ZBrAginnElNMsX6Mho6I4BIL/DNcQ8cNVRvjD4iKV4FSsuklgcT+SM/B0R1Q8WNDH0D5k+AE0pIijwfT1iDEWPoh/qvU+HuZSXQrqxzv7GSsZMT8LzlW2gfFsUd4sTodIBP02+vGeMKa/l8Fpnq3d2XBY2u+6Y+beDLqsn2NvdfNvEGsCIzroHPguZxirEb7GGXktfmdFs5c+fqlfwQ0E1r3H5IM3hSggdgD06XO2Lx6HVI9wLQ42GhZgx12HDF+63kaWJAbm/WDLcffp2r3hewdQa6O8hQvSehPBu33RiQMeyojXXDcw7BY/+7QPuAE2Zi/ySuNRTJ7k2hMWEz48OZzhP9vbaJq9v156DLFNr58Eb+9FlstCM5neFc1Ty86aNSwqCq1gdwGuZfCy8LrYiq/IgtuoSko0kQkHolKjnP2QDeWfzevQZW09juNaStUHgnxAe1UNRpcVDHZvLhfAjBaVqYJ20YLeNx5AMKssfikY+9JHSfIywiQ7B0/FZ4CZeg+ti+LOBbUzSlZSgw0ZblzCNe6aAF435qdhuBZP4e8/QYIqbiILW2iyBYxC9Y8KVGBVD/d+zI6LHGa55/10bKXUnGjZr2YY1OB40nqxucqCN/UqG6Ohl2CTvNi11gJ2jBYvFqERkUoTGnLrlZiw8hUHdskh8PLJxCBBHnBS6FYGQn9Y2u8cYlx9anEP4oes2rtohVqh4eLyvZVxOMlLlDj/RX3rLQTmWXEVPZ88A6qnLjAIpL1u6bc/mTDW6Oi3ANyn5j4FmeQ653BCxEMEf0fEW2QcwjCGnYEWL5XGVY6fIbqIXjBB9uuldLZ39rOHRwjwdO3W+/6wvd4Tdli8xAkYPqThNAIY5gKgffbK1xOsjBKC5qfN45tIBm9iT3ZfL8DVk7MU4gMIxwfXh6W3PqgK8VCGf5BQs6TuPZlvwvuuBsPH0eN6gXYTaJe48gp0LblGGqZBf3NpEPFMlwciCNOyvzJGZhf9rGvpWI18H6yRzqESLFbHhMyPeiO5J3P1oo2B9c5nKepQ3k+/6zTfMeZZUSDdPDmMYJgRtFjo9sWBFPXkM3gEiZ8CuLzP8rL1sA/ihHWKspsCGhnfNo3zQWjXgzFdXo0efkAUChXDHXEqyHOQh8Kb5ZaUUSD4HPxekduSPtunOnrup6jQhdMKdMtqCeorevsheUbxNNqJ+FKUnfzPMpZbGFKOXF/bE/rX/vaiUXBbLNM4qVbgLF1CBBt1Ozq3s6XWXRQllN5mZJnvqVM/r2DJX6h2Qgp2xv4FlFFzb4JTVZwB/R5LE2OGo11Ab8RLgLSs/Kj4AVd3wOsss0b4V5aG6WMN9yOPNo3WIGmbORZSuZ8GY+Dey+MMi1jPeRnAW4t0Bz/Ubk9x80emYEHcNHwW4Bbjml8JhPnbugT59SvHoDK7DQZV1M90TLffco/TF7VogvVoehhAbAezqWpXx5Juvl0L+pJjjCRxa0GbG7S03GerBe6q2+QcAhlP0bpCp1EV1GhHWQ7A8wps3xXLydqVHGOEW3NHa4dxGNsWwnRUm6qcRnVQl5thx3ck6ebbtaAvOcZIXoB8DQvsXFJHtaVD8LoMK4tnPGmpep3GWFixpVTkIYLcoO/65MZ6TfEx57HvqNtOZu/R3oIZvf2j26zyzX9YGXeZ0FIdSZUdVeY2i6dVaaQNaNvv1Ia68IgjegkeIXj6BJLNbZWkjgtwANOXiddLkOGoO+w8IG1xr6b8iIqG4iM4DB8n2V0b72Rfx7Lpn1EUJSM7HaLZRVY4OFD2JceYpPja4J9lLIMZDkMzBx6EsxKwYfEMb33WJUeR4FmSfM/w0d+sr0hMILlRR4/S4snlNJ0uO/oyur8jMO/Tg+FkQz1OwUQDDg3dB8d1erq31gP+CQriLR0A6jyzRJLx+lFRZDEkI8vsAuq3j6+b/xmuP0lXr/BOBGVv0puWLEFy+6b79qVKSrhJYIxq/u31u9fsTPma1m7Wr7C/Nr6vsoiKOFrxVECtX0lbMGjwsCFM0rA9/4OrhjjB1GFjFllalHmEKMtPb5jGcBMlDAWYfIaCAHeuQ2RTPoANPtVKS3FXTWqk3rDmK+7wGWrr6PDrW4pv7NhJaK3nBlM0in8WdhJRaHvYqV8kZmGn7lApoFz66c2bd9zY9UjdcSEN1X/cdZkiPg2VDf/M0qQ7CjvaIJJjx4Gm+RAU18usymM5R7RlB+PYf5xEuHBH0KK2jMxbXZcnYC71kbukTvWx7OxsdhXl2MEkiLjYYzazGvI52EzhmjEbmzXL607JMpHKxqXtjSSzUc+2UZB1HKYRY/Mr4vPz14SquAt7KY/aJuWu9kcTkAZbZtkzpu2UXVSDhc0ZpO+NDKN7ZSPH5jYiXKORh48gf7nC936kY0tnGgzqIMFqD2ghDUPxhbSr4/CG1bpGtW1unui0OIlX73Yp5kyjRfy0R2o52yMbrXxr+GpnLJYzMtX0Nks7q9XMRksc1PE2IpazfELDJV3tAG6YsOsGzEFvdsGI2bDjgCvknfAd0HGFjrRJwvRRWM16/MNIIOtq6QJbxXs7s8JtAbNNv9LZi7eZVzvmSsslQGgQ3/2t75gYnNQMEQYjzSZNwn7iC0ryZnU5RmFBhgQTcKLBBS1JxSY3sraEJ3XHOoJRl4zzn4ILbbhgNvaTX+37/9u7sh63cST8VxbzB4J5zy7QOTdArkl3Zh4Ftc10C3FbDVvOsb9+JUqUeFQVS7JISk5egrRZZB38SBYpsioW7qp6n1LL9a1WVWSg6MvCmC6viS2zZPWYMtQZgaVkUFLv+mYEkWf/aEXNki31v60dAOBT1IV3PQHa88fw4sAwfohGhIQhXLrbcOptYp9dz+daqxrm9ND/GGWRsV+DQ6IEnmeUwhFQRb59R1jS72MTIcvjII+YIC4WV2P6OAGs8JgIsVH1rNjtao8ejgcxCVO+S7xj5r31ooHFyzR+ejR0YbEyWImFLl5WLC9DlL7sYhYvpdGYxSsZrtoY6tfVQb6aPEqHKFOB81Fw9QR6jw4/RgEXJLkpD0gQBGawwcLADFSLw1dPh5AEa+pp+DrwpaQ1ZBh+XD2OelVWgR31cnwd2FHSGjIMP64eO70qq8BO8+R9HbhpJDU/1sofVo8XqcZqsNKGR8hcocf2VECUtDI6Eqifw3zP5/biDGjpFOFwa8jT4cWOa6fHHvOeMNq1zf50CuOgi4paSMlnEoZBoG2SGGhkR3HE8MkK27gAsPq+6mNAoDr+FwTpaICkBqgnYmTs885RM+iijrl8AyQi+KIeeE0F3OLAtqIZcImgizXjnQ28tDPdyx9NGpt8d3Wq7psWN7LQWpIXPeNRGhhy0YSrnwFJ9cYAMhkWX5WH04M8lJ0bePj3xZ6n0ZL26+pxMeiyHhDIPF+xUdAmF3Ng0P18GTgAEqgtGgiZ/LdNUU12npHFeui87ucoC5Fk6IoQCDqYYQKCh8VokGsJUwgg9/geC4mXBJMOvy/jzjiSfAHOByb2OV0XEkLRvZeR/RrVf5FiJQORDKMc4JIc5bsYuVZlI90va78b16rBYWQaPHHvr2LTHBc0Md1cPmqSe7gqPNPnY34n/lvU0hx+ZnBcqYWG4dIlB+UxCS4mEJehFodv8khcINaAsTJ+jvgFIMafU1LhS0qYduGTN+KWjKheSleCgFfhomFnUGRNgFl+DJM0sIkYr2QccFJHKXldfKme54dt9vHUpIY5iu0/RXWP6DC9Gz3PkpQURmPDj+FmEnY/nQmKXhcWJsCuSA4Rw9eBFZrapYHmGkhyUJ4ITs8oBMyEttF+j6q4LKx91ofCQr2hJUEtmo80GWdGj6Z1m96XlVi+n91I6UrQ/rpuDA2KLN/P/iS+12j/WNYNHNXktIrzSUBwQxywfPVnl5BWqzjJhHA26yLocceXA5do09BUrBjdkjawX5PHYNErmRLSCdYXMK9ENAD1eix/GVOiyhMjWO6pvRYYOc6Bg1kQLM4ou2NnAhH7WKKvkPYjrQpNGS+Qze/P9H9YRk/orVSnw75JpiMC3DUO5A9rIluujVFyAT6wrs8qvN/+AU/8WeV3eCx0eknp25ZSkOf5oXVZnp32250gP090n1U0SuCLl146Cia2RJZLYheG/KKF2SIMgBzdODwHEZcDoVWsUkuAWcy1ahK6ki9XDraW/w1+CcCK+EV+Eq5Sf5jv+F8/ik3xpXt9eFVVh+L2VIkVYIyUH5ILo1w7+mj11gBFWPQPj82fGUcvferx44EHhFBTI6ErQ0pFGOYkajqEzp1BKWVZmwmwgYVOrnxtQwPnl5ybzwZb8gmb0iA15pXrcJPf+WOYLcWLGGSGZNFLL8Rb0FQa4SEkDD8GgMsTcYfuU6AzfwWIcTs8Jcpq8uRA+zs/FPm+6ufW5+XDbbGXhBmeMl5/D6MTGe9gjIJRx4J+2SBokeRhnnihBgqKNkpRjhS62EvG32r25osHavx191yMpt6qM+D516kuqXvp877AMWoQ6VgwCy5yesQNtGjo6WIvDX/rnRM5kLzY2W/NU56CnP9I0iEEujz2MeRi0Rj12HEGWC7mrNFW4u98d+pBSmsYBhdxkSvV5YjZEYbE8CQ8hYFyq+0IPNsNpIZ1cneS4zKs2zFcnfeXXecPjzvxovy+35U5/kS4JzDOX/ofF44GWL30SDCNnwwLN4U41PpthBGH3XvfShJx469LTARZxXrpDVm0Xy8i2PqgD4eZLt0CYLX8LWUaEEXcNo7Dz0I2isN11O4i8fKB5Eg+7jbz+oDlqsRhanTngmA2K8C8LtBvlNBMF4ePfi/3qthVTbzb4M8bYk1Tlma0kA7x5Tyb8OrKn92SPqN4lm++vtlfV+Xm6/Xp9rg5FO0d0FW8pkCEN3NAYzSrf1uBaTbG8V8c7pbvuS0PdBHX53Mwl3qVvtrsgESa8x5fBMJcL7r5IHb49SLOLgZ9VnN2cfXlS7GrfxFZpFwGPUOzoeHXtT9oHlThMEsdKEGm3gjqOOFQcHi7KU4uzONxdVqFr1Pr960G9LsaqPmdyF4dyod4KDGZW36TWbR6fFgKcTjqnbEUgNyUv+GxEHgMXZHQba2Kb7XJ35Z3mfb/pqNwZ8OiM71GqyySB9tzxaQJ5btQNgsDNF0plhdjirgIqK3iACgdqmLOW2PhlHzK+uskTmL78iEvdpn892pDZ2wyiIyM1EZBFExpwlv3mrTfg2AKN1UYXOkacdjp8qULJXYvNl/Lk33r0vkZn7ccSmMCc0ujoA5WixYt5L1Kjz3DABLRkDXn2VUTxk7cnA61xncf858yfu+bfdG0O1MYX9yVtxlbQRDtwnXH5nX0YfHUemIx+FDRwGmN5urnYNE3QaVI0cKGGZ8GkHlBOSbquF03GT7flpt8V/xPbJWM2dt8f3dydsxa7/cEencPP0ZBoCO2KYxbGgR1sKnCoM3VicNUSZgQX3NvLsPdGbb3lZexn1zNPrL7YvxJfCvE9/+K3eOX027fBK5tumRoXJ6o95iqf68at+umzLD6Xth1bYDo68tGX+Nz5QBuMQBEjFcTZ2FOaRTlCJbTI6N4p8ShUfAnCiuDDO/0ZJCa9x5Kp8zS0WRUTncPalfePS8f5EaROatpVWJPZDpr476T/vsFTVeoqUexiwyj5v8fy2OFz0eKwunD9sc41+jGQ2mmaapXdIEAUrIlA9J78f04cj7SqsSej3TWepPG7xc0H6GmHsUuMoya/zd33PH5SFE4fRjxpcMEKM00H/WKLhBASrZ0rne52/1dVsX+rru7y936WfWib/ls/oZf7hRe0BxFW34UzxT4an642h+/iwOxn+tpnG5VP8d5nXUOyOba9A1KLxVcg4jLOEGYdHyV9MyKf2ax7qlrpgOM6IDq/vKeQKV/8D4WT+t7IjUeRHEfRr2UQYWaUVXXEAd11l9uxavicKxe5FV+mx/dj31NrWtRAcGJ/vhXW4oGgbre3IuH/N9/bG/LuufzWzd+lDMNmdz8L4sdCfxVIKn8tcZKyhGNKQuXOc6SZORt/kVx7C7iOe0PRRCDodTD4XleiTt5J8DhMBRBHIZSn4mKTXU6CMhEqgQ0kSrk9QDBxSIg+iPriLJ3ucx1xe6fJjVocRDtlhXtKoOK6jWD0CeCEWrd5W0Ug0wNCp61CdQ4FJS9FRXb4F3Fd/n+9CWXfUXMRCYVJYdOyZbFI4Sfu0nhG6i2Y+mOV5sCHLY2EZMtwY9m5OXQP8l0GPQlUPt9oU+BZkDB85sqAcVXhZ7mVYLXd6K6L7cAF5sAYmbT+HhW9bCpofut9o+gMW+VgxxNEg/D4UOFw2sogtgMpR4Ow9Ulh8NQBHEYSn0cmrs74ro6yJ3ssc3ZA3ADyUDOIKVHiuGI1eE8FEHcmlL/7Kgdh7iTolYIzoVaOYMPwgFvmz3VtK/80emmLaamnJbCp4M4PBTHY+2qq8MaVx+HBNTNofJwfvmjdmv3+e7qVN0324k2wxUqBU0OSUTX8EgnX/wig10rg/jK4mPGGvCS9qZ8LDYYn66QYNRRcDhhTMj2OwpO+68P5ekRY9IVEpw6Cg+n7n66w6T7HWq/K2K6sZ+bV5r9JWHUjzXJKEfWpPRI8br4UruNh61HCpgMkgKmZEpBcKa58boRWQS0MrQ7WRtTSfm+rKCZVCtDmbTFHiafxPda4Y9lsa+OeIeBVBBjkJDhfyHbrqEI87lYGyxFiPSYWUxxYvXbJ1FvB/bNBlCAU7BVDpvRIPGqZ2eqBlS0SWA1bSreXg5Li4nt6jB6an+H5Cjl7vTobKOuvUhy0HZkjUnSseUaIxG3T2WKP6wDZSHRW7Kcx4fMa4TxJysRcpH1psnLF9JzbmXl6Rh7nsIQiCfJaKQgmSu4ZunIR/RaV8PnEfUB9F0vqC8CPZ++1MNBi2DtsNDKIB5asfekoATPRLvf4ZOBknHqKql6kMHt98UoGy5Kb+r6QnbkLbg1tMpBm5kkvgMPLJqge/6BUYLHIRjxiJPGG/HwuMvBkQqT+U4eB0rmpwJCApeE+nTA5qwWBpyxQ0GtL1y2r8Ve1MOMmhxdEnBr4FD5zkSHOIPuqehQBp6LDsUeJp8POBOtDGKiFfsmirr29rQTN/nxKzRRGMXgRGFQ+Ow2ROVz7TaUgXYbin2jUeTHeuD8I4q7e8hFt8rB8WeS8Bi+KOpdxBGehlwSgq1G5T2SOxzEfgOenvdF8FGcKuWcnnjmWICGOEsZNbPaEbeAicUkoM69LFLvGNci5gCjXCuFx7lGwGfVBqCi2LUUHpYtkXfBeHjMizuoS4cieIFQpb6jVCNYjXt0ahSDR6UGhXccyp6lVmCbAh6FFhHjm8FbUVXi4BknGCH2PQGi9chihORxBDBK4SsoGoEPPW5cGBdGLg2IJ5dsLHNsX4IRssTg7UXQKA7A4RBCCR8TIcQjxWHIwROA9wnPDCngsAZo0I93JplX7WawbvELPzYBrLJJ40PhoTwe68Z3OFeXBESeQ+U1NbQooYsRZxGq/1chu0VVAvp/qtC3R8x/ELdSjFJwf6gT+Fghn62IL1a8T1XqswmhiEtCfX/Jrh4fd4XY3pQdfSH8X5FVZRx0DgVPho7cL4F5HwK/wYHQ+a9YfBLHegqWd2sZN0i6z8r4RRWHgvo63RExOsL+suz5QE6Tcz5dZw0p++ywv0rE+27P/35vZAsYe5TJOYFmHkRnA91YKYAAAJgwACkhE0DNuLzTP6dzz6/0UuwKT0/A8FRxVkYp5pNyWblvbVz7OiTY1RSTakwP+7qV0ZcOQ+0WOXbNNetvs2rE4H3XntK+5q69EzCvztY80du30PsCsLpVZL9aMFWcoL5aePzqw88QoIcarvyLUb+/J0v0u0Mzf4/bfomsiLpN49Wk3Jbsurth52rOqYYrZd+xl0ph9+fBmqBZ3MKw5rnJD3cCHBGcajP1+cLMoxzOUdixK4VEDjT3OGUhDTMGNXal+afURGYBnl0cSZOAFULiBHhfYjRCPRiZvNhm5ssQdKk16QhlgJcqrRbEC5SpuJqudr+ODlMTqrpLG2IiRR7d6OrPOI3aqvl9LYc0mLMVyQTGcyDC5QLp5ne7oIdGsjL5gGhyz1uPoXQWKATwOjOrBaECbQgsD2Im/yAByYMNlMgmMU/XMnW8JrozOdp951fG1YafYUmN6cdVRCvWuSPQGPZILIgBcVeNX5lYnQBlEfWWYjL9CVvWtwuYByQMYAroWV5rAvK13XjVuyNkSmmbZH51reNwWQ97lDldRct0uKIm4ey9FFH1/hi8/1JATJ44MdHj1pvetsuR97pgTcgATlkIQ+CTIE48Sz8uwQzPit2unkj7lgkjWKThTMDC0BlKq+BMQ9O41g5tyCFgvcM3GsAe1483A/TIeMgOAdiCroCrZT/Alvpgj6vNmsTD6rYVzkPp8aZRb8tJc7hEIUxgv4+XNdG37+NVVU/DSVVdohCq2g/YZc3hx/M3V+VuR6ppEoRQUX/d3u6ZoNfr01RrH9hnbYuwcjrJmUI6NczgAH095Nn/BBXtCwX6l3zKjeFVJJRDXvO3Knpe6JMtQbcbwFbJ6AOzmxJ3hHgV/QucozCl4CJNCN4nYTnTI9E3gzfhM3oc0zDc6+hoSmUiKooG6Y7yKoZAEyeyiGxtVISQ8abr44SQdgKoQhjFCWgiq2q/zqOuvOHp19ciC6awcUd10Bi+hzpR5UyPboLoq9N4RDZiqAwig7FR3NpuxXl7tmuS7NaW5mxB0/SmHIl6s9SI1ehmFD3yuJWBThinKiDd/EcqRngfWQuO3jNVUWpusihCTEvh1IOiDWXD/Qr8IgdcgbjBcPZFDii2kdEKGatoJtN0dmfaBUwwPL5fF2GOPn4SbgObZF7FndhPQ72ZDlAG+Ylv0i7R/F+iw6uqIm5lH+v92X1+FNt/iupe4+Aq7qsymzpGXTummKyKxgubbghj3A7t42aAK8ykCFgTHfGsYG0zmeaz3u9s+5i15p0XUpmnj/XmmRA1kgAToh6nbqgHhqAbryIQVY50hUj6EI4REUVPNsIJizePWbzjwl9pXnSkMY0K2IePCYtiXqXtkIbX6hbNTNd79XiE2dAurqVJOJPgYE1nfSVjJ07dCKkbSd4tn024pj2fEQXSM+GBlGGmOiC6ZTeSqaCVZxxwMzobpZ2/v0Nfm7CDcGZ6LhNohOPk3h2Bm3FF3xvgSVSskQ+HH+1Gvyek6AwGokYGThxicCQ3BbFhRGnn3zfGNgMZVpZxsdtTcX4DseLm6q0xY7lOQBARthaJuQuCa0IzBH78duEZhNGqGQKYaBsJ5Ts3WmnmYyFMthbZVAsdAW6kCPKWC04dbJpw42HoLeilQYxB3FPBqb1KIdoAaizDJEToaO4rS3YTxKn7XA8wGWG3dYuOCZwdxMT+RZxTO9gQXZs5zUSSk2xqNIEbCMp5KU1B5bJcq4knwDQ+NjnWCm8i0p1k1/UqT2rN8xsXaUYZdzJD2bJNSrcTxSicdo3YnVTjcDzO6Y4Qc6KMPhuGDaCQXecPjzsxZELAFbcoCdfFysfQui1IroWIKvepGIx7sZDKCKX/IGn6zVknxYSsjmePOEd9Yu1yieZfrcKr6hxrdmel/kUbrRJs0Z56MjuLWUYZ5KJNYWUy95zFj6ke52zeJxHdtC+P+4QXf3D6FPII31snxEm+J32MbIibC2Y+MxED01dl/uGZ1kR9khTvso1Qhly2neQv7ddFNK/LBPVVrpOM+CrqEs3/OdTJ19LWRFOxTLzazp4tCOoQ8wSa/OS6v+o+M/DNHCXZq0P5QNmDIg9hEDgdSzepkNlVzjbFTTnCEBrx2s2gJXXJnAQv0HyIk1NTGpiAppvYyKwyWDtYGzObhAIFSBcCD3HU1pKzZGZeHFd1lBZXAsrVI7WgcvBY5x9O8pnu+APPKjPh5hCYpSUDcsgAN4m4dQmUYGlvWrj4MtnQbbknYcycNFPun5nZXbI3+6Iq8h1xt5SqgCs57V4pnMPmur2K5slLc74x1N1Zl5XfLmjdQOqSLcGXcnmJhSZGw9Gz9vhD4cDUuIJnxsGBshO1TXgTDk0xB70+BV+X7PVonnWo2z46mS6yxj14Xu6P1SEv6vmh8UZ7/VTsp5syw+oThy7z8/Kbe0IAKWjnjWYa0bfi/qwhM3aTUfDnKKtbVZnK4xov2oBaZhUu3IxkLMAxznkthscskIumPekhksycZ1gVhQvEIUxIi8+O85VAcS2BDrf/jZw7roXObDE8ooCUQ7INKpfQeYZVwc5ARMGEtPjscGoJFLezI7HXRSerErAKzNV2hLUWyTbVLhG+DFIzmH2I0gYvqBQ9rdaokHBpjaIvxZMcNK8vMq3VyL4e390528jdX34nbqAM9iV3RuWfPmkbaUxf97M49GVPn7T52rof6j/rrWu983tXbsXuKH99+uTTqa79INq/XohjcTc08bRucy9kZsmhUUXzZv+lVPs/SyJFoor7lNNVvs2r/OpQFU28/rp4U3ufMhOhPKlpjs9uxfbN/sOpejxVtcri4XZn7C6fPqH5P33iyPy0vYR/nEOFWsyiVkF82D87FbttL/erfHe0dtRYE89r678W9e9tX9aDsxJ3P/uW3pd7ZkOd+V6IR7Hf1oNO5eA+fthf59/EFNk+H8VbcZdvfn5sIofLgyesEX9HmGZ/+qLI7w75w7FrY6hf/1ljePvw4z//B8YYmOffAAgA + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.Designer.cs new file mode 100644 index 0000000000..e5e31c1659 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.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.0-30225")] + public sealed partial class PaymentShippingRestrictions : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(PaymentShippingRestrictions)); + + string IMigrationMetadata.Id + { + get { return "201506211043073_PaymentShippingRestrictions"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.cs b/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.cs new file mode 100644 index 0000000000..7f4f7300a3 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.cs @@ -0,0 +1,88 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class PaymentShippingRestrictions : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + CreateTable( + "dbo.PaymentMethod", + c => new + { + Id = c.Int(nullable: false, identity: true), + PaymentMethodSystemName = c.String(nullable: false, maxLength: 4000), + ExcludedCustomerRoleIds = c.String(maxLength: 500), + ExcludedCountryIds = c.String(maxLength: 2000), + ExcludedShippingMethodIds = c.String(maxLength: 500), + CountryExclusionContextId = c.Int(nullable: false), + MinimumOrderAmount = c.Decimal(precision: 18, scale: 4), + MaximumOrderAmount = c.Decimal(precision: 18, scale: 4), + AmountRestrictionContextId = c.Int(nullable: false), + }) + .PrimaryKey(t => t.Id) + .Index(t => t.PaymentMethodSystemName); + + AddColumn("dbo.ShippingMethod", "ExcludedCustomerRoleIds", c => c.String(maxLength: 500)); + AddColumn("dbo.ShippingMethod", "CountryExclusionContextId", c => c.Int(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.ShippingMethod", "CountryExclusionContextId"); + DropColumn("dbo.ShippingMethod", "ExcludedCustomerRoleIds"); + DropTable("dbo.PaymentMethod"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + context.MigrateSettings(x => + { + x.Add("localizationsettings.loadalllocalizedpropertiesonstartup", true); + x.Add("seosettings.loadallurlaliasesonstartup", true); + }); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Common.Restrictions", + "Restrictions", + "Einschrnkungen"); + + builder.AddOrUpdate("Admin.Common.DeleteAll", + "Delete all", + "Alle lschen"); + + builder.AddOrUpdate("Admin.Common.RecordsDeleted", + "{0} records were deleted.", + "Es wurden {0} Datenstze gelscht."); + + builder.AddOrUpdate("Common.RequestProcessingFailed", + "The request could not be processed.
Controller: {0}, Action: {1}, Reason: {2}.", + "Die Anfrage konnte nicht ausgefhrt werden.
Controller: {0}, Action: {1}, Grund: {2}."); + + builder.AddOrUpdate("Admin.System.Warnings.SitemapReachable.OK", + "The sitemap for the store is reachable.", + "Die Sitemap fr den Shop ist erreichbar."); + + builder.AddOrUpdate("Admin.System.Warnings.SitemapReachable.Wrong", + "The sitemap for the store is not reachable.", + "Die Sitemap fr den Shop ist nicht erreichbar."); + + + builder.Delete("Admin.Configuration.Shipping.Restrictions.Updated"); + builder.Delete("Admin.Configuration.Shipping.Restrictions.Description"); + builder.Delete("Admin.Configuration.Shipping.Restrictions.Country"); + builder.Delete("Admin.Configuration.Shipping.Restrictions"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.resx b/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.resx new file mode 100644 index 0000000000..43c6122456 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.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/201506261018157_Merge.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201506261018157_Merge.Designer.cs new file mode 100644 index 0000000000..8d36b523ab --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506261018157_Merge.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.0-30225")] + public sealed partial class Merge : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Merge)); + + string IMigrationMetadata.Id + { + get { return "201506261018157_Merge"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201506261018157_Merge.cs b/src/Libraries/SmartStore.Data/Migrations/201506261018157_Merge.cs new file mode 100644 index 0000000000..d5201b31f3 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506261018157_Merge.cs @@ -0,0 +1,16 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Merge : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201506261018157_Merge.resx b/src/Libraries/SmartStore.Data/Migrations/201506261018157_Merge.resx new file mode 100644 index 0000000000..01a895415e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506261018157_Merge.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/201506261756463_PrimaryStoreCurrencyMultiStore.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201506261756463_PrimaryStoreCurrencyMultiStore.Designer.cs new file mode 100644 index 0000000000..11aadff013 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506261756463_PrimaryStoreCurrencyMultiStore.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.0-30225")] + public sealed partial class PrimaryStoreCurrencyMultiStore : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(PrimaryStoreCurrencyMultiStore)); + + string IMigrationMetadata.Id + { + get { return "201506261756463_PrimaryStoreCurrencyMultiStore"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201506261756463_PrimaryStoreCurrencyMultiStore.cs b/src/Libraries/SmartStore.Data/Migrations/201506261756463_PrimaryStoreCurrencyMultiStore.cs new file mode 100644 index 0000000000..b54d2c2f84 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506261756463_PrimaryStoreCurrencyMultiStore.cs @@ -0,0 +1,160 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Linq; + using System.Web.Hosting; + using SmartStore.Core.Data; + using SmartStore.Core.Domain.Configuration; + using SmartStore.Core.Domain.Directory; + using SmartStore.Core.Domain.Stores; + using SmartStore.Data.Setup; + + public partial class PrimaryStoreCurrencyMultiStore : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Store", "PrimaryStoreCurrencyId", c => c.Int(nullable: false, defaultValue: 1)); + AddColumn("dbo.Store", "PrimaryExchangeRateCurrencyId", c => c.Int(nullable: false, defaultValue: 1)); + + // avoid conflicts with foreign key constraint + if (HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer) + { + // what sql-server compact does not support here: + // - Update Set with a select sub-query + // - Select in variable via declare + // - Alter table to check/nocheck a constraint + // so the the foreign key check constraint fails here (and therefore this migration) if there's no currency with id 1. + + Sql("Update dbo.Store Set PrimaryStoreCurrencyId = (Select Min(Id) From dbo.Currency)"); + Sql("Update dbo.Store Set PrimaryExchangeRateCurrencyId = (Select Min(Id) From dbo.Currency)"); + } + + CreateIndex("dbo.Store", "PrimaryStoreCurrencyId"); + CreateIndex("dbo.Store", "PrimaryExchangeRateCurrencyId"); + + AddForeignKey("dbo.Store", "PrimaryExchangeRateCurrencyId", "dbo.Currency", "Id"); + AddForeignKey("dbo.Store", "PrimaryStoreCurrencyId", "dbo.Currency", "Id"); + } + + public override void Down() + { + DropForeignKey("dbo.Store", "PrimaryStoreCurrencyId", "dbo.Currency"); + DropForeignKey("dbo.Store", "PrimaryExchangeRateCurrencyId", "dbo.Currency"); + + DropIndex("dbo.Store", new[] { "PrimaryExchangeRateCurrencyId" }); + DropIndex("dbo.Store", new[] { "PrimaryStoreCurrencyId" }); + + DropColumn("dbo.Store", "PrimaryExchangeRateCurrencyId"); + DropColumn("dbo.Store", "PrimaryStoreCurrencyId"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + var settings = context.Set(); + var primaryStoreCurrencySetting = settings.FirstOrDefault(x => x.Name == "CurrencySettings.PrimaryStoreCurrencyId"); + var primaryExchangeRateCurrencySetting = settings.FirstOrDefault(x => x.Name == "CurrencySettings.PrimaryExchangeRateCurrencyId"); + + int primaryStoreCurrencyId = primaryStoreCurrencySetting.Value.ToInt(); + int primaryExchangeRateCurrencyId = primaryExchangeRateCurrencySetting.Value.ToInt(); + + if (primaryStoreCurrencyId == 0) + primaryStoreCurrencyId = context.Set().First().Id; + + if (primaryExchangeRateCurrencyId == 0) + primaryExchangeRateCurrencyId = primaryStoreCurrencyId; + + var stores = context.Set().ToList(); + + stores.ForEach(x => + { + x.PrimaryStoreCurrencyId = primaryStoreCurrencyId; + x.PrimaryExchangeRateCurrencyId = primaryExchangeRateCurrencyId; + }); + + settings.Remove(primaryStoreCurrencySetting); + settings.Remove(primaryExchangeRateCurrencySetting); + + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Configuration.Currencies.DeleteOrPublishStoreConflict", + "The currency cannot be deleted or deactivated because it is attached to the store \"{0}\" as primary or exchange rate currency.", + "Die Whrung kann nicht gelscht oder deaktiviert werden, weil sie dem Shop \"{0}\" als Leit- oder Umrechnungswhrung zugeordnet ist."); + + //builder.AddOrUpdate("Admin.Configuration.Currencies.StoreLimitationConflict", + // "The store limitations must include store \"{0}\" because the currency is attached to it as primary or exchange rate currency.", + // "Die Shop-Eingrenzungen mssen den Shop \"{0}\" enthalten, da ihm die Whrung als Leit- oder Umrechnungswhrung zugeordnet ist."); + + builder.AddOrUpdate("Admin.Configuration.Stores.Fields.PrimaryStoreCurrencyId", + "Primary store currency", + "Leitwhrung", + "Specifies the the primary store currency.", + "Legt die Leitwhrung des Shops fest."); + + builder.AddOrUpdate("Admin.Configuration.Stores.Fields.PrimaryExchangeRateCurrencyId", + "Exchange rate currency", + "Umrechnungswhrung", + "Specifies the primary exchange rate currency for this store.", + "Legt die Umrechnungswhrung fr diesen Shop fest."); + + builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.IsPrimaryStoreCurrency", + "Primary currency", + "Leitwhrung"); + + builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.IsPrimaryExchangeRateCurrency", + "Exchange rate currency", + "Umrechnungswhrung"); + + builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.PrimaryStoreCurrencyStores", + "Is primary store currency for", + "Ist Leitwhrung fr", + "A list of stores where the currency is primary store currency.", + "Eine Liste mit Shops, in denen die Whrung Leitwhrung ist."); + + builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.PrimaryExchangeRateCurrencyStores", + "Is exchange rate currency for", + "Ist Umrechnungswhrung fr", + "A list of stores where the currency is primary exchange rate currency.", + "Eine Liste mit Shops, in denen die Whrung Umrechnungswhrung ist."); + + builder.AddOrUpdate("Admin.Configuration.Stores.Fields.SslEnabled", + "SSL", + "SSL", + "Specifies whether the store should be SSL secured.", + "Legt fest, ob der Shop SSL gesichert werden soll."); + + builder.AddOrUpdate("Admin.Configuration.Settings.News.MaxAgeInDays", + "Maximum age (in days)", + "Maximales Alter (in Tagen)", + "Specifies the maximum news age in days. Older news are not exported in the RSS feed.", + "Legt das maximale News-Alter in Tagen fest. ltere News werden im RSS-Feed nicht exportiert."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Blog.MaxAgeInDays", + "Maximum age (in days)", + "Maximales Alter (in Tagen)", + "Specifies the maximum news age in days. Older blog posts are not exported in the RSS feed.", + "Legt das maximale Blog-Alter in Tagen fest. ltere Blog-Eintrge werden im RSS-Feed nicht exportiert."); + + + builder.AddOrUpdate("Admin.Common.Deleted", + "Deleted", + "Gelscht"); + + + builder.Delete("Admin.Configuration.Currencies.CantDeletePrimary"); + builder.Delete("Admin.Configuration.Currencies.CantDeleteExchange"); + builder.Delete("Admin.Configuration.Currencies.Fields.MarkAsPrimaryStoreCurrency"); + builder.Delete("Admin.Configuration.Currencies.Fields.MarkAsPrimaryExchangeRateCurrency"); + builder.Delete("Forum.ForumFeedTitle"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201506261756463_PrimaryStoreCurrencyMultiStore.resx b/src/Libraries/SmartStore.Data/Migrations/201506261756463_PrimaryStoreCurrencyMultiStore.resx new file mode 100644 index 0000000000..7e45cc22c4 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201506261756463_PrimaryStoreCurrencyMultiStore.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/201507072138058_MessageTemplateAttachments.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201507072138058_MessageTemplateAttachments.Designer.cs new file mode 100644 index 0000000000..cb29f9e05f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507072138058_MessageTemplateAttachments.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.0-30225")] + public sealed partial class MessageTemplateAttachments : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(MessageTemplateAttachments)); + + string IMigrationMetadata.Id + { + get { return "201507072138058_MessageTemplateAttachments"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507072138058_MessageTemplateAttachments.cs b/src/Libraries/SmartStore.Data/Migrations/201507072138058_MessageTemplateAttachments.cs new file mode 100644 index 0000000000..d83d2520df --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507072138058_MessageTemplateAttachments.cs @@ -0,0 +1,40 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class MessageTemplateAttachments : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.MessageTemplate", "Attachment1FileId", c => c.Int()); + AddColumn("dbo.MessageTemplate", "Attachment2FileId", c => c.Int()); + AddColumn("dbo.MessageTemplate", "Attachment3FileId", c => c.Int()); + } + + public override void Down() + { + DropColumn("dbo.MessageTemplate", "Attachment3FileId"); + DropColumn("dbo.MessageTemplate", "Attachment2FileId"); + DropColumn("dbo.MessageTemplate", "Attachment1FileId"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Common.Replace", + "Replace", + "Ersetzen"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507072138058_MessageTemplateAttachments.resx b/src/Libraries/SmartStore.Data/Migrations/201507072138058_MessageTemplateAttachments.resx new file mode 100644 index 0000000000..3970382b24 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507072138058_MessageTemplateAttachments.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/201507092146153_DownloadGuidIndex.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201507092146153_DownloadGuidIndex.Designer.cs new file mode 100644 index 0000000000..0693d8660f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507092146153_DownloadGuidIndex.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.0-30225")] + public sealed partial class DownloadGuidIndex : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(DownloadGuidIndex)); + + string IMigrationMetadata.Id + { + get { return "201507092146153_DownloadGuidIndex"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507092146153_DownloadGuidIndex.cs b/src/Libraries/SmartStore.Data/Migrations/201507092146153_DownloadGuidIndex.cs new file mode 100644 index 0000000000..803e4557f6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507092146153_DownloadGuidIndex.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class DownloadGuidIndex : DbMigration + { + public override void Up() + { + CreateIndex("dbo.Download", "DownloadGuid"); + } + + public override void Down() + { + DropIndex("dbo.Download", new[] { "DownloadGuid" }); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507092146153_DownloadGuidIndex.resx b/src/Libraries/SmartStore.Data/Migrations/201507092146153_DownloadGuidIndex.resx new file mode 100644 index 0000000000..457d21844b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507092146153_DownloadGuidIndex.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/201507102159496_TransientMedia.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201507102159496_TransientMedia.Designer.cs new file mode 100644 index 0000000000..d738e053cf --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507102159496_TransientMedia.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.0-30225")] + public sealed partial class TransientMedia : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(TransientMedia)); + + string IMigrationMetadata.Id + { + get { return "201507102159496_TransientMedia"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507102159496_TransientMedia.cs b/src/Libraries/SmartStore.Data/Migrations/201507102159496_TransientMedia.cs new file mode 100644 index 0000000000..f70f8ba8a7 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507102159496_TransientMedia.cs @@ -0,0 +1,51 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Core.Domain.Tasks; + using SmartStore.Data.Setup; + + public partial class TransientMedia : DbMigration, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Download", "IsTransient", c => c.Boolean(nullable: false)); + AddColumn("dbo.Download", "UpdatedOnUtc", c => c.DateTime(nullable: false)); + AddColumn("dbo.Picture", "IsTransient", c => c.Boolean(nullable: false)); + AddColumn("dbo.Picture", "UpdatedOnUtc", c => c.DateTime(nullable: false)); + CreateIndex("dbo.Download", new[] { "UpdatedOnUtc", "IsTransient" }, name: "IX_UpdatedOn_IsTransient"); + CreateIndex("dbo.Picture", new[] { "UpdatedOnUtc", "IsTransient" }, name: "IX_UpdatedOn_IsTransient"); + } + + public override void Down() + { + DropIndex("dbo.Picture", "IX_UpdatedOn_IsTransient"); + DropIndex("dbo.Download", "IX_UpdatedOn_IsTransient"); + DropColumn("dbo.Picture", "UpdatedOnUtc"); + DropColumn("dbo.Picture", "IsTransient"); + DropColumn("dbo.Download", "UpdatedOnUtc"); + DropColumn("dbo.Download", "IsTransient"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.Set().AddOrUpdate(x => x.Type, + new ScheduleTask + { + Name = "Clear transient uploads", + CronExpression = "30 1,13 * * *", + Type = "SmartStore.Services.Media.TransientMediaClearTask, SmartStore.Services", + Enabled = true, + StopOnError = false, + } + ); + + context.SaveChanges(); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507102159496_TransientMedia.resx b/src/Libraries/SmartStore.Data/Migrations/201507102159496_TransientMedia.resx new file mode 100644 index 0000000000..11d76add9f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507102159496_TransientMedia.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/201507132241575_QueuedEmailAttachments.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201507132241575_QueuedEmailAttachments.Designer.cs new file mode 100644 index 0000000000..c4e3a3c11d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507132241575_QueuedEmailAttachments.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.0-30225")] + public sealed partial class QueuedEmailAttachments : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(QueuedEmailAttachments)); + + string IMigrationMetadata.Id + { + get { return "201507132241575_QueuedEmailAttachments"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507132241575_QueuedEmailAttachments.cs b/src/Libraries/SmartStore.Data/Migrations/201507132241575_QueuedEmailAttachments.cs new file mode 100644 index 0000000000..e174e25c2c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507132241575_QueuedEmailAttachments.cs @@ -0,0 +1,107 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using SmartStore.Core.Domain.Tasks; + using SmartStore.Data.Setup; + + public partial class QueuedEmailAttachments : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + CreateTable( + "dbo.QueuedEmailAttachment", + c => new + { + Id = c.Int(nullable: false, identity: true), + QueuedEmailId = c.Int(nullable: false), + StorageLocation = c.Int(nullable: false), + Path = c.String(maxLength: 1000), + FileId = c.Int(), + Data = c.Binary(), + Name = c.String(nullable: false, maxLength: 200), + MimeType = c.String(nullable: false, maxLength: 200), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.Download", t => t.FileId, cascadeDelete: true) + .ForeignKey("dbo.QueuedEmail", t => t.QueuedEmailId, cascadeDelete: true) + .Index(t => t.QueuedEmailId) + .Index(t => t.FileId); + + } + + public override void Down() + { + DropForeignKey("dbo.QueuedEmailAttachment", "QueuedEmailId", "dbo.QueuedEmail"); + DropForeignKey("dbo.QueuedEmailAttachment", "FileId", "dbo.Download"); + DropIndex("dbo.QueuedEmailAttachment", new[] { "FileId" }); + DropIndex("dbo.QueuedEmailAttachment", new[] { "QueuedEmailId" }); + DropTable("dbo.QueuedEmailAttachment"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + context.Set().AddOrUpdate(x => x.Type, + new ScheduleTask + { + Name = "Clear email queue", + CronExpression = "0 2 * * *", + Type = "SmartStore.Services.Messages.QueuedMessagesClearTask, SmartStore.Services", + Enabled = true, + StopOnError = false, + } + ); + + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.AttachOrderPdfToOrderPlacedEmail", + "Attach order PDF to 'Order Placed' email", + "Bei Bestelleingang PDF mitsenden", + "Dynamically creates and attaches the order PDF to the 'Order Placed' customer notification email.", + "Erstellt bei Bestelleingang das Auftrags-PDF-Dokument und hngt es der Kunden-Benachrichtigungs-Email an"); + + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.AttachOrderPdfToOrderCompletedEmail", + "Attach order PDF to 'Order Completed' email", + "Bei Abschluss einer Bestellung PDF mitsenden", + "Dynamically creates and attaches the order PDF to the 'Order Completed' customer notification email.", + "Erstellt bei Abschluss einer Bestellung das Auftrags-PDF-Dokument und hngt es der Kunden-Benachrichtigungs-Email an"); + + builder.AddOrUpdate("Admin.System.QueuedEmails.Fields.Attachments", + "Attachments", + "Anhnge"); + + builder.AddOrUpdate("Admin.System.QueuedEmails.CouldNotDownloadAttachment", + "Could not download e-mail attachment: no data.", + "E-Mail Anhang konnte nicht herunterladen: Daten nicht verfgbar."); + + builder.AddOrUpdate("Admin.System.QueuedEmails.ErrorCreatingAttachment", + "An error occured while creating e-mail attachment", + "Whrend der Erstellung des E-Mail-Anhangs ist ein Fehler aufgetreten"); + + builder.AddOrUpdate("Admin.System.QueuedEmails.ErrorEmptyAttachmentResult", + "The e-mail attachment data could not be downloaded from path '{0}'", + "Daten fr den E-Mail Anhang konnten nicht heruntergeladen werden. Pfad: {0}"); + + builder.AddOrUpdate("Admin.System.QueuedEmails.ErrorNoPdfAttachment", + "The content type of the e-mail attachment must be 'application/pdf'", + "Der Inhaltstyp des E-Mail Anhangs muss 'application/pdf' sein"); + + builder.AddOrUpdate("Admin.System.QueuedEmails.List.AttachmentsCount", + "Number of attachments", + "Anzahl Anhnge"); + + builder.AddOrUpdate("Order.PdfInvoiceFileName", + "order-{0}.pdf", + "bestellung-{0}.pdf"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507132241575_QueuedEmailAttachments.resx b/src/Libraries/SmartStore.Data/Migrations/201507132241575_QueuedEmailAttachments.resx new file mode 100644 index 0000000000..4bcdc56bda --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507132241575_QueuedEmailAttachments.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/201507141647299_CustomerTablePerf.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201507141647299_CustomerTablePerf.Designer.cs new file mode 100644 index 0000000000..c12151b6d9 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507141647299_CustomerTablePerf.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.0-30225")] + public sealed partial class CustomerTablePerf : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(CustomerTablePerf)); + + string IMigrationMetadata.Id + { + get { return "201507141647299_CustomerTablePerf"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507141647299_CustomerTablePerf.cs b/src/Libraries/SmartStore.Data/Migrations/201507141647299_CustomerTablePerf.cs new file mode 100644 index 0000000000..982225727d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507141647299_CustomerTablePerf.cs @@ -0,0 +1,35 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class CustomerTablePerf : DbMigration + { + public override void Up() + { + // without dropping the indexes we cannot adjust column lengths + DropIndex("dbo.Customer", "IX_Customer_Email"); + DropIndex("dbo.Customer", "IX_Customer_Username"); + + AlterColumn("dbo.Customer", "Username", c => c.String(maxLength: 500)); + AlterColumn("dbo.Customer", "Email", c => c.String(maxLength: 500)); + AlterColumn("dbo.Customer", "Password", c => c.String(maxLength: 500)); + AlterColumn("dbo.Customer", "PasswordSalt", c => c.String(maxLength: 500)); + AlterColumn("dbo.Customer", "LastIpAddress", c => c.String(maxLength: 100)); + + // recreate previously dropped indexes + CreateIndex("dbo.Customer", "Email", name: "IX_Customer_Email"); + CreateIndex("dbo.Customer", "Username", name: "IX_Customer_Username"); + } + + public override void Down() + { + //// INFO: (mc) Unnecessary + //AlterColumn("dbo.Customer", "LastIpAddress", c => c.String()); + //AlterColumn("dbo.Customer", "PasswordSalt", c => c.String()); + //AlterColumn("dbo.Customer", "Password", c => c.String()); + //AlterColumn("dbo.Customer", "Email", c => c.String(maxLength: 1000)); + //AlterColumn("dbo.Customer", "Username", c => c.String(maxLength: 1000)); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507141647299_CustomerTablePerf.resx b/src/Libraries/SmartStore.Data/Migrations/201507141647299_CustomerTablePerf.resx new file mode 100644 index 0000000000..5e18dcfce7 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507141647299_CustomerTablePerf.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+w8yPc2snZFKqi6zPm1VO0ZSZCnt6MImKemcfqGBkSAZzciIrLjw0mv7ZfuwnzS/MADikrjfIyIzK1+kZMDhABzuDofD4fhf/9///+v/fF5lrx5hWaVF/tvrd29+ev0K5kmxTPO731439e3/+Ovr//l//5//x6+ny9Xzq+893M8YDtXMq99e39f1+m9v31bJPVyB6s0qTcqiKm7rN0mxeguWxdv3P/3072/fvXsLEYrXCNerV79eNHmdriD5A/15UuQJXNcNyD4XS5hV3XdUckmwvvoCVrBagwT+9vpyBcr6si5K+OYDqMHrV0dZClA3LmF2+/oVyPOiBjXq5N++VfCyLov87nKNPoDs6mUNEdwtyCrYdf5vG3Dbcfz0Ho/j7aZijyppqrpYOSJ893NHmLd8dS/yvh4Ih0h3ikhcv+BRE/L99vrvDSCfvuVp/foV3+DfTrISAzMUPiFkRmBp/uZDWsIEfX15QyP6t1cc+L8NXPL+zfs3P7356d9enTRZ3ZTwtxw2dQmyf3t13txkafIf8OWqeID5b3mTZXTXUedRGfMBfTovizUs65cLeNsNaLF8/eotW+8tX3GoRtVph7nI65/fv371BTUObjI4cAZFEjKq32EOS1DD5Tmoa1jmGAck4xda59rC//atIVZEIvX61Wfw/Anmd/X9b69/QTJ0lj7DZf+h6wAiKxJAVKcuGyjpoL7RD7BKynTd8lBo24am0mqdgZdPRQKyCAO1auxruYSlaQr1mBbVB3gLEE/2aI6LIoMgNyD69e1GogxyBhu4PEVCkx3VNUjuV4hdPATuM6wqcAerN1KEB8FTt0URzNysHhXuCZoEzOOMSKHV5g03IwKoY1NomPcaKXr3008x5OgszaBAFIPokXW2k5U0B+UL0zH0U96vIO343nK0jkT+jMwO3OYELX8Bj+kdYQXJFLx+dQEzUljdp+uOoaSCft2Cn5XF6qLIVPqFQF1fFk2Z4LEVRtArUN7B2r7TFCqHvjO1TEOgvlqOhK4hG5C1zv5QPOVZAZZeanqZgjc9goNa1uiRjka/N+nQavvbUYiRad/j+lZmbsu4ulsUKolWUGo5Nyuma6tXo7GVKtpJ1WgyDBouzliwGsn1SjxOQ6fPaEyV3qCN09Ki+gKfQvlpUV2VAPWXWH1hqL6tl1jSvubf6qTHhVZjeJWuTAuQj7kayUg96EB1W+dlWpQELsQqxSupdsM1iu2EWzXuaWPYqFfF9IO7KiYZ2gVcZy9RxmfVziRjOjkZvYnjJBm9jcvm5p8wqcffgh0Xy5fRl7KTEvouHRLSYJuiTGEVuJlGaBQdMlgAxO5PkqLJ6+AdPcyXn0HegCx7cV6fldujzaakmmJ71G927LdH/YbKdkA0yU0jumaBpQNhYHTbOxYwaFfHdsvftqHxHIwbg5jqPCu//DKKy7t1DZscSpaN69v6WFS6FWKkEZ4XZR2m9dCWuTRs1sbqO6iqp6JcTt/yaY6BL6tgDwH2N7RHBmhVJbKAao10enAOXrDu/gzr+8LHHdXVr94wiA5aS8egFKEuX6oamnY5f7G1A10Z9jnJmiVcnpBJhyVeGxfLanTLd2gXL3Dli77J93GM4L7NS2RRrFEbLfmnGG03StIBLAHEZ/YcbFd+TvN01azIAeXRqjU5OisXJukKIC10XqJfXaTFX1+/usTnpoibjJjB80iYW2wXsEK0TmpfWtjrtrJYNkl93ORLxNhIzpCtWqY3TQ3P0qzG57rO6u4E1CAr7t6YUB8UoLqtDdFCZWAgejRE30HWBGNbVEg+0KYF7e7hMt6eb0M36QbJxJLXdP3NnsmhmrCNcqkbtLMSGoopugdZVbfVEStUIlpiR0LWx4OFYUG7uCTm2tbjO4dIRPIabeRDtwFmqzCGF/Ie7fXsgriiHeMhFhwUheO+RrJDTpfw6r5Z3eSUM8AX2Xc08TdZ8MwR5VDdeyj/0aLQovpnA84JDU5NevWQezYnWOZ4b6fPEum2qHct2A34mqujGSQLal6/OXjXoB23YVgNwL7ruk67Ghrh5sXBqNCox5ZEuLlQW+AclKjN38uiWaOOxLEvukVgkS/Tx3Tpd3AzzlLuevQ0+dqO+jdle0fLVZqfFKsVFfsyWmM918IVWpLDt5xocp6+5h+LFTyPYCt+hjVAIo4d3zqPVhybETc25TTj9q7SWhv0H2dkR1lWPA3uUPiYwqdgA/VovS6LR7i8QLozv7tsVmF886WoI2Ps0V0h9Z5xgw7uYzykXbzEVXGUBNv5n9JVWuPOkZUseIovH5rxhQ7kzS1IsMFQolWv/oK2PJtNwWjN/o5YbPRGFtXv6W19AsrgDVOPJ4ZtcQH/aNISfq3vEcVb5R/MKR3Ojami09aR4n+OGmzJ1mmCLZmj5ZLrQ/CYFtUmnD1OaHTozH3Ls1bAe4TBY0Sz0grc11sBp6f/qUNz+rxOS2JWfwAvPE47FEdJnT4SFDHY/iOoLgGycWCsWWWxOV4GQr3BsQRHdyWEtJkXcLa+QXYFn8e3GxfVBUyassUeqj06RCcvSQbbToXqOBrjOSzTIlj6Bpxk7SeIA2VlUeFj2za+IpgdF9UZmv7+HDjYulsuUyx5IOsxntxjB4S9W9k93B88nz6jXUiwLCyq0yqYnKg3J2g7dFfgg/zAI22Qo63QIn9EoonQ9Uf0gWZjXSQP0Y4MsDeWYDx6BCmqm2YU1kAvr7Sn3gtWmkcc+afiqR01XmkQttBJQRuE9PaFbN/PirLv4zFEu6/AgyaQPBTYW44zOwSfCuPdIMa4aGmJdiDDpjfYoiA+/S6II84ktRjb4I04GAkB4LJDRsVjj2esIlnAHTxuXo6but44FwJkC0P/SKv7LK3qOEg74c8gYl6k15njGV+cJ8g6J+jSJPz8iUYSfQX6mi3HbaDbmJxQsa/R27hcIzwgcxyIPc7LGu3R+zMxjysINK7TfBmAqXdnneb4TC0Kf3Vqi8EMR2aKTrFN2ibafFylsJXJYIWP1lBY1cPR4Umxuknz7hQtIhOiPvehCNXRep2l4RbzD5je3Y8niuw+Jjr6H+lyROwfx6XNsNKE6pMBUZgyiReQEC9GAmbQJ85uewMkxAGmj7B8wZUdvSd06izHqseggt3qE2XbPeD7DEHVlLBNC6aJvY5x87FvM24Y84AW/2BR25GWBC2QEy2DByqOe70L6oAljpOI5fhgkGJqxMZ5eV+0/hRQuns61DFG7ZI4LJHSWJW+9LqDvio2vvJNsIoaSogg0oC6BgzR2kAXaHPNAgoxNky5KryGBfIMB9rE6FhGdPkHOBkjtxQBUY4j6hxeZEOsHlDvFtMEOvEgqgEIcJ4dpw8RtX2nATX9l4GpxiCF9RzHeUrwaIfQwWh6z0GoOs6DefZ5OARXd7kF0fSYBVB1mIPy7C/Zgd6mbXY5Ol5X3X95Fc149BVU4zPU8hzvFbjTDu6agRP1KVWsVKc0jKc2/Q7KFBl1sn2ktvuaepoJsqilmiWbqp5TxaN2GrjDaF2H6D0uNleuhgdZQJEJ6XIlFzJArmzInw1rOsuDit1lIZQd5sBcu0z7cCTdHYplnCEUCrwgQshm3z414HAvxSNlcmfUDr8OUcjqtgwxuSMlBO1nJkakxrcK75Ui3TZS31+K7tTqm3LdlTuf/HYuM58MQK2XzKNmF11VnRTNGl9wXgbPjIhJfvwW5/SNzAyJjyRSFsqjG0yYeK5xIKZt/FVh2IqJG2+6gm4vv4ETliUtsOviJHExjOGRMI9BcF5YeyQ6VB3nq5NSDW3Ka0jGIQNUD0UKHXQdqN93B9wH6lEclmJ1W4aleKTrMfi2SqSWTQ786e5KHBc14tNJL+HgB1FGX5l6MYp1/eZwYSbKyNr7d7ECADtPm+NJFb4/dZn+C4a1zVz9qa6KNn8Hj9s7IqfD85UNHgvYd1hEAF2A/E4bsxUthUDEm2wjxC9s8c2hbTsF38uEAwKpSG697yl8+qzfVHlq49iHjx5bFvMBpGR/Y+2FbdW0tPebk6oeaNNjvkyw5AWAsLv8fQ8839jo6h8Md436akk01vMSFq/n/GWcm/OwsHhu4udIlx/35hGI2Ce3ogqRQxhPbjl946dOGJzhGUIO6mWq7GOqbcV0tpHXUuotDYpjNJXQOEpw3JgLU19jJPWR+AWjnK5R+A5SrJWbKC8pSMh+0WTQKoVyHL/K5RqOfmp1nGYZjnrscyMH3n7sL6XGQden7iUbCCZrtJPHZoNm0PA6N0WcyaMapTNwT9lwPxtmL1Dsljt/heM8nT4jRUU/xznBHnkT/zDeCZJyd6w9b/Jbe5go3IBXsGlEh9VG3ZZhIRjnFeyTIivKj/CZpI+evPWdeBjbdZ8V4eiVw3SQGnVbkXZasQ6CFtUZBHgiegNhRr+2cqXacKjFHYANsPoSwPBDsSkSAUfYwvncWjD1N8ou7ly8RhCuG2hsB/0wun6gyf2n0BEst1ret2Er6S/dMH8o5FBdYQT9EXJzyKb/UXRJJCVy0B7bHN81acQNxQmHEKXtClE6BBXtYFDRIc5m9+JsvE652MVdPOOSlQuGghQoxm6jvUobvs9o8USzEV7hzBNd+yTqplMx5D29vH49hS1v0t6xXNiYcJMkam3TqYcR5SPM1rdN9l+wIslPoyD7UvjgCrHhXe+hK+x2xW11x462WDpa5LCqkKyaUi8Ida45eVaMSFlDf9VeXS3o7KCXah/109scw6/D/kTdVk+j35t0aLX97ZFd2hA0FukJXcPT53FasXhLO25DZ0W5AuHxPh22S5DpU0xFya4/5Rs0EZM/H93eplkaYZtKcgIH72Ui2dWLqg0HOUqYxx29bw7YxJbE4aNPoKoX66PlEu12JrhOFXPXgLv+qbhLc88r07h+n1laicL/qnBLUoXB0Kv+awqMilgXSsWYdRHE1dDpQn6Gudd1k4eV9JUFUXeYg3PtNb/n0HW7g7neWDRiv3kYweRSArreX6Ajlwzk5kAl1GYg1MRmwVxpffqMDRqQHTX1PTZp2tRHFzBpfYOSEehqSKfBqoIwJ3a1XCcImQHN6ryoFDdkhmLpQMRSodcSEK8uXhXrNNH0kZSrO8kWy3vJwbh2kziY5D0kRdLOsSVCv7hi1y5dQLTHyHGEFVRNMAMi7aIcQuiqAsy9y0+gXJ4XaV5XH9OqVh36S+AUvVfDScagAXYdSR+BaLXWCMAS9cfBqBUgD+icd4pKB6rOJclDScmvBBJor4YM2tgPBHXf16ONRpG/6RAc9vTqts7SsqoniQrH5uskDZk2/XFaQSy2BvnLBO3IQ9INOzLEHTh962OaJ65neifUgygjvklLRPPdVA29H72hf6RrbCuBzJCCKtIzu/dFDo2PW0Z6r7h/1W98Zg/ab6t3Mq0MSZfCfqEdYDYLIFckLNp8ufNaTQuptnMcpNhFBkDZURYqLOVTT66AWP0Ox2F5VrdlWDBtvVw+8RhV5/gI9qASZLHy6V89FZ8gJuKiKgyK9n0EhXR1X0Jo2+DPERpEihaWacI1FvgU9HcQ7OKNFxkSLaQjNPZFs4/lb6INd+Ckeyka5npTuVVvXDoSM7Rkd2Ws4ry/pRcCxbCYtUKyOkoBxM5LoYK2hSw9PFagHgE3dYd1SKOW9j40NZ5eWtzliNbt+7bBEXmnz0nWLJGsM9eIdSdfcc7ZOkklzWMRIKcJz+5nzhZKltKTU6hX3jh20Mh+6oq13QPsZQbTQVsZmTf4+d15jO+bmxI+psCg9eIccG+DWee1bfc0TwTh1xoxQeFow4FvhKi0DtdB5jUy35Eq+L6cRXBJnHsCC5y4E7HeXj3KaIwmiB8AoTpLU0ZKeIn1cVbcDfFrziKNa1dvKBzbEePedWaSuHE8eOwgj2dC9hilLEXR+noDuGEnWbnASlKgYDZquxLAQxjBYS1Qt/UJ5HcNuAuO2Yx1d8M1t1WxjHEA53UJr2X24G1rv7J1+E4kj7+6GuNFHR9p+0jdyIpvngd2Dtdit/GC5UgHnNQ6JfeieK2GvC9Xu2TadrXXzcpuEqwbKLaPTKF0uWYhgtbqTSec12qSZCr9FwF+0+M5LNnqtubxtfQz01Fdu+Ubo31U+48GXsIpjjLPMnC3WKHB4pTdxrsaMS6K1vv7jMQI3q42L107GRewIqpNrsxlkFKNqQUU1LseOujIToY6WKtKcB40rFnXRUhuS2htUCEjPQzat25KITnSJmlRYek6z5q7NBeUiPFSYtEkMuUTxXSKpBR4k8pOhXgphXPF3fBYaSQolNvhbWN6FyqGP0DVDTBe4Ms5e/t/ysQB2twI5nwDXgz4BT5V/i5dXPsNhWI7eKz36E6SdWRK9zGmNL5bEs993GOUMjo1sdcbwA1by8oFJpYCBbNs2xV/fsX1D4aSuq0d9x5f3hdlDHF0bPaMqK6pW422o5vHSftn9LlvlUf14DSPMjK/LcuwJMq2KUKhdHVlIVwv4FDrs9zT4mUF8H4Vrangt3Mqsux7UZMtWUISDzlbAxhF9YZHtB02LO7VUV49eQTzqLc2A075voYjxDUNT21m1GDiDkYDG7ZvproWMO8tioMhqOfDkWNIRzKL2uuaX28R/21W2C16zq/AKkIhhp2ItDCs6FFFUnGjy53TKHLiqkicGK4nhBSJFjrFW08EaYiDblC3FWuTOI9+aBO2dZbu+HfVo23S7ounr/lHZEicE2sxcL+EN1+/k/Q6VwXWlNtztXGe3ehoFyq1uwCirWU7AKZAqu/9Lf+NjlXrecuFSKbPZQtV0GUCjDrKTQKM6KDX1W2ZTjV/+WUU51kJYbQb8rFSnyJEnUbDeLgpMKjWKMlO+yylLfMHLh02KUptZ9day8BylVaVIQchD6XOpqiHFDWRHjzMuORw+1zDhklTIqwCAQ76yVs/jWovzqMbN08Cjt5yQCrUaEIs2Dl2Mu8lxLq0pEHWhg7xQbo1vB7pEuM0KfD6WW5Hd5tOkCCsb7IzTCZs+SvmZcJ90zR1lCSwqqZp8BxfgEabVis1H/slc+3F0JHyM/NK1i2rs5euHZIpeyhWUrfaJHQ+qFB1WyQTdPADzZH0cKTIKNd90/lOvo8wx8Vz5xzpvObQpFG37eAmgbmhizSgrJNUJnR1NymgcH3WdSZIoREcB42mbotQaWs0Gp4tjCAUUZecUBeLYnkLyf1stHszIaT731P4FIgCJ8T2SoigwBNrint8rd51O0LYjwXB7UEKqa5VPFrhtCaYOtgBSXtHfuq61gKELwGB2v+g+NVtEQL9XhbNeuRT5pHuak0Z3Phl88xMEKGiLA5Yg0bZh+zjEvHnetN5I8NqbX5NA3HanCqTa3MaIFybd50IUukEx0Gva5TM3mvjvZTxqaJlWlkKVxfSh9Jk+sRLXXRz66wp2qfW2v8OSkLdFiHQRM+ukLaCX3UmA94WPwX7amews4J9lc3xQSVCXpxptonWkTjYzsELvpUSFVmbRnvMkyQFx5w0ZQnz5GWS55f6xi7AJmz0A0zSFchevzov0S+ixV6/++vrV5c4g8Jvr//qLQtsIFboPH0H9ZRq5bK5qZFKzBZ5kl1htFbE+ovzsJjGcNr6KRq7wo2huSGvaE81QqbRaUbaaZ1pRtg1NunIUEMOouzeGKMc0SqSYhMBZGcQjk1TdctjE1jd8tjU7vCPn9qT8NDoTNrL+ritELUyWhPI8G7yJVwerUYdCf0K8A9YQsR2EdKr38PkoWjqoxrx0U1Twym30kLj1X+uJnjdtDM9Yl1zOrq9TbMUyWS0/cF6dBqQ+0F4U4NfNi4h0l8niLdY28mbpRAmOqp+vInEXZ7EDBdoM14iA1A9wKVqSkYd4cnj4/gvyeKGTp/XaUkcCZ+LvL6fuM3/gmB8etLy1T5y9AHepMHXVihURwlZNz8W2XIC/hAbnogxqYaPQf4wyQaYa3MSFUO3uTiZsrnhfaHpmlzcgAmMi241JQbgkApvbLlv0Iag7NK4XpUgr0CCf25Mg8mbnkRkVI1fwKrJxif7CVhjR/q0BBcbnWi0l83NYKNPO+TzpkzuQQWndOCfg9T30jf/murY89I1h/fnSOGsm/a0ZHKv8QeYwXqvXt7S7IQvID56o+4WWx1bfATVF/jUeXG+FPieTcK8Shh+1Mme0kiPPIkYXfOAm2NPWbkQKSEFco3J08YNti3IQgbZEkXXvAMFe9/Qtwrt0j+mVV0oHkyUAV53h7ibzqqhhONkDajrmyC/p7dkI2cchAzw+lsFlz/S+l4YjBlaGJRFFdfBkVo455uGv0lKOKH/XJHQWb7cq2df2hRRqp7hYkXPNkXynlHlrj27gEsIV3BJK7FT5VugNJSRKYzAwmDMNZyfX0d9Vycd7EtFsrMl0ofiqWKfXq2ttLEAyWs7DkCh9HiooPA1uSL0eG25RVNJFeYhYEXd1nAMGujhbbVaqJt4nHe2NicxtivspoZhke0/CrKih3a1FzqdMoaBYNVxVnN5SbrcWvANRJNhO4i5uq2eXqESyqygMZAxL8JEP1gcSaH01LS3djc1DIZu/1GQSz20q0LhDK0pjHarAcltuiCFE65kDopF3VbvN6O0guy5DzvtFCNQbuRQhUXVd5ZkSwMRvFE9wpOiWU/k1L5AxFin2EE0hdduaG2aBDeXMMf72ClG1jY1zbA+o80WlT91tHYW1YY7iPtwbn/rSA4o9yXZyu0kLOC2o5FrUu14rlV1xBEpQJWrsgo+aEmmehh0A+XwFpLNZhwRKfh2SBQ7/7x9/C0Uzd8b0I0+aNORp/V5mSajh/AODY0esTvFaCYZydgRxr1vo7URxyYZ29rYtJslzHXa8NYPxVOeFWAZ4TmrRdUji2bGf0ICklewx+u4BcLa+gdM7+4dti6Gu3RNvswgMrPA+IEMrYI/IXnyYvG30lA6qqoiwVHJy95YkZ96RLSSVJafyaoK9+86HDRKj2skB5HWBmk7q4ZuDVCyjnWFmq71EOF25RfyFkSQXYlRHOxKdVtRrMEv1Jsd02VX7K9iFptgj63c0xo0gd3BvlTcJAf/XsImOVgPyjotwXeQQQ3fRbpq3xI8Bo5jkIE8gYHbsmr0W2ZTOdBG0gzaQDZZtIssrE0HJ6gNLXD8E6fIAUE2o4l43NSH8fg8qdFF1AwxRAf9p24rig1yVYLkAVF8okBvckk37u6O8Az0DR//ALP0EZYvntUnt32sg+t4oVfE3rmG/KkDQWmI640OEDvIACiDAFmooFRPNMoYSungdDdLJKFTlEg62SH6NP5yozDElAOVvMqlxXO/gtgxv4B/4Dcj/R0EDJqDHGjkIEZCsWhCEGu7FOfw6QKCqsjPirLlpundIB3/QuL2jnJAENYB/eUpu6ntbsCOnzfmsga3t1+KKVLUHC1XaW6+2vuXn6I8CMLotjh55bboip3vjpqiiWIvLYOQ7DulYIGbzYIYZydoEQqLp+AxHVY2nfxHWNnOQdnZN44nhO15nkdFeopjRFhG80DGCQuZJ8USEklYIh7CYRGjeSzjGB27oIo3zK3YXbBa6pqGp/cYSjDJTkMNG/WOsdCQbC1RApn77e2R1Z3vCq1IjnlVMOYuRzn07ZBcrpGs9XfcB13gcyIFapAVd2+0eA/L4+j6XE74r2s2EYjnWoGTV52lGRpYhDfbEV8/fc27YZ9TR0v+SbC8E/B7SbmW1WUib1VBfAzZqpazb1bDJx6j1aOzJYEOiyNdtKjCdg/aoXorTh3ag950VXgjPx41bpjNGBpMTibFu+5zizp/vhNBa4TpRbmVp21XgYcyAN2ri7ahB44RlF90tXdQeH8G1aTjXsV58fQiJ5w1B4htyE7tCtyFb8sQkoNoeYvWXyI9UmZaq7Wr8vVmKivJcksXq9ZRBiYGb34HZQryTbr2k2J1k+Yg0CI2Iz/wsrqty4cmAisb7t7Xqe4QNE4jn0He3IIET2J5jmbbGHUWp1lH37ThdhPpSCxsP9JlPGQf416bAhUklHONRrZEi3+wqK0OcY6qKr3LkRSlhI8WS90Z9Ls4x8J9lCD27TueOfUnF/gSrOtNuIpcyQt2pu3k8RJSmclDnGMf4uv82tRfbwnS9rzXmajqpGoUc+hWe83id83iECwB26oqx5p1/ZgnJxZtqx2rFrV8BuvrYqWl2HewLA6nEdNVfYbN1I9x1sQ3Ft80PNiDoyv0Dk00R+sVfK7Rp9V6/PT4ODXPH01ahl9Wp+Qlr8siixEXMtPpFS9CDurVVad6K1Ke53zGI/UH2dZxHaHe2WoxVB4xyfOodQtIa1wrNa+RBtr6KseCG5IYnoeIi8lhFbFYRY6yFBj2ThE09Uwu7tFSv4RaSl2a19jmEkF74HZ1Wwq6hcfR7JAQGaIji6woL/9oQAmri7ub0QfV+paW/0SSQAf3R48dba+bTtDQokKY0EIOkwhJlAJsSA4TUQ0xzNpPaf6AA33nzGHnam1tj51laXfaGWteK9JVCsvOFe69/Aw4DmuNTrPFif+MccuiD4rGTOfpug71MkS9GGAMPccDlUr9wLrXLOhGruUQguQqwGL6TjdNSPbwQqGmh7p9un2kEGZEn5vz+EvV/nfQF+q2JopV4Fr9VupOcUZq9LLKTnMMH2wgXcIE8UqcQRiOVYtKe6c20iHjp+KuGA41Z3NA8mOvV9lxsXyZ4q3VIq+RvPTnUV9g/VSUD5Fn103nfQbkuk6g6uuwHDSguq2W/KFs32KZR5t6GGv2hvs9XEGyF7jJfNZhUr96w6A5cKO6LUKo0ZeVSHxqsfnXtPI+zto1Jvcfg+RhkZPQDfpV7IBNrALjQSSiTvBo++JY1+7nSE+pYD3pVWgTrLDtM1aIuUVVNSbZsBpArccRZTNLx6BewRWyk4NOHGXoDopE3dY81tn3FL8Ev4m0na5l362YNT8jRoR3RfkSgZd5VAc+PvDxZHzcKfcIbMxhOnDxgYsn42IyFWkSFMtUrFZF/obHdGBjdVtx3EdoRL+XRbOeni1RyzNIocE1MFJo1piugqMku4BJ4fXUMTlPQOjeDEgOEje2xM3psNUejDtH/iyW2TCG8CtFxrPsgUeVZ9lyCGGrrQAL2mF/K0OksHgz1D8I4D4L4GXW3E3faqxLlZ9AfteAu7FWssvkHi6bDF6B6sHnxAdVq97QSA6ypG5rnhh1ZHAU+TLw4Slcd/quxwrhqIv11/y0LIvgR+g+AZKvu6w90qfjuqf50rPmZZMksKp826UHHyu2xN5evr1Ns9TP2zLUrTY/D0pG3dbRclkiPgkO7YEZjBBe7rkIqp+ibUcnN1Z79rgeoCg7lS8UTVQBIuz8B4IK8V//CJUz339IS5jgN9PeMJgOrK9uy7C+2l4ncV1dXqoarhAdnshWZOrmLzDpLOOe/7p9jsyOuT+gdSyv/MItBEkZkB2E5SAs+yQsJ01Zwjzxef53IyQ9koNweAvHLyN5CtuJOUEodI2PJBhwbLn4VGAE4WS18beeFeUKTTqVc36s9j6lqxSx2FXRhmKHGsxEwKr7bbrYuUXPmfBjJLoN7anRzEa/OWGtlRGvNavAkFGCo3ojoDooaXVbNJ1+b9Kh5fb3TMGeZAaDMxwV6zTZxbBTgX+lAadqKGFTrgENzC+WPiJaDC/U+wosi+cgrRppjRHZjXkolqReFbEwIf78J0y0z0aOYzHiTGjTe+VxUjQQbJ8sqs7FePxy1NT34a55CuEFslvXKZWDwxfnSDqUZmNF5gharVyz8HSCCCWYJA+EGtY1Xn8jOja9p6GVfd8AmXpOQQYtAcQ1jT59KnwuXqJad/ixdgrLQfur26LIhJvcmqs5xgd/R9KhIykWjsyKmJ4B5lqApwN7lGCS6B41bNSnHemGZEpFVq7tbXRV0pIxhjrB8AeVom5rZleywUX4fpxWvSIiHO74rNYgvfNxYHSrc/Wmx3FgXW/eGSsQyLhNGalhnNhj15bYmF5eh8NQIkQBd5MGMeRQHaTRWxpH0uTHSXKKpizrgk2gzn9t24VQ+Y+UYSiSvE8TYNtOQpLgB2LCM1dGPQu6hPkS3/sGWfYSiuuorkFyjzc7785S5xR9m9rvg2r/bKxtrSu/wKfqE8SiHnj0MqhMOcaD5lS3JadY8FkMEUmdOvxllDPwOPokqtkx5iW2k3uYPBRNUD789o2jNwKqg9BohEZvbox0H2UnH025vE/Xawx/3l61j4V3UV2B59NnSFHDFxVC1KeziPbw2XY8ERPL3lJ7+nitoXmyRA57LdFhG2egbR3haRLrikHPkchbiaiED281mJZqnmKh0jaPaj88cDCSBrTXXNPrLP5Qw1nZeemsC5y8APP1OXjBLPAxrXBMsb/SUiA8aC11WzzJQpUWmYktDTbjxyqVMwUPXYuVN3JmW0eQM+uKUeUsnoAdJEvDxC9JBtl3tz2lASM6h2VaLMOj1GqQEXyBN7jJrWUskjH8ErGcr5FumS7ytE5Bts2ajO6ilRa7ZmuoVRcDaNRXLLRrcIZy/Z9aLfNbNmd97qWWyQ2W9F/kcSpCFq9ID4KDAL8REB7Us7qtOKlo/BKqSPCQ20zz5XIb2p+r6anyuimVUT+TUu0jSNb1Bnyjb9RQgiLVgAaaehmRhj6dtXc6UhbRQZGo2zrv88O/C1MBA5738zhp7V2NZVFVSCyycC7jUR34bFv5zMGqCQmAPwS+G9bKu0/wEWbBd4zui7IerqtNHUt3hsDjNW7YzK2H9D0jB0QpbxEYxBFRQf8eXBxCICaHZQnLKdqa8I636YYf/KNpc1yEXewgj7x9BJU++7cP6fyuLqiuLGivKsS7onAJu2wIHnnC89v0rinbHWuH56Dy1W3NE0+6h2m2qdgKr9ykz28oDAeO9ebYXX0kgaRR8OEcXK9q/zuwjUb2yQ2sSJdZTB7+JGuWcJFfpqhJMPjb/CPAzkFV4ZtjqKCOEmjQIxydFldprU2jFKeVSYL4P8MadDf4dOZ+vMY+QCpUfIL2ppmryPcOLmCOVPJR9SNdtocsQdhaLP8o8vHp0Db1owTrzv4X+m5TH221n5h5Cxv5cYEXuAj6pUyLkmjloPNkPLAroEuL5jMX6jW5WVErcnvZZrgOgtkVbaaxa72VyEV1loG7amjR/zKHvqVoqzqiCZrc7AXRkJ4Vdgo+w9UNLHu9mhU3r18Ra/231z8J08XA4tsyxAuANsRwqPROX6l98aqDldyuaSeE/nhUVUWSEsJ0OP7ewAYuORpe4+5wQzvNl6/araq6ymZX284c3g1LoREXIpqna0RlxEq/vf6/hIFaNTecDW+a+1A85VmBk9awLfz05s2717wh9jVvwxJe4RgHHMx4AqoELEXRQsRcsl+6ycIH7EgDVYhv0rwWDb00T9I1yKyHwmGwNBZxD4e2+JIPcI21fF5bT59NJ/rrXWJHhvY4Epoo9utbijutmfaavtVnw7JMBQPDerIp24SESZk+c21sFZdKRzItj0rny6YL/HXPuTmVEjaaxdzUHk2Y6ZQt3aqEnTUSs1XcrB/RbKpXMqc2faGqzcbe3XnleUqeq77u/lfytBxcxsgspAsHK9qQsK0C+fwsqx/CBHyqnyebDnRVtoYxu2gIW6bhn5EfgzG5p+fFNrafMdkhzMCY7DxZMWYfYTETY/aHFEZdyQPKmHFzZmLPhgJee81I9lA8Zi9GUnViAhZS0XUXtNqHtGrN4KM1mhTsBuxGk8JKyUi6SjKm6uFdmErbhoTBLBk3iDSdpLsQpq8yHlmGFuzVfgSa9D+6W/XY6rQgi6yWC2Wirle2/ZO5hEQ48yR6qTUr0k2g46xIZNOPvv7cdtw14pv0EZYvJLDIYGExwBobzsN4Y1HLeI3u52jLp64301lhUjpbcRVVb27OGsyBYUkysIBQQcNhPvaZshmHZXRrdgrKUUzHpcr5sukCnfBlK7jUciPLw4/Mozu8l1WNYQYG3cHdLM5ZeAvIpqg07mhlwDLGpOFcuFKKf47dra4jE/CVjs67sMvtmJoZBsMTBrWkrKjRgr48Z2xSwn+6trZGLxpHNJ2CNM6nTVfoelvF2ZYruqzORPy8w6u7bhwzMfAOrvIkOWOa332G9X2xvL6ACFuKA2lPsG9A63w0V5UxMVvLhX8t2pPto0hhRG/kZY2MOjRtj4hR4HWPXtlpGbSULjSgE1mkLdhTYn6B1o5gAlHWzpHVVrKtMpsQd1ctjczIwcnYcLi1ac+APFZ71otnliv6MAHzKGi6S2zDqh7TNLPSEpeFWNwSRtJqyfjsJO3PhEwlpbVN+0zF+Q7E+0uvm+cxVBwggkoPxYertw6H4iJmCWPZMa3P2I/TLENWS9+AsZssfHQqcOjtSSFKlwc1uhsdmzvUpv7yFXT0GK6LuJNFaEa2iCnIPr8BZRrFBBrLNF9W6yGVyWEWfdWmxGov06DtBen5JuOUioe0tWT8KqvgwrT6BiWcu0matW2cazWUCdjXag5t+kEnppuFh4+z4u68qGoz3wqQMl7tgVz4U0S8Uzyp7P4EfKick53hve4x3uuBcXRcwgOrOHB44deNCQX0Ej5UMfh28KFqBBOxomp+bJrv68ztkb+Ajyl8+giz9W2T5djmZQpMznlDdY2fXqjp4aw3ta7223ej2zqudhvYdI58y3l28Om3FWdjf/ys26KGK7MhIEDKmLoHcmFiEfFOGQLK7k/Alco52QlDAPe+XzsGxtFxCQ+s4kAPQ0CKXsKHKgbfDj5UjWAiVlTNj03zfZ35DIEiy8xakIGSLutF5nRrnEW4U5pP2vUp1mLZHOyExsM9P8qrJxwFUAiJwlimoOBUfNaCuHIbjVhmGUoYeDt4TdLxibhNMhdWJh6Cn5XXvhc1cVUlRbm8pjhGxx6KOioepMFdOVHVlIIr5ey+HbxpGMpEfGqYO1uebWvNx7mwXKUVztDUjaR3+eOBqs/G9NWk/MvVcOJffWuakyEMMMIJoh2R5ODRzw9nIsbpM85qCbKjpr7HvNhmAOMmSEkbq9oyUukqupDPrgM7dejoNKQJtKTTHNv0Z/bjyLOibFbE7WpkcBFUxs0DlAvrSlA78WmUgBx1JybgLDVxd4eNSBpkSz5iYZWM1CVmduQkDvlMrCTvxVS8JCfw7jDTNfm3fTJNO9kUoJKNnDmIRiphH6pvW7dmqvo/FeNJ5sOm6U2tbVBiLddYKJl2yGOorxazivm2lO8kXZ9W4THzYc13W2B+UfxitpKoAcc3wSjkKu6T8vWWsKBiDJPacOL82DRPKszGiu07v5aRxDJgGSt2T3bYs6EUsX0AcST7TdeLCRhJR12b5tmaM3OUcT/AgkXkot30ecj7PhnX7eSOoc8z9Q2n9O9f9B7yiqm4RFtLl6mMruCTz03eoCb12PZxqdVQJmBaqzm06cfsqcqkI2k1nxM/tWI8Ffe2rUlYV6qzt5RvmUHMxbTMvNl0glSYd3EnYTp6HuXglMu7a9wfj3d3WFDR86lWeHEudonZTOlOBMgRGG4XE5ooez8p2+1g8pLf09v6BOCACNTle1DB5Y+0vt9wkIpdDPVkbNlXceFKUzMqtSjj/nh32y17NQHvWU6DFSdKMczOmIwRMbCQiV+ktXRM6Ws16huUsKdKCubXoVZDmZCntXNo04++znbx8DdaxNwYmak6GTezre6OIWo/mLmYWjqfNp1hKs5rtn4pamizR9rAKU1WDOJsslJ4d4c1FT2fylgV52L790gX8AmJz3mBEFS9/Bh977pKMjaUwLswpLa5nfLS24xkAm61mb+d8ODLBmJnCBhrWqs96f7HpSE/gQlKeoQTVpJbc3oisWCqnJmu1x45rLuzvMg7PoG8yudh+xeXvt/tnrlnFRNfMNA6pnP1xckbkOXyU3D19rCgdAgTcqJ0jmzaHxDMG17QZ+y1i1jhoCMGG/CYQ5LeBVnNqp5MZTuraGzLU+stiFy5gHVT5vjVK2hzM0IOLjcHKEg3y1naxI7ZzLoxTGIt6+ZpJ+zkTact9Z6qQvRLezEVoJNlUpBmT0DZrmXHTb7MoPZsRlNHbqWw4G6Wirop9fkhNYbRFguLnk1ihhinwqYXm1ozmsjcSIzLhrLG6Ey4m+uHcRhz8OtOriLCKExhFaoKo3PqLsZamAYxB5vuYOQFmxvPxKFSaGPmRO90iTvJmNoRTMCV2jnaIZa8XMMkve1u/x/VdZneNDW0ZVF9bQ3Lyit6sLChBzvI0nYjmo7F7eZ4F1hePpKva/zntYIjlau7Oy6pgaFB42RseHRH5mO2Esv5RSVguFNYLP68YeVZlGLY0oVEy+B+ul1P3tmWHG23rGVNLvrzS1z42GdfsWz4xl/+WjxzS2G/LF+BO01+OBFWIzceEsJgVptgqDhexrMO53dQpiCvh2k5KVY3aU4Ar5HEpI+wfLlKV5q8tm54NITToPCgqXWHZHcv6YGP5oX17Oh0esF1Tm16RtebW/h147Pc0Fmg2AqO3+H9ncOwtkM0dnCnZzGqvzeoBHHGtzwNkgoaz1aIBtMhiXwwA59zMZB1dDs4XjanNj2j620b7/uuAB5qPwJD75OC3x6tvkeqvB+K2VNni2AeBte644Qx7hrLz+F9c51vByHYGmcbP7jvIGuGEQrc6siaWmQOUkLwRBAVfX/UYmOS2q2VHqsBzydJVvzhIFU8irmFy2lrYGv/+3jRtsqSn9lc3wub/PoSrNYZ/FA85VkB1JkN5OCRuYtDLnMb9v0cnbfkfZmQu+SUtjoNYGrOxmFXKSzRqBPIvDii5DA5uIzDBkgXHlOgd3z+hPDZ3Gu0figTsKh+qmw6QNfbAgY1OSIEyBHYchfdC8reT8qEu+tCoMLx25+WHjFVPc16rL70YLEyK9vbJWa1HMt0S7xpFm16wlTcInZ2Z+TpWNiHeWPaluquzMJ7+6A+h536WZrVOLu7+ZqaAw4rnuSqB7Gopis+19q2UPeaRziHLJhZwF4nz3pd7hgkD4v8si6Sh8vmpkrKtI0pM96aM1WUiYKijosEGJvdqat0tqOZgMVt59Nl07Z1XG0yNgz1JubpXbSdLccyI0PvoBVzlGSS94mVXCwHlzHvAOnCrgr0jg6y+VlVP44JOFQ/TzvjHTu6vU0z9AVem5JgCJBSnuyBnHhSwDz5Y1XKLkzBSSrC2rQ9d5Yf8kqbm/GprqJ8iM93cdY0NdODtnNbimbi74SNiMb7iCTmM2J9cAevz8piZeQ7TR351p8Gd9voqxuanu0sejPJJtxIfJte0PW2hfmuClfW29QYlfGoZmZnO7Ev0zOdSHabPmxqzbiFqNNHNDmfirtr6jeeTM0+QllHvpkYQNy2E+pWZEYc1/kt3FYYxzPJ3sI4d1a2IVt1K9jXqCplwCMx7G66HXUjmJg3d9J0PLmHyUPR8GGlwmd1NkhLBNLskNK6TrkibVuXMbUwxq3jbtfhTcDxrvNtJQV85RnT9CZNiUhxdw5eSPrqRZ5ivKYHAzS15El72QpueXt1jdlnzI9i11p1ZgKutJoBq35Q9baGC/u3IQS2seURFQIb3vR6lMWyeQm3mkRjfqXsOroZ2N803zZd4uvOJg2figRk6b/gsu/j9SeQ3zXYG6BiQHUVGccL0C68rmlKwt1Dz7eOq83jmICPzfNm04m+zowca7G1M23pHLdyvlu4rYjun2n3tiO7tlNycwrVqVENWPY3XlagrL/e/BMmNS6Cz2i+E9IxkOdFTbD87VsFT7ISs0H12+u6bESViVFfwlpyTat6/aotVt51E5iRRwYbuDxdgTRDlj1I7vEyIscqATSi7y/7yDBu7kzZ99HQMyOqtv9JUjTyUdLlRmTdovsZ1veFdIQMgBmdIepP2oIx2tO1UatWbNFqkJlZJ62U09SXGZGcgBreFWUKZVi6whfzaNKkbkopjq7IliA6TAyENXnwuyZpCVViKwEzo6YSdUlxMjnS7AaunQgWxmI+WvjPIG9uASGXTjhoMCNqE04nZP0aJGW+Ya03IOniJaSEG0JMTB3BLKCSAlJmJnr/0ota3bEQZoRo/cM3RB6RJSDtGQNgTWyy0MqFgQMxojzOirvzopLi6suMSHpLV4Zks9UwIcFGN7ys8V7vAlbEDJIilMAZkX+BT5VK9fdlZpkssuwor54UojiUWiFSobBmAmw76jigDRE09QSWq7SqkI3Wxs1Je8XBmC2QZ7Qs5yA7aup7bGi2uVHVDejgjY2R2BkVBw+FdmiuinWaKPGQUhtEQIXBrhe/l0WzVvaClBoREcehDEfnhbVceak3y+WaVQJoVrOSF9Hl2OWP2lti1yG0o59KYQyFdmjwm9tKNO3L5wY04lu7cnpJ3+S1WPNUltXmkVFLJCqKsc+hGkdLPWYnHybz1qCxb+yLMfL+8S/72Jln8uTPGkNNlXbcNAZNkmnpeLTZxL0as2/GlngkK7WSUiQdtB0iTfJGTQPaHJx+DTu0ZtuEDW5npNIcTg6d787HDY0NWQJkmKmMDUYbupDvJkmBXfXPgIi3EktXbh7SPdpeEmrcyI0vBsBsfMsv1EhtcdWlKIfN3hVcrTMgZyUZnK3r4UWHmIex1g4anByI2SSAOUS8ppUlHsa8ae3vmkh3rZtrQAY030oNmqHQzObJPVw2GbwC1YOUzaly89D6yw/SoW1uk5h4D4IK8dMPmN7dSxdyBsAW3YcUWRKVQkx4GIvdVFnCPFH4DdpCsxElBPArzXcn4WVDZeWSwIYlG3l2iBOTcy0VvmePivj19ejawFKjOlmtQXonJV5fZsElhBRaXceCWDkPPsEabVJNUyyHNA+cDzySUkCME3NFq1znVRFwRktddt6v2pso4jkc27DBbedrog545V2WHMgbO4sZaqk5GmAhzFNYFlWFKmYalDyMxeil0m8j9ehXrTKk2iKzDQWetZ7yTbmZ3kqHjdxXQx1sGo4Hr89SbL9RFXRnhC00fwK7ORRXVxpO3IeRqc8rhSNeqwb6M3iqAerEkj9HZynkRr1r9mRRSzsW1mpgTBUD3aypxSKV0Io7TY1JL2qmmPNdW6ajK7myBk2CMVmQbkdCXd2UeRCXPV+8Hs4ZRYoqINXDk1eQ0U44BtUQTYFVQikVughU6lcNM5VkuT604+GSfESiEpfEQ8Qag0r9GqTjIgFGPQYeVEYTelnUkENANS679C7+66P1Okvh8qqg+ykSRQuvHpWumoxYVOyEhlZarBK6qacgCuU2/GlDtwHaZXx9pZg0G3COK3JDw/LADw3JpBUsRiirF4FwUrQyQ0w6zlgKnn3hUK3eNS8hytSw/MFDC44wI5ORiA3UiUWbQYduthpK+oiwxmEJVTR0stT6SsxjazG+YbO9IIDaj8psMXiSaxqjgfai6wwHKZx6MDJwGYm4yC4NeaQYJzE7mZbZUDMlQ6nrGBlAWVXDYvZkNDYiIakWeyS6mmVUCu42ULOshhFyGpFlI/quL2BVl4jp4ZIKKBSpaFFLPVhzZRlNheBEDTktWpAtG5sRh5OVDmy8HgIwJZSUAmqGJoOX0ouLvdSRS4pzZAr1ecU0tOFB1CPgIGX0oIJsNaTgEU1EBC4OVk0KFtA8DnZqg8nCopMQR891Pn6I/mIQ1VGJJ0KE0jgQBGCpN2IT2a1zRoi4JFRREjmEIMdpliEdNwSIa6jCgVoMh60RgT4cwomI1IWBb64BaqgkwJpHxVfR0WkTtG5BLgGxTA+pJsCDXrKg8s39XgnR9BXUA9TWk5FPERavoaG+CQkhqfD9YEL2dwa0xBOB1KMRYGVEom4xaAgjopqAGCfFipyEbC5TyOkhwOnHwYMHS54UqYQ+SlL7b10u4GMKnz7CbH3bZDnWjkyBZhdjqmnccBgQBFPVshmNdlM140Hu/qKLVjZFIPXoBFgZvairNxpCiahGlk3cYM/vmxtAcnoIcPpx8ODBXCRFKqGPktQ+sllkmZZRWACNCNBwUmdBextKJ0UMipEZY3OP67q9jyUfOg2i7zkFqRr+cKvMQAQalcwtIqOkJwm+FzWxH0g2dfpum5wcKnD9eBS1wtWuBreCboop8KEed1/umruxJyGgvoZmnNqKUm4TL/zp6KjHr1m2urHG2+0ZiaiAtNiaGYnmuNWbnEi6C5TarZ9dRfWQrerLKGq4Iqqhsl2TI28Xh3ulWupKoNTjEoFldKNvu2qIJEE2BUVI1KWZJByYYRgstJIofSioiSocuinIck1f3FXQhIYxjIACVVADmMlAI5HQgLmIHJM5unvQWs5oYazmsR1LHJ5occmJIVA0SGnQ18l1aoOCs5F1aiwxVAeFTsUhcgJ70KZNkGn26Erh1IORgcto01+X19BFimpkL27bpk6fchCm7uu0qDUNptKdstwC15u0SOpwMHkF9bC09XThYHwCA4vYMHkbmtiw0SjZZYKwJKMk87B5fGzi4REIyCYbNjKzrwgSf4uSXjyIQX42kEopNLvLeEzTkEATViIC2XReE0LiTIhpwkX6DCLX56jH96CCyx9pfU9lBBFJY6qiHpyhpoxsVPYTDdVMiFXsFMu5KEvscr1JzqKmobyCeaDSejr6OWgmfRsSUirnKBYlv9ETak1OtpbjeJnKYxKWbWh0vYfz8xhUPwVi0FMbSKXG65IFmTQehWlMEkgSCmnNUS28eki6ajJKKfIhaYimbWFkO1bWtlFCzZXcBuspNo54fefKM26THIgpSchBqMfFAqpiL81XIDk8YwomnVDrepOmS00EFtA8BgZeRxKziSZHKYudU1HZe+vcx8EaPQo8oGkPzMEH7ad5XCM7FZjsaQZtLoXUKQhZBblWYHO8abWOFOnISnvTeTMLKWEtzoosGMnx9GliduJT513TKbRl+kgNrlMhylpy3SSk/NPqJzVy9WaSSScen4o6uVQDOwxSJ52B9JtKRoWGNf4JJazDuDTeikCKTeO7YGPOzHeEOEBNmIAMPm7s3LQkkie2tCCZoaJxsPr6mqtWyqSdZgIb2hz5+pUmU+m1Kr+oRLw90Gjk0x2bVCnos7bqFIRHB2QGtR1TRJcTffpZV+HRYvPlbj2FJxQzbUes51TBVAHJA5gkvcopY8CMJKChgzMH0LjUOqrtfyyqaHIG22ZbsEZhpIAtJg2l9ZmTzZNg3YXpMjvoumRev21qB1HFvJKPOSeTGlG6jrAPeHlNB4MiiCo0ppkmhumCNFEa8yDaWFPkISH+YuEhC150npfr+9a15qt1Xe/Baw3VMaitNUx5mBHp374Lrih0mAs9HmcyadE5zFGf0td9ovQ9UE+akUsCjE7LZcFR99sqeDu7cyZVfX0JVusMbl6UVFOHgzQPia0QTCEO3cgZaIeXJJh7FzL6KCDVA5JXkNGHfutCQyEFwgmuiGxa1izxIpDNUDTLuDNZJl2qKT9/+9Ns/SirGKVCVVMjbZojBqcWpiamExl9CDgm6WYiGveoreEUzaW6w+DVWKxILT7m60J5TeNTncIpHtnRHsYZ66hJYKoqzXShfDJIl9vB1NDIB3Wq9jWKwlTFfbAapRGVqNMokOGBIaPVo4BUD0xeQZo7i3oCSZc8S45wAqtneKvoWhO+IQJphsLDSslCPZ+kI4uAa+RQDeEBIvNVTVeVpq6kvJJmL3Ea5CMrMPaZpeuzsljpSKcD162Iylry5Zd7HEq72KpRT0u6q8KBcBSw9dg2dSITjUI8Msmod7OuhTe0ZApeDa5TyspaclVPvxymVfZqvDL9JjwjFpV8OmaTwtkNTMdiXpSaLKxR+tDXteRtMUmYo21dTZCiJQpp7JDqNTNdUKRtezKySx5lixB8y75Udr3I0zoFmeYegK6CeuTaevKIXOGdNW1Qrg79uFdSpO/IXYtvwJmJqaxrP3AVChsS215dsWxRQnXjpPqmyqRfxTPnyZRDq4esrqTMkMk/5qchqAb7yKnFTCuS5UpkWoGMK88YK86vb1sEOLQTpDksh7Jf3+LHZ1eg+/DrWwSSwHXdgOxzsYRZ1Rf0rzRvanZfXl2uQYLV+f+4fP3qeZXl1W+v7+t6/be3byuCunqzSpOyqIrb+k1SrN6CZfH2/U8//fvbd+/erlocbxNmX/cr19uhJSRbaLa5Upz0egnP0rKqP4Aa3IAKUfxkuRLALlegrL/e/BMmNYlxfeZ0ya8DlfsG+2fh6EMjceZwGbaP+lr4dxdAiVvEvYZvcNfe6I7GNhQ9Q4PECoGMFypOr8TqCMElEh5Q9qLTdX+xROQosmaVb/7m+U9dG//L1m+/2GP4AKnHVmlETIEDvrRaZ+ClzaTLYWSLnHF2K70EZVdij3FRfYC3oMlqjvibzyKuX99yHMDz2luB2TgNwHOwJX9LHxQMY3QJSi+Ot8IzDutTjfOIuCJ7nJ0KwxwqCoRQaI/3HNT3LLL2iz0G/CApP8z+m4MgodnnBIh8mVLhfE5XsN2P01g2X7dG8DYn7SGypoolsBAvddVxJKpv7/cm5fCwJfYYv1VDLMO3MmNx8mXu/RRQBuI7TnOAXxORoezL7LF2F3VEVmcK3DRALkjf5qs9JpwHMq8E/UZ9dllIv+A04+wiSj654LgqAWqZbD1ZTFSBA9etl/ih8K/5tzrheI4p2RpNw7zlG2lhD1vOp1I552WKtvE1J3Sbrw7iURYrTjTIFzcM4vK2+WqP6apgceC/XWqLvei/2WO5gOvshe/I8NEZj9glpsBBLZ5w2vDEpfZxwkk0+eBg4DVkg8kZdv1Hh34US45n2y8OdCihQkmxJQ5jw4tK6xNjRrf57IZL0jXqs8NyQ70Dz2sLvsyph0v85h3Ishehk1TJ1ih5eqhhWp7G5KHm9dXH0fPtoiJOvYcXQNRETIE9vo/kSRkaUfvFYfUqSg5D+8XJQC5Fo27z1WWLWVVPRcnN0uarw1zl4CaDlxU/X5vPbhuA1qOCtNoScWMKskoYrAxka+S28//3b0eGCC6DykNyDfVHMtHoRi9fqhpKrCQlkMuuJMmaJVzSgUyLZcXvURRAHu20zzSqm6DK3bGzz4gqG5GAuewwSQ8JLrxv69zm/HRrwFy8Nnm6albE0Xq0atMIs/4bsdwBO3jWY5eU22Nva/QvuaoJpYPbHnVkDB0O0lAG7D5KyxnlOHps0wEeD1viwFf9QARGogs88JFQCyXSodTFw3JewkuYQfyQMe9jYYq2l88jM3YMTp7OS0Ia5pFQn13FQIFRKHQ5jmlPP/mTmP6rk52fiEvA5qs7pnNYJmhCSZyFDCddPuVRyOU92i4oD2DFUidnMVKtdPwV5zTmSh12TekSXt03q5tc2NFxRfY4v6dVesOfFg8fHeSkucnS6p5XcdTnOc+c43t8ds7RPdziiKDK/RX4xGobj0Shuvsil91YiUZGnhciUVjSdUEB4yyQi3yZPqZLiY9NCrAnqrPJMiViodDBuluu0rx7ipSz7JgSd/6CK6SPRCtUUuxE36ev+Ue0xz4Xlk2+zGGfB2vwH/Dlqb33xOzwmBI3jMrZEgrd8F6lNb8oUZ8dZj7LiqfBY0FyMnKjl0M4tLBel8UjXF4A/HDnZcOdhEmKHWS1qA3o5RDuvb8qapDJ6SOF8BqDuhElkPPx0lVxlHC2EVtij/FTukpr3CGylnE9FgodevrQcB3EH1w8NnlzC5K6KWGJ1pv6CzL7eENJBWPfyu+Im1ic7ReXne/mbRR227v57tCfro5sSefLXI5Y/2jSEn6t73Euh/6eK3vWKoNwbmFjDMjxM+UO8tsgvQXwe6LIDDhaLjlsvCwboZ3COIekLlwc5/DdPQ6In1n6u4NxnmeteFIZWxgTXVLu5DFtxenrrQK/HMKdGqfP67QkEY8fwEslpwwP494KudFFMMhkSw3lsHEFFZ8HiNm8isUO2pSpyfdeLHXqNT4NPLorIRQtR7HU7ehxqHiFw+2FM0iu2EUuh+szvGBSBS76q6t08pJk8BPM7/h4WjmEbwvnsEwLYR5VMB6tEAODoBE0sQzChfL4SKk9qxWUIlPkgvMM8UF/VMUjZctc9kPLFEsyyPraJ/f4Pg2/N1JBOcUXgufTZ7QPEuMLNwUu+E4rgbbkk0OQF3g+QZuyuwKfMHKxXmyRk02G9mOL/BFJLKrcnykKRpkcyClKPnmQ+3u5Imd3G6l/9AjSDNykmYBeDeXXknwQcgins1INdrHUYTdQPLVj7+4/85MrK3faJaW3L8QbcVaUff+OIdqbCjslNaDDUQRIHgrsRcV32YSjCL7Qcb+tTkwjbLztc9io2yTe4O4QXD7vcgjXFtqDcF0LPIQj3eCyqyoEEkqKnaQOrzvHzctxU9fCXTOh1Bnzj7S6z9Kq1qDnQRwo0+qaDCJ2P0f7MPFQQA7hcDiAdkOkaprw1wWYEqfQah6VM46v2VKCZvPV2Vd5IsTcMQUOaxDO5Q8ySe/YEj+MlzUo8XVViDOsi1GwWki/Fk/zpVV7PJwDh3XuxdMcR4nIGE0G4BwhxGCBMvbRAjrHDFm0pwN02onROTm5bZguXacaJ1qvYSVNay4ZhxHYaSzDI+lH63WW8nsFKYA9/h8wvbvnBL3/5kAdyT7PfWf3I13ySLpPDvSSjOej83iGNUKvXjRgHm3pFIsSaM5j8qiH+DCDQsTT8HE3j+41Yx0eBRH8Z1yZe1gPTmogXrNmy1ws/wp2y5nEUSGWemD+DEHVlLBNhy7FzkB4tCALVBUKPfDiH1rcNIBr4Bc5vBS9fUKhc0BZ+8K93EmkAPFsA9NA38QA4T2KzSOD2pHQYFsTZDMs3GH31vt4O/cwG3XVceJs8L9h0SR9j6VnD1yZk4ddFXnIFW1LSGNfW6Z++DIXf2Fnx4g7G6bEHmNnrwj46O/Op6PVSdGscdD/UrjpKpY72A4KrH7Y+lkgR//E+FfxKwvhGHHQnrG1z4sJEQd04dZovc5ljt1SIWqv97x7qD111W1VeziaTcSy+epifMbNInVc1GizrsQqKXZxc6ZAcG2STy7uunauVbF3svJDmJweVxu2qjoVE0sdMKckCElAufns0ss7eJn+S7j/2H/1DAusror2Eo4cvwnWvf9fZcchQqGju/kC5Hf8ksEUzB2AOrbja3cCALfRzbLfdy7U1CO337+n8OmzYCUKhVtjcHXaM/AuR4vE5y6HquY4xlbXnCxFGFfkcmLimglPqXZgIU8PxhS4RLgcsnqJ5X6XneLICYPL/+rT1FIT8eJqTCPOf6GZ2afXuSLCU8BKEAZ4+rRYxmGtvmmV+8MPGzWQiyaDqnQnFuBOYQByjxtT4LB3TrMMe6X7/CXc1lkodbHJuxBMOWpJsYsTrk27ga1ZNrsL74xTw/m0dq68EiGD8GqBTSCkaoWD8mmpnwDp3k4D5jdLRBnopmcAcHDsPtclELMUU5+3RyVTZ6qBupjC5KOEtdW31feI6hblR/hMspnwnnGmyHlZ35Lc8/OanZH84Sw2H7e4EcP2m54qn6SfN3JRnUGAbdZ+ZRFuVPDF+8+t9FXRKAxLI/RnWj2W7Wdcuv+SiyBM2YGBvRg4HucGsuwcvBpuBsQ+OKSpoDqcU8EcDuj0uA7HaLt3jHY4PtqX46O54mo6aeleLYlinLWZVfzNMlX97TfIJKrYWQ23oxdjeOnvDthIxh4OU/fN5YJEtr5tsv+CFbm3zt+U4Aqd8X4p1GiHsvgiY+DuRYV/f739b0IQGSsz/90nmIxFMRWv980KO13qu8NGYX20XKKFhVt2qM8um44+ORO/29h8/3MqdkcdfJwVd32yOXe+1NYeiSfb5kSFxxQ4nI+gIZwXlZj8lvo++ywxK12n6XIsMoGLphbVqOtn275iFd0U2uP9AapuNCxK+vvs8/gFPlX+0qatPa60iYYKWzKf/GKiyNK4099nn/fzIsu+F9iMuoAJfoDFQ26NKEaSV9TuUV49iVYAW7J1W5TAyH/F6+EOxtrUVpr4UCdb4nRVKdI7RDFeeor3llFf56woV0CSKJkvdcd8CfhHpNkSB0dW9ATBsRNfHd3eplkq8agyBQ74cJoi3kXXfZvefbSo2vid/mk0jmJcoUPUjCKIyC9a6BNAFqp8X8UVzbkbwl35VNylufSanljqhrlPb6VELgBszVrVzU7gqXw/x+5LlbLmOCvVWVpWtcj61Ge3mRdRbb5Ou0YhdbwG+Ytg0bYfXfBIY+n84vNqxO/o22OaJ4KOFgod+iikGjtxTS3Wst07fl3rvzpjei/F9N4F0z/SNd7ug0y8m8sVOVgF90UOZbmwmQIH+emT+HLys/k8hZafy64nMhB8o7eVJB+zXlVzWw/TyfFm1UX4Sk4+N0WuOOV5Pfgyh3OQp+ITrGu0kawKUfzEUgfM9yWEOtyScoc5atDmKk2kmPkyj1uD34H8aequZKbj2ejHqqGH0jPpI/axzjCtxOLyUE4mBNuqo2IH/MTnzsVdjkjdppnmT67YIpe49mketx3jSdi5pI22VwOFjUblI2v6+mM54uNtDCKYFjc3JXxMgSi3bMmuLVEzMXd/1hjG1z0Wz3NcedVxuPkTyO8anHiew0J/nzaM5rhYcpva9otr5GHrlpU+6DUUOeDsghm6uieiJ1IO4aALNs9cqRtRAjnMEbjjiNJ+cXJnbHXesD9nFPBYEaU7557oVVeYDu+xeOhwddVtNf77Hp80GUlDIF0KhkKH48s8/aOBl1CyK+eKHFxvGbhbrFB/cAINceiSYoegy5pzP5MP88vgn9oma++GXtYlibioiqYM3XfIMPrIuRWaXbDb+t6L4sSWuGOUXCPmilwOgs/KYnWeNXf865NsidNBfNEkglxRn7dGCvooqjDO77F4xrzJq+4Ch8fYmZDXqzmDt/3ksHo1GbfEtF/mWQu23ZI/7OR07Wyfxf/n3JHNdUV/iPoMvPw14PEMhlVVHmddwC3KAmCn3jP1j/h+L2pe9riiPbbNMeHDuc+T73bTEgnnvDZAsdOastjFoWgmq2KUFMFkwf+9gVWNrHMsWRJ7gC2e87By2y2rHT3qp49g41wrwJgCrhbIq2+rn0/9XrTva9GKQHavMHZUqRM3MfsuXzZ1MHsfZ07mWxqB3paMHX4+1zoPy1Va4ZCE9s5T4JrPYfNZ/40otlUG4147GNK3sfu04evWcNDpc40vLWVHTX2PGk0TEpgQg5t0mD04yw3dOFwW97p/jLD6niqLJabJbcrbSbJyd+ydijc1IgGzb+srntir4gFyTgP6uyO2oySBVaXCyZQ6WOQ4rglZnSp9ISvfGmk/K8pmFR5IM6DxkGNN3XGE9qpYpwmPYvg4l/CL17td73UvzuXZQs634kbbziVuInxJ2CKCbBA8vsKhqDyOdJAWeRTDx9mkA5NA9rokU+B8Y0B6WcDRudhqT96v2H21x4RfqOHQdJ9cHGBVLcvMQn93x6aaSVm5O3aSdVuKty056Cw3nQXClZWvnppURf1eFs1aqqeGkl2+lvFlWHt4zdJ/nkNJYdGUmk9MwUFhWfLMId/mSGYbUQERzDaCx1cdKipvq79r+y+WbRN379l5DZmLMIFpp9NdVhT1xhET0pgsIwBT4IhPTCNFfXY5ipS8B+TxBlDcnVd35b3zIQg5H4VSl+Pn9hhLgVpS7Dgv+NphI+Dlitz7K0crljq4ENu3q+SIhUJnvO0VZ6V/UgXkznEnTVnCPHkRbxHIIVxaaOtdAD6egC1x7zN7YKmSGRHKYWcPapnGoT678nVzU+OUzYs8yVDHZOzNQ3i2gC8461sYINxbIGmnh+cGdWORQwa2qB2bHNK1xU4laMbGQ3i2oBkLD+HZAqoryp4cwlM/IT2fYhsTZGcQSklmAR6jbSkxLcBjtC0lswW4U/AGriJcLe2/OvKHnOt8OK2XNgm6TZFr78Ss9/R3l2syt02+hEvZM6N8mQvWJ1Auz4s0r6sfsIRobvnAPgWIwzp3D5OHoqmP6rpMb5oaKvd5esiAFqv/XGWGploQ99VbFfIpK3cIRYqcp3OwvNcKi3ztHGCJ9wVI6aKNL1IHJ4hFZCaGHtIleKVciqFem69umEQTdPPVEZNkzH4j/AyqB7jUU1MF49bnk8fH92KP269umE6f12lJgl4+F3l9LyIVAHzx/xcEEirz5X4c/CEtYVJ/gDcpf7tHBeTighqqdXlnPxaZxB2lggppScZBaiivlo5B/iButaQA3vhFYZUC+OFfnKhR4zIvrH3WQRXmodwL++IG8O5RvtB9XSA2iTRztRzCQdIaZDOW6b+ImF6VIK9Agn8KC5sGLrw1kUn1kOEtXsCq4fOKm2BdtOMaZ1jQ0FMOEdKCbERqKKdIiMHK0wxIA+ZyeaVM7kEFlV5YKYDLviqV371gCtw9fl2OQKm/ry9zx4o3XDjhcVO3a7TWS2ddyeXAJ07e9104lqG3URdwBdJcuFmhALFv4yOovsCnbrP+pcARr4kk35sGbGuOg/oN97cK7Z0+plUd/hC6BKVPTmE7NOMcHg0eSF5mqO+ODgrp4YTjUrFreZ9+T2/J/ikic0lQ+jCXHZpxmKtvm8dCf3fQoBVc/kjreymTCYVueCWpYqjPfwLGjcOrAfw52RX+zhyjuEXybpgayp37ZWdwfJnD7kDirXX30i6qvgfkOiIQbCYpgPvY0cZ0LdsrycpdLJ8kXafY1BBsSq7IA6fkxhJf5mAXwxxb/aLpS313xSbpIFPg4CGEVSXckh8+unDThuzE+BO5SQKwx1p10BgRwo88U2Zp6o4YhoQbVIQObYqmtyfPIz7X/fcGECqzqDZfHQyPPK3PyzSRnwyLpR6Ypee+YqkLJVX99e2rup++fZQeOHscK/d7oHZ5lQ5aAeLbhpQMChAHk8F4TBp6PKo5FvU+Dv1QPOVZAZaS/GVckctC1VdVmj0SAJdI2QTmFexxCMH6YrFD35H+/AHTu3v+FULqu0MIZJMvM4hXGS74kfrurF9P8I1XmYZtC7Zref5CEmQFL88Yje/yLK875vIcvpR+EbIfffFLeHRVDE/kyiLp6eI9thRpD20cv5GI0cdtZIVlHE6NG+vdDoHTSt03VyzHqAS/9iJBNhS5+Z1ku3n6+/R7uZ2TIHyK1CZiDZGaHovnq1vyqtutya9KkDyggckOLfkyB6w4DFFmpzAFjieLUH4Eypc5nROmj7CUvxYsFP4JpCfcWUFjCpCiKV0WQ5uSc/D+u4cDRC6bzv5jd0fDbDZM3ZT5BfyDZMAMNF4oVF5mi7b+SGwU6ZJbbAaKa0jF83tdQFAV+VlRtrPF+9q5Qhe8ZNoh2bvzLg6h0BuvOpJDC+g+b/Lk82KpC6eC29svYtJo+ruD12e5SnNpiB9b4kJpSnzlVwcVILsZ2jPbol+QP0+Q8o2x8LPYvBZ/E4rt1tznoOxMF/GmK13i6p+TYWRLXIydDY1lR9Ky8tk27RHPa8bwTfejQiwMS7gkBxXycbMQc6y2B33X883lGiZDZOJm7sPSCetQ++QWdsM3jlqMKX7ykXxdSwOyDbCOt3HO0gwJnxAUy5c5qdCnr3lHBtX7Akyxs1d8d57f0E1WoEGhwexjXDihG8nQkPbBjv8nT5q0H5w4Bg9G474dSqG1a9zQ6V/ysHSElR3h8V/GpZXnnul55+U7KFNkxA6ScFKsbtIchK8aZvz+8+iEdKQV5KHhFgv8wSEit+YfLm2/OJwwgry5BQm+nVeiba30NrAKxjEcTBIH5hSGA/M7/vJ4/80ey490ySPpPjncn5IchH10D9YBFSREkB0XC4UeePEPLW4awMEKr6r0Lke735RwxIJ/gFFW7n6Gh1OUCteXuDL3jTcOkuSx8mUugWeyp4A2X/fHNYI0aPIg915wRY67ua9N/fWWoOgSO4qbOhFk21e/UZa8eOvcLjocukrKTZas3CG8AT7X6JP46Bj13e3ixB9NWsruS/TfPcT5pMjrsshkXl4VzP5vCOLKXARhm1zKjrIUCGqTfJp0Q+mZk3m7FDe5GTqK9m7vnEZT4Qp0o+pxvg8KFSwD2zVuRnWL8vKPBpSwuri74Q5Y+ELHbdDR8p9NVYvH60Khw6aGbD5UiMVSl7XsvISXMIOJ5FYFUzTFWqPCSMRBtjAyBS43PvIHfLwntV+Ewj0M/rpK0U4f82OYMhzQeGg+Td3tN1djZ0jHT8GqDuH7sjmOoV38OnOdI7QvGAQdG+CfPqcE8npzu4qVEQAl5y4gHxyYvspOc3CT8QsF/d0BG0yaEgp9oj47OOzER4U+Or8oVNwVg0uLWxHYojlXwo/1Kjsuli98F+nvLnZQXiPG7v1uX2D9VJQPvDkkh9kuBdB9iqAHOjBfdaCsPo5WaJvncWy+umIStQz9fbwFci476B6uINnRIPUVaAvRqHzsIX39cdiHNMr5xNpPUy5MkjxOzjmcdoThjkHysMiJt5tOZBnGegqkHkxojWmk49JYkb0RTf24MbI7d9WMPhu+gitktIT6YmUYPVjVDs222uL4Rd1zwJ9Ub77usWf/BE3UXVG+xOEmHpsHJ5lRHLho67ioj9WKwkQcsoDQsQML7Q4L/Q5zWKZJpCNGHptPVk0jim3fRP4HfGkfm2Uwbb46YRKQHPYUUj4+SrILmBSh6WAHNB6cq6m77Swb1+8xjkN/US2WmRCG1n7bGjb8VkZhwwGNBxtq6v652PAya7j7U+2XOWIfVS+j6V9Em8uZnNzDZZPBK1A9BDqTKUw+zmRt9W21Ji+R9OV8wPDw0cEvKbzx5vq+m/SkyueYqi7WX/PTsixKwQ7YFLhIA0mAUNaCA4gtccN4mi+l+Prvjv1rkgRWlbyHVJljH0UaUp+3RgMMLysGWlM9Gh9rSl13HMFXPHzu9eB5rMd8ZMuPevGZyzsKQdWUsI3ACnSL0qh8/KH6+lu7YpAXo9Dm7gmbbayGZYvscV7gO14sru7THnszuvn/kKImq+AjJR6bP0NqUBx4cs958qQpS5gnoclxOzQ+GXHVVbeV+foei8+vsCVOrMe/t+KeUx+z16cCFQqPsDJFrp6Ss6JcgbpO+XQfYqlLdO8qRcbGVdGFBnLRvVyhw6lyc5Ol1T1v21Cf54wM26aMQcpRF/g1Q7QpIaFa7LDZoq1RYYgFm1W8UA0BnYdOs8AxjnKj2xTfixFL5wqyIATiUQ0fHbwQxTpNeDzDxz0O+jgvySsaXSLy0LNVGpfX0aoewUi8HikU6awsViru5stcOFOFky1xku1/wqQWRLr96HbVVrxk63q9Fkiu1gLHI5PO53D80j63zSMUir1wDy+kKdFTEHusMYhnBhV8KgJjsClEXueQmtoj+c42TUqvbovFs8UeypL+euT73WXmxJijMSg5kwhiUjmGkVa0Ebwa4dtd13Oa2YIWV2uQ3gVa/z0WryBFVdVtdWTEsijwtSYWSftlzq1xqIthNh8xsaEjxXGzyLw8xAYM28rax0lCnu3tDsh4BpAU76LYxAsBaamRkAcxhWAZrmx+Px9+lxnfLgBZxufCYkocbMS6Bsk9loF3Z6kYGCYp9sH9Xo/7fQjun/W4f1bjnknVfYFP1SdY17CM57yT4/RQfLaIRtJ/0tZFh54OzlH2JSI/XizAdGbAjgQIn9zD5KFoYiXTEtD5mLNmHNu6+G9zMjnyth7eO3X3UeS41VAu/b4Cz6fPUCADU+BAVfDc38MSXJpskdPytdWp9eIZXtuiWCIkWJPjjKFiJk2qJrQu+PBkAFPqsUOysjDpne3dxqQp8Rso5+AFtxrr/WkZVr83qC0xjSN3fPM8Nlm5PfZYr/junAudJ1tcbovAZpPp9Zckg7JU9UyBI75zWKbFUlgh2CKX0+EaZKQ25xRhCly2OWj65G85MyVzuJZihZsv8rROQSYVcL5sj+WcxPGl/yL5L/FAQtcVHp/PimKBYxxZP412u8/vDpwSGwm2lN+15ss8sCoQuuOS3MFmCraG6y8gPn/oc76Grm00Lq+VTY9gHF4/73MjveM3ApvvHtjeK7C932sb/aQsqgrtRrIoHMVj89kLG1HsL1fNtpIGRkL5RUBNGPmEmvoEH2EmLGrUdxfXZVHWfZAn566kSxxCL5sskyJkChxMxHV3oMsRbfN5rsgu/GypkEl3+Oiyeb6FZQlLARdTsF93Es5K/Co9viLExeYOn10i7Eiu3I+g4jeHdMHWqCj0oU6Dk+a2SHxSHKhqbuvJz58op9DmzCV0B0id3vhkxdXV3lY+2Tl7ldxpCZxmjMJnguX1xpnaNs5VnGD6u4vLKMmaJVzklymqDbiNuFjqdNABqgoH46KCWn7cIQK42AttZd5g6L86uBrTmr8R2n2aMlLtM6xBF73MGWdsiRtG5ZtXQqEbXgnNqM/zx79dwBypp6PqR7q8gzVvCLJlDseCpMY/ipwbOv3dFduPEqw700qGlCl2xY12HU+SmRIKXfEeF1j58wLNlzkd46JFmn/sZfPVUZbx49eiOJOv0y9XR1VVJCl5sVlYs9BSSmJGr4/W6yzFnE4ZLJqFSl+PX556aJURgtbIpbj6Kxu4viyaUvpcktVqN6CWLXuYmENfArt5BUos3p7d7Inl2M1f30rnO4QlBv+bE0NsaqnYQeFGsyJzj32reWHoZBgndGimZgQS4Ib+RMvqfbG8voBVXabYSDrBozRpCJvawl6WqdMC2igLc1uBbMI1EIFZLLocqD5a6k3NNL3r7Zq6tqHLSSNCi8lnWpjeP2imrYg1cPoHhBEmXtK5sInu8Mw20cdplqHPg/vWYrb5Kqopd5lrFmcgTTlkMaedQx3GmM4dQ4b8MsXz+WpRfWmy7LfXtyCroOPog5nnHJartML5zNpMw9d0VmctD5lqCnkvOHga3IK99M0Fzp6APAKfGTocuKZQuGbTN9asoqqh0jaOrCFHv40rzb6wwAV8AuXyvEjzuuoCYq+/VXD5I63vO2ewLqLEWFmMIRGqWPCFsaHAGWBxReATc4e3cZEykSGewulNdRcLR6gTw8ThkAbyEY8tpsbhcW8jA5nHH27mdBv37v8rcKe3bWTgYiIvUriBsbFiRMShpouXS8K6c1H8JQhT9BWq97VihzhIc1jyIIMzt/sy/F31H/CEgjv4uVjCrNrUw08VrAAZUbUGCdnBL+FZWlY4NBzcgAq2IK9fob4/pkjr9UduncPlj+wka1Nk9QCfQZ7ewqq+Kh5g/tvr9z+9e//61VGWggrHDWS3r189r7K8+ltC5BbkeVGTof/2+r6u1397+7YiLVZvVmlSFlVxW79JitVbsCzeIlw/v3337i1crt7y1Tu0Vlh++vceS1UtM3rGqYPTbp77t9e/5SnHG7/+BxQ4rZ/hC3j7SqVHfn3LV/xVwlu4C7+9TjFliTSSF6ZIdCnA97pzDAVJz16/wuoGXwodVM5bLfr2tLRtIH8EZXIPkCHyGTy3dyB+e/3LT844mUM1a9R1KYZgCJjZNLVj4KbsuI7kjqPHSeNuQZPVPZKb1ISEPmAxMCBs4LLNtTFkbdgTTqTGxrXEi/ffFvkSPv/2+v8hNf/2avGf10zlf3tFJvFvr3569f86d6PTjpjFaAb24YT26T0Nk7776SdnNu1TdLjRp62lIoyVeCAV37eKxnKT5qB8+W8r8PzfXTEZVc77n9x1zud0BdvHZYIQW0vih+Ipzwqw3BPh64fTpgVpG2ry9I8GpgTlbYqVoprR3OUMbZ/6Rkk4qq2uVPedQtPPvhd/9viOCYvH4fkufkHGoV74sDznEjnyQnb6XHdvO8TAhnY88ClkPhfVVQlQf4glSaGxUHND9PA1hWTQeu982JSJR257g7/UKSZ+cJfMK5SPbbAnSmkT/eK/BuPkzAZD0X2xwUgtDGfntf2qiN7Vq2KMjl7Adfbi1lsHtGP0+OQkNsbjJImNcsgPGNlmbAMxI+h29r4Hrw1dLW28IG6iOPzkGyMxdsdqEeQSGLrZ12ztsA0Ik50w+haS7umerBNdIjqd4f/LL+5mZesPMO9WWNw2vPaxqPRC7tPf86KsQyQJGeSlzKQM79gQkB4ZcZtf+rIK2j3gfUjrr0HKjXAgqjWC3HUJTtpwqz0RPGZM9O0LzUT/5ScPK+b0mdzCYIInFssq9vo7NNOGrplaeO+xFPdNsOF3I4ylGwNpD+/tyA70mV/anL0sebpqVmR1O1qRJaRfdmGSrkCGXf/oV0V8+O/+ilgOe2rRrLt2Hw1+opZa7H38ox+l7LVAezJ03ORLxMFIXoa8cWhHX/OnsTurGDbjczel6LpBhhSTks+f59nEg2G4uPx28RcZnr32hJ/O+9QQrsw0VAzipJae3p3gqgd1pT+CDOHC/p7AWBp1uF4BywRNOMnP4G+g2VgUzhttnItCc0zp65NFbE5lZw4Y9Md0Ca/um9VNTm1tfBB9R/N5kwVNAPUyZoCTPtLpalQPhN65G1Ul75cixi2ErYTnoERdINnNhiRZYRg7TkfqN31Ml47uk3C1Y+fbGUXvIOjYOI+WqzQfHhWLgLBnnO6RmLCpxvd2v+Yf0U7wPHBxYS+Wx11khPvlEchI3TCP29mjLCuehu01fEzhU9ACdrRel8UjXOLX0fO7y2YVMt9fijoqvh4ZSaTKDTawf7FQdkcBV8VRErT+CwkE/FFdPjTRRQTkzS1I6qaEJVoR6i/I4tnYB7Fa+R2xTGyci+r39LY+AWWQVdTjCF9Pu8cfvtb3sDxnrkv79ax/TGJYmvXq0edU6qjBG6c6TfBSfbRcck0GdX9RDQE6ESJLwqbmW561QtijCxoZInorJV9vBXxem8EOyenzOi3JDvYDeFFhdAmfIUmgCcJw7v4IqkuALAcYY1ZZTO7beb5+SFQdGhg+dzm6KyFc8QEvHgc4A6L2ueYo4TxDjvYwfdIhYRKsh+g7Gt/5kF49AkYmvXqIrw/7+Ic3UEN02Rma1P7AIMgoW7a3UkDWYzu5xxcdfBxBtqFcm1eEQihwWgWRkHt7KOAABOQ4z3f+iEQMIetPb4IsvbpIHiK59bCLheA7egQpqptmFM4At420j16rV5pHG++n4qkda/cQc9g0IBs+vX0h+96zouz7dwzRtigE7TFIHkg6KHzdJvAgAm/RML5FS0P2RcSASSHLV3e0F2NiWnztAV4MfGTgcNmhogKGopmiiM0x8HHzctzU9Wav7ikyGPpHWt1naVWHI+xkOYOIL5FiZlyoPvhOkL1NUKVJmF+YRhB96fiaLcdtoNtmnFCxONHbuFwjPCDzHojVWQbVxvCQy1W6ghGC4mjcp/kyIube1XSa17Csgnmx014MVjgyA3UabtI20X7hKoWt7AbpfLR4wqqmnk1c4dsNeCkZlVlR//vTwarLUhYyivYZutGozW5ToqP/kS5HxP5xXNoMq1JsnTMgjqtwYh1AxjkN7V+2olDEvFy1Laek0pGnj7B8wbPq7npha4c4Xuir3D7XTenaIf04BhXsltdgR8GA6zMEVVNCck1dH1bo3d1xY/KGZvAPtikPj2Qbf9NmwJW4xUKPArrwHljigKsYbhoGISZDTHyX90Xr+QGbcPGIEWhDbNF+xDsYowB87ir3NAr3i3+rYJwgK3XIVnTDoW/KX4NYutXoNy3DbIfOEomAqTvjqk6KZo0jjZdBsyZiUXlEfEP4yPExWQXDGHWDBy/dDn51a8UjT+29v4rHJ/wIRwq5IrbilshRLsdFjSyw6DFNOO9QbEHp2S5OYNMhHIkJD4xzZnOekgATj+DxvmKIgY8j1C7Tf8GQETDBWdVV0d4g4DF7+XA7HF/ZUwQbU8cu6g/Z8BcgvzP47P1CuCPFAEb2SW1h5NZO+Cz2LUJcRkFyz/R7Cp8+S2w2Cw1sH3Xeqq49MYa60cRMyGOVL+ovHhHesFDl5mFQ/+wTcnhIrDNHYp3uoHDPRGreG3Xj2WQjrjXOTrBut79HSRr7kXm47oeaoTPHk/aiyaA6+YDX6nC5hqM7qLpHLoZcA0ExZH04Xwxkm6eArgo24UKAD36D9FwZUe41VRRiOhtFbOQ9hRUbpUDsnZEfROHT57oEdJbOMexK+gBuT1Sa0SHmkQH5pMiK8iN87t6Vjot8+5Igu5pSe+arndmWUnjJLNrf1AzqwKI6Q1vuZnN1J0LE8cgcSF88O3BhDC6kKereB7b2n4Yb95ANRzm4in5uQdH9cHIT9+TmcNpi7N3unbYcDiN2+zDCPoyif4uwTVi/J4tSPyoPE3moGWaUrJnH2+LJ/KLqM08EBRLtKpvuGX/qn99wDw40J/T1yZJqTLfsgdQqSXAA3rOiXIHADFM9rkuwedwpVj+jJ1yKdPX66PY2zdJg85jc0Q2yncZe8RdVe4bQp0YPiQDRZELWdjmMhT6BqrZbZ3zi3GKuELinn4q7NI8UVYvx9bfALVC6HM/Qj3O6WQ4CgpB9jeytULfuiBjs+2O9LPfMtx+rMnn7MtqZImbSaMika7Dny1SrNcgjPQ8iPX60sbL7ikEygtDjLLuPaZ54+B+46iE9OaESHgSmYSTy9C4msvdRkP0jXZ8XFU6U4xfJJrGu7oscyhPA+aXF7BNlxWFt//XPflPTSsGeaE+jI/idhyOY+OaqbmkNsmsJohi39a6eik8QE2tRFcarOO9d+e7qvoTQHv/PrviRiMAyTTjcQRkrv4Mg63mr0k17+z2thZ59COTPIvvbcAgU7cHnuxxRr82kFuQZn+iVmwgvw9hzN21U7Qlzj2flzriu3tyU8DEFMuEKdR3MqNCt+fQ4K+6wPbsnLPoJ5HcNTlTozKObmkFMKj1aFrS0nSPG5vFKh4Pd1scaJbl5h+tEk5/CTqQ3+cNjobwCd3GCVLfzHv0h3CL+wfwUW9xeveyJnh1lLe5pdNJk5MKR/sTY/ViQnCtewhE2q2cZuFusUNfxFTgjbdyjky/q/bpRO65N0waIX9Y4p/UFrIqm3BsTfHb7pqenkcl98gP1yKW3B/xMH5xDvFidZ81dqspFa3moXDSJmu0Djo6+wKdqj14snJ1DI1rg5NWqOKhwBpo4mKJo4O00bv9M+5TtsF8PG4werfU9miLLjvLqaW9C8PCAPILWSa3o/jw/ldi/C/S9qMOeLhn7DhYi2p5wzezLfDzuaaPWOp0X50Q/yhIdLc8TWVZ/b2BVIysWy8g2HDBupwUy/rEnfca1J7rAvBf85Rd3gznS009RgoERko7vMQ4F21tZuMHhwH24LuGfkWJ11VMXYjjBcpVW+Nz1AiZY0+4v84esQ6MI05B3IAyx9VyfPmOqguyoqe8xXRNiDOzVvM9+sypeJGw/Wwvqyk1EtJ3mjIz9K+atq+IBxtlGEnRHSQKrKh5SEv+BJjgoSZO10J0VZbPao7P8q2KdJu7i1VULyywyt3BbvWJqZy+c78LFmCmuThL5INyxJwJCBuTOol21HRcQPI+45cD8cW3srt536JGq6kuriYOcUThdbhACfPEG9yLwBYMOi2LKPbGRPGqBm/Xd1ED7pHx+L4tm7amBurrjhoR6vVIT+YTiS7fqBMlyDIWCZU9mVB3Uyigezt1UT0Qs90RH7YR62Dd2m8Op3hJvP5iWjCXiBU6CL2p+FX3K3t3YP7BJCkKTHAR2hktREJzjIOwSN6mJ7y81fE88hxUDV5fjOiKq9t5h5PzpQxaxpixhnrxEuyTeI7wAm+NbQ2b2v3pLJXvcFUbt76COrczQpr1GspAt8iRDXbUkx1/8NHHfGL41OEVjV7ix4c2AiUbINDrNSDvdMM0Iu8YmHRlqyEFY3Rtj1BhS/CleG0B2BuHYNFW3PDaB1S2PTe0Of5yrZYRPRmfE4UXiUVshqmO0JpCl3iBrZznykyQX8AmUy/MCLXDVD1hCxFqBeSXvYfJQNPVRXZfpTVPD2LtGoYHqP1eR8iJ1RoAiws819ixOGr3B6l9HGSOJiMPbGPx0TAmRHjlB88/aKV7TjrDQEUJhE4G7FM0wFcYXmEe8eoBLFemCe3ry+BgnWxNGdvq8Tkuyc/pc5PX9CHj/C4I4Y6f58kOKlFv9ARI+DInXHNB0GR4/Ftky0lyJyCMyAoX8GOQP0bZSHN5oIkbjXZzERtnnyIqMdnEDIi1InYYmRkHMfK44MAcJxL/aR9Txe4cgYd9lHwV9NHZTNXABq0ZMp+upjdb4wnR84oiII/Ya7TsHmyh+188bXLeCsd2p5yCNFWzOZcCKQtMOJd6r4ASXTesyHMXXNXZi4u090KC3CxdwBdKcClL3iAr/CPALt93m9kuB3fIJk5Eo4lFJvyv8ViHb/mOKpmtvEi/O/kYnqereelctLCBrghwmv6e3xNjfQ9bph+Y+eZuaQfP3rYLLH2l978lCXPXgrjAZGKK7W6Zk1j1h0N6coWYZreUe93YVeEIODHtShx8WjezmW1R9V8k9LMBbL55KA23N1hE3DBdosGv8pns0S23AGO/OyCXMsV0dq4ctunjd+wyriro1G/igyGZGiG0225NDbvEwe5RUZhhP1DiWGc2183nfLP17A7qJ8NfV3/K0Jg/vjX3cODQ0+uniFKOZZCRjn4b2+6V2vR6bZGxrY9NutCO7+Ed1H4qnPCvAMjgZ0aLqUUUxjD4hFs8r2ONU2oRWqy/S+j9genfvZRbatHDc5MsMfgA1iOMg7J56xxcx4/Co27L/BSf42Kdlf5YF+guVJiXsAmYfSVcMTzduufFIezf3y8sze9RxS9Sg4FWC4RhkAL8rEGK+VaNH1cTchMU9DKD9Hx5PyvH1R3hQDh/krPbnHeIZNflVCZKHNL+LeAZIYt7GNUrIQR6MddL4AWbpIyxt3ojcljfBegHYIy/GMCSv+x1tzQjnU6LfeHTvgYPtUTdlfgH/wNnq9mXaw69LRZq42e2fGH6nCwiqIj8rypZX4hjpHcdBsv+12O/7ILUNQXC6Z8QlSw598eT29ksRK8pd97I3n5zX/UUEWlHEuBW2LdEmDgtkQUJ9TkC5V4tkuLY8B2VnOAR5nlq/kN8xK1037PnrzSSHH6/Orv9nPvYY77IEkk5YIvbHzv3R9tMxVs+dU3PdzF+uEQ37sLhhHvdE580sFnLafl2z0bjWexUttjD5xdcjztIMkTswK3CbXLsjX2h+7ZGzxusIuicCIB9iLN7bmkzzszDKnrDIzsxCp1SuwN0eU97xTWtX4n0HZYpsnYGH0S7yJs3BPmm8h8aFplZhmHWqf8LY58UekDe3IMGXgEq0rVJc7wttxdtstnrSgfRrLOw/0uV4yD+OG/cAKkgo738M6NQM/sE25fOAQlWldzmSwJQwpemV+HcerqX+sATnEvS4SsLUDtn799s9HP/m3g+2dkg/FlX4qxV7t/FH60HyEGNDTvY0X5v66y1BSQY5Roo8xcq6J8tpKH/1/w90uf4MiONtw4DXtN04cOM7fy9YwD5HxBCcbR59oh7JCQ6Y/6NJy7AgQsr4y+uyyMLdn0rDfwI+eW+cFldJ3jcRPspSEP2Rgoi7xqBo4FA13d7H24+JVgzRWwtKaaUqjKnH5+NYq6OJIivKyz8aUMLq4u4mdj/bncPyn2ia6APe6IcebZjVBA0tKoTpEmYwCYx+j7DMRGFp85Ij9p00FL7SfkrzB3waJjPKtifY6CqFZet62CvFOtsxUry00PhxQ/9D6rZ2jK13UBRCxLNg+yMAPAd7ws4R/M6SI+tS2P8H47ysstMcwwctG5cwaUro2D8rLyL9bkg0L9mn4q4YnHCj7Moc8XysV9lxsXyJlfoK7ToR4/buvC+wfirKh7C5cRPjbme5J9LcjjGMVVoco+gF58XL3sq4hytIbLabvXnxmYwptp5ynVcbnEweG9UTMM5Yx2OWY5A8LHLiCabT4+0J28SI7ZzXxp09gnKKqzf0oe8VXKHVeW+cjKOsHfihRtQ1IbNwMOKRI1X6V8EPc7y/c9xHIx2meG+nmNA1TfbtPCjGhgENoX3PMPaUIsTx+U9mq/odVo1noB4l2QVMir3Jq7jl29IpHKKWpyOLZeYQAmTNUN/KA0NN6efImrvoSGNEiPk8N2PvT0vu4bLJ4BWoHvaEz+JFNlwi8cuXQVlpcM04nYnhSK+L9df8tCyLoFRL+EnpyxqUdYTr2hjXab6MhOmySRJYVbH6RRPK83jA3nzon4PaEyn0frE1zsOxsicYXEfgqLntvUgQVE0JuzQ5+zHdxiX5nc+STJ7lQCN7wlZQbOwXmAMtj6HdX4wdeWvb8dAHpNryan/84Ac2mtjR2T2e/Gdhn198NprqB6tZ3D6sYxsH4805nwqMwIEo9skdzopyhaaMunAeCf2ndJUifrgqCH843PmQnIo1N1la3Y8UtzjdEZWILOq7TR8K/FYTMsTRZIZGxFgrH8Q/zWofD3OpIUVNyz/7GSuZsTAPzlWxTpPZElI5nA6RZN99otY9YczwKIOzslj5syFbO+xmVuHfDbpuWGBvc/NPmBhyuHnYHPjaWRyvEb5xBoIWv0XV7aWPX9oHOyMhG56OCcE3hSogfgD06VOxLxGH1IhwKx4+Gh7BjocOWT7Ku40sSRzM+8GW4+7TjXvD9x5InZ3yDiFIqzVI7/bFJo54KCNfc/2Q4bD42cU+4gbYwX9IDNs/W/STj4wfJwl5fq87mBBzAOtasHzu22hF+lxEsWLuqfKctCRMZM8QTyUcsgPefIljekGWvYTgOaprkNxjG+LdWSrEo7gRe4PrfURcPzvislYkX+BT9Qliwd1DT458cFF9OtKXPa2MX9y5TOjcNUF43W312Vv8tJr6xd1dHa4Eoi6SMm9GbLJFTE5ycg+Th6LZu+wkxjXXJ25qK/PukJdnMPx5GzoeA+eiugLPp8+QGqkPGoSkv7MQtrhOmFto9FA6f9Hcp6QywuA83Cwiiuh5g4NVxiHtSlwJdHiZKGlKnHb8HLxgguzXy4j86NylR8QQ4amqrT9A40e9J+xw8pJkkE2c6zUDGM05LNNC/Qaw5TFYDTKCLShMmYTzWryBN1nQe4T4zUWe1inIPB9YZGtv/c1kEuaT/oukliJt74m8SS+NWEyfQI/rE7SbKJp8GZTYTnEXI06H/uLTIRLepb7NF6dnP3PeAx/7bOjpWJ18H62T3vcNHVbHjMhHu5HcE1k97xNJvAtZigYs76ffddrvGMuiqpBtnh1mMM4MOix0+xKFgUbyCT7CLMwAvLwvytr2PXG7EC8EHvOB8sW6OzmLfW42ZtyIVaJXRCFJVjovIiFOhWUJy1j4Zgk1tunYWYlfJsVXDkK2T20Kuo+gMuWkiHTKdQnbOPf90DyjHJPvRJIGyk/+Z5nLLczLQmLA94T+bdCaVXiT1WKZJ1mzhIv8MkVowbCj8ztbOgdVhYPpUEEd7EbukUUZ51Vai7eSvDBFi3L5DGvQBR/GeeYbIwx7HEGOMx7tIobSXEBkZZVH1Y90eQeDzjJbDP8o8v/d3pU1x20r67+S8uN9iE/efW6VLMeOq7zoWHJc94lFz1ASK9RwiuTY8fn1lzuxdDcaHIAgx3lJrEEDvX1oLAQabnTsmvtSxMd+1oDIxm+reSJV8sF8LV/mTYg9s18WaV6cmZK7Vad5mNHe4uyoXw8q7emvcwbgoY1IHwlA+oEXQG8eOSRmdgsMoep5W8ei/JZrnKmqhQjW3jxjA8TWmT2rrfpSFN9OgqmmD0+2x1rqxU09tDzm++tG1XmdU26I3UU7hrO8CnC0s6zWwJn3SCZVLDvrWNOHh4ftjmEfZoZvhya4Xu1ZzfKqxGvexs65nhTFt5NgqunDkzdJ8ZSWzeGSLjWcmMdujlvV9tidVuA7y8cgY8uoqDfh5BraqNM86PXVffbjc71u25nP9vYKevT6vduP8jdnvZKOz5Eo8prZvF687JQGFWCQf5YMXWWPHv2UfEuT738k2fH+lB1mDsAOtr44p16cuKNTeLZDhupnifIlLnuLezgw/DLLH4ZLw5fqy14/VioC1j5dbbObvJxxgHKq6aOTNvdBfhZfOts4dA2Oxglvq0TNLcO8ztPV9BLB8yz7M28+/QVNxbxA5K4VvTqU3+d8TRfreh9GL9cDYd/vcRcbOj85Cw1NfsUpw9ys9++6ecD/JWV7WtpBUx9yy5YwrF+VZb5LW8/2HP5zSk7JvrstPd7ejZrbuwryfz/sf2lWI03ytu+HLJ+y/Nwm2f2v04/vT1mVHrN0V/P/97N//frrb8/ULvPx0J23/qU5uN1cXLmOy128181Rq7HHxAAll2RCKGQB/0dj2p6NaT7Pxtl1fiirIq4Nrvf69LBLj3GmGkUhZAaIRtexSbXkVXJsvnYdKlJzDuPhVrbOfOShOMBkjBfPBUyxoRaJ1/NRoElEomPlAtmf/tGGYcwTsnBTeUcXi52aaSE0toQwhqpynmN/ioBmC4NgMU2oGQx+w15a9wxv1P8fxdxQLvp2/G0RrMkCy4IoRV7QBVrID7IUfTgcheeUVwEn8Ju35kzIiz8LnCALrQdO01IrCJzGoxkO41I7r1d9N7U0HroRm5p+3HpMgc8UYe4PHE3GAyVXx9rkzfG7Xvw0KaPb/FTsiLVeX1de640/LhJctINckCyesTWqvAC46INrCE/6cFNwmN3FRXNM00m4uGCYWbk8BMyIA47BYNYPr5uKZdCUTSu7mEhmMz1bYyAbEWYIY+En4uHxteBUfA68yAMri6Jr+Eef1q3Z71BPaqwzdgkCg+JI5RcTw0StbOJY6E2EqPZ8+i0pftw1t2dR34pEklOlAqs1oEU0dIULVFU/2LAa1wTZQmNinMUhd2PWNTFXhIbwdEHTclUjm1n5anC1mR3LgKBafs/Sbtcq8KZlkyf9Pm53zooFNy5FtlJzcsHWNzAlbbawidnDUUIFrsS5DvUbdFCRwHIvULP2v5sIZM1WrLAq7G1mhFsB2JYf6ezDW+DRTrkY+impW0ubDBrdTU3Glxq5BcnTatEi8IOv+eJijRReIKiYYAEkMq45I4w595pXgknTZx3A64iffwoU2qAgGPzwC/JL4a6q1ym1XN9qVZMIFH1dGBPllbEll2weU5I6FlgKBqXhdrxDEBnWj0ruybal8betAwBM6LBy1xOgPb8Prw4M9l10QUhIwoU7DTfc8B8fWDRNrYcacngYf1xkkFFzqkCieI4zg8ILoIrMIIOwpLNMBEKWYYJsESAuFlc2Pg4AKzyz0NKoeplmWT2jh7MqzcKU6RCvTdzbLhpYvGTjh0dDn1wygpVY6eClZMSURBnLLmbwGjSyGbyC4ap7ieS2KtrcA2U7IYqG52dQcI0EokenHxcBFyS5LA9I4AVmsMH8wAxUi8NXfFQoCNaGBCvbwNcgrSTD9OPmcTSqsgnsDPlXtoGdQVpJhunHzWNnVGUT2GkSx2wDN42k8sfa9ofN46VVYzNY6ZIMRbrQtp7yiJJORk2C4Wc/3/O5XnSAll4RDreGPBxe1OywYgZP4w6jWlv2p1a4DLqo3L+UfDKhHwSqJlkCjexcyBg+WcmPVwBW01d9DAiU439CkFoDJDRADXmXl97vtIqgq9rmMnWQBcG36IbXXMCtDmwbioBrBN1SEe9s4IWNdL//3TwGF2dXp+qxaXHXFipD8qojHqWBnLGPJNx8BCTVswFkMCy+zovTU7sp6xp4+PfFkafUkvDr5nEx6bIdELSvZS6Ngu6JTg0G/c+XgQPgGdJVAyFq//umyE9HFAUCiea8/udFBqKWoS6CJ+hghvEIHhajSa41hBBAbnuP+cRLgKDD9+WyEaclX8HkAxP7HNf5hNDisxdLvy46f2nFCgai9jECD4fkqLmL9GJ520j/y9bPxnVqcBjJBg/s/U0smpcFzZLTXD5qgs9wh/RMn8v4IfkjraUpfkRwXqmVpuESJQflkQkuJhGXpBaHb/BMXCDWgL5iHyN+AojxY0oofLUShh342hNxa0bUKKUugcejcIthZ1JkS4BZfw6TMLBZMF+JHXBCZyl5k95X13Gxj25OzQNrZbL/klaPiA7z3Wi4ljRIITU2/egvkrD9dCYoRl1YmABdERwi0lwHVmiuSz3FGkhyUJ4FJj1WCHCENut5z1BxXVj7LHaFlc6G1gS1xeZIs3EmeTTstOlDXiXrn2c3UuoSdL9uG0OTIuufZ39Kvtdov8nrBsohOG1ifxIQXBIHLN/83iWk1SZ2MiGcOR0EDdPx9cBlsTA0FyuSW8Im9mvfR13zSDYIqSXr8/iuxGIAGvVY/zA2iNruGMFyz/WaZ+RoGw5ygbc8o2zHOgIRe1tirBD2I+2QmnK5RDb/fKZ/phg94GylOhWH5jGdxMNZY0/zYUFkZWojlVzAHFjUZxOz3/ECz/JR5Z/0WGh4CTm3zVtBruOim7K8PB32WUJ+nug/qwiUwBcvsdQKJqpEypRELfT5RQuzhR8AabpxeE4irgdCmxil1gCzJceqWegKPlxp2Fr/N/g1AGvBL/KzcBX6w3zP//aY7NL7/vbhVVUV6ddTlWwAY6T8kFwY5dbRR6u3BSjCon88Nn9GHL3E0GPGAw8IvkIjoStDyoHQz07UfAidG0EpZVmLCbCBlQZXvra+gfNTxuazwRY8YFMahMb8MHW4ix/MOczWMouYZIZkEUsvZLYgqGQxQwiYfgwAlyHjDu1TwJk/A8S4Dg+Jspo8OND+jIs0PlRjbL3On76mh5Ywwp+MF+/DiETSPRipwGpb0CwbBC2S3M8VL9RAXtFGKcqRQhR7zfjbzNp89UBdftw9F6Ohl+oMeP7nVJfUXvp8SHGMSkQiFuSCiwyPuIFWDT1R7LXhb7sxkQPJi41+Ww55A+TMW5IaIeDypbchV4vGRbcdHcByNXuNqhJ/xtlpBCmtoR9cLIvcVl2OmD2hTwzPwpMfKHfaWuBZbSA0rINPJzlThm1PDDc3+4tu46djlrzKvx+yPMavCI8E0v7L+OPK0QCrFx4JsvGDYeEuTYpav10i5WE3nrdqibj511tMeBnFRuklWYRfLyLZ+qQPh5ko3Qpgtf4lZRgQLbhstMPPShaK03HU/iDx+oGkSW53mnl7wNJV4jCV3LkimDkFmHEK9A9KaKarw8e4lnudZlWT79b79YalwpSiGS2kRnw51yaMuvKjW9BrFC/j3V9vD7dVvvvr9vS13BVpdwZ0E7cpEOHlN6Axms3frcA0s5n4rw5365+5rQ90C47P52Au9Ch9tcuAhzTdbl94wtwounwhdvr1IvYuJn02s3dxdX+fZvUvSbTQWwYjQ7mh6detX2ieVOEwC50ooX16w+vECYeCxlt/4uTCZjy6TpuY69T6fasB/b4GavyQRK+L/Gk5lMjMlXmTXLR5fCgKcTiKzlgLQO7yf+CxEnhMrgg4ba3Sb7XJ3+UPkfDvxlH4ZEOhk2eNStlCM9iRKyaNr7kLZTM/QBOVYs1iZBFXAbVNbACFQ9WSccsWTsFD1vVjsvsrP6kn47SfcWxplBLI9NJlHnEH1aJF83n2zWBPP2BENGThUq0aML/d7lTUGj/cxD/aHKtvD2nTrqNUq/h0S2WsJKpTC7edP1XTh8VT8MRq8DFkbKY1cuVnbxkSQaVI0fymgp4HELegtMkMrdYNhs93+S7O0v8m+0HG6F18eDhpqxrB+yOB6O7px0UQqIktC6OXekEdbCo/aNN14jAdJAyIL9cLAH/nOtW5/2XM+Tcz1++/6n1KvqXJ9z+S7Hh/yg5NctHGJVPj7a7niKn696qZdt3lEVbfCLu+DRB9Y5n1UStdDuBLM0DEONl+FuYGjRbZJuN4xIp3SBxKBb+hsJLIcKcHg5TbswK9MmtHk1Q53FmVLH+4zp/ahSIzqglVlg5kImvpTIr4+wWFK9TUVuwWhlHz75u8rPB4NFBoPux+XOaokz2UHIWpUdEVAmiQLRiQPiTfS8t4JFRZOh6JrMUmpd8vKB6hprZitzCMmn8355DxeDRQaD5c8DT6DCg5ikejoisE0CBbuKl3nmV/5lV6eOjPV3KXfkq9xZd8Kn9pXq4VXlCMoi1vxTMEvpofrg7l96Qg1nMjjebW4edlbtCcAzJXi75J6bWCaxJxHTsIs7avgu5Z8fcsth26HG1gLA6o/i/jDlT4S8m2eNreNRZ7EC17eeX3NvFL06vqGkkx7PXn++R1WpTVq7iKv8al/rGvqXWbVEACmWe/dKVoop7b3WPyFP/72f5rXns+/qrn+NHCkMotOSX735/iNLuqqnj32E3HAbYgHcwfJDUIMqWb0XhPRRC7qZSvKq2gUS0jq0753S4/gcaUiyFmMoWBW/9d+31SPeaQ/ZRyiJ9CYmJovDKsy2CsAoplrGUrKUc0pixc5jhLkpG5x6QlBrCpCOwxY6mBw3VcJQ/tQRKNw1QEcZhKTSZKd9WpSCATDSWgiYZCngcILgoB4Y+oJ4rex+0jVmz/NG9+pkWCBFaQivKaRGgSQcqhrvOWikGmEgXP2gRqNArK3gMV2+B9xffx4XQft74iIpFMRckhUrJlMQhh5i5TmDqquhrR+6tKAXZblYjJluBHMzJyGO9aagzGEqj9sdCkQNOh4Pg2lIDiD4WG5oeXW9GRWSWAmKk0Jp5V3W1q6H6rJ9VQn1fKQY4yiYHh9HVL4zUVQWymUgOH6bybxmEqgjhMpSYOzYGv5LYq2u2PsnuMB+AGkoGcQUqDFNO+vMZ5KoK4NaXm6CjsoelBUSgEY6FQzuCDcMDbZoea7vo+Gm66YirkdBQmHZLiKS3Len037PDp+mgkoG4alWnl8Hc9rT3E2dWpemzWoN3TVagUNDm4siBrGKRrr/IinV0og/i2xWXE6vAt7V1+THcYn76QYNRTcDhhTMj2ewpO+2+K/HTEmPSFBKeewsCpv9SgMel/h9rvi5jT2M/N9cvxZDk6j5XJqImsTGmQ4k16X08bi71BCpgMkgKmZEpBcKa58dyIDAJCGepO1sK0pfyQV1AkFcpQJl2xgcmn5Hut8E2eHqoSdxhIBTEGCRnzL2TZNRVhcy7WAmsgRDwmF1OcWH77lNTLgUOzAEzAEKyUw2aUSIzqqU9QAyqqJLCaKhVvLYe9d4mt6jB6an2HPD7KXenRz4jq9iLJQduRNWZJx5bLRiKuT9u3+zAHtoWEt9pyHh/ywSKMP1mJkIusN09evpCGfSvlAQ7b/RSGQDxJrJGCPEnBNUtPbuG1voZBOiFxtCaKUAbxFYqN6/gc3LHsf4fX7TljT7SlGiEAtz8Wo2y4GLqr6yetmb+CCzelHLSZTGLajsCS+Om7ExgluFmBEVvsA94lT8csBvsRTGbaF5womRv5hAQ6CbWxz+Y8hG2csUZBRX8u2zfJIam7GRW6dBJw4q5RmXYsp/R++p7lVAbuWk7FBiafC5yJUAYxEYpNgaKuvT9lyV1c/gUFCqkYDBQShcluUzI83W5TGWi3qdjUG5O4rDvOlyR9eIQm0Eo52P9kEh7DV2k9xy/hMKSTEGwFKuOGWVEkhx24tz0WwRtlQylnb8MQYwEaYqfDKrKqia6AwCITULtSCqmxjwuJaoBeLpTC/Vwg4LPq8j5R7DoKA8uOyDhgPB3j9AFy6VQEDxBDqbFntLamxkSVAu4XChFjj/1dUlVJYUAuRojtv0O0JiPrGXF0a+s0oNl1Mlvm2OQaI2SJwZtQo/krgB0OhBLe60CILcVhyMETgPcdSk6moLEGaNAvUDKZUe2mB+3xUysqAayyTGNCYZGXZd14hnPVSUDkaVRGU0OxG43ZnFhd/6tCFlVDCThNGgpNS6n4b+JohVQKLqNEAhMr5NsL8dmF971l2PsnFNFJqI8I0dXxmKXJ/i7v6dPE/Cl0qIyDTqPgydCTmyWQP+rjxxAQOvM5gU9JWYfg9lQx4xhE/20UP22hUVCfWHsihiPUz6OGr7w0Oef7a9SQsjfAxvMwvI/P/I/QUi572/04zjYqczc1muhspQBSH2DCAKSETAA14wTKeJFQ3+YRS7FzKCMBY/qIs5JKsYkil5V+y0i3r0aCna+QqWw8bHIrw5caQ+H8vOEkevQ6bfqSUIE6j95Rqwf9p5sS6hnzmjl+flyqSR6Tb5vhnXp/LqtuZ5ZIPhVOGkWmxRWDTqq3+lAn0DHTYAZxawbB3xIPLkjESk7VWgli5DPN0Xi2WbcPQokrpRykbtVAzmJDV5TA6kqRc/WHGZxZffgmE3TXS5d/NeqPp6YJv2s07j2uTvDbiuj6w15Nav4f3fbnLXXNOdWI8UO5cdGNH8htCrAmaBa90K957uLiIQF7BKeaI5+vzDzDys0KO2oln8iBYo9W5tMwNqhRK7kPqYHMAlzCKUmTgBV84gS4bSQ1Ql0fmj3YRvI9IXSolekIZYB7S50WxH2kubiar/Y4jk6hCVVdp/URSJErWKL6DsOoqpp5rqWReptsLWQC6XIYMeUC6dxPu6BrZ21l8jrZbM8rV+NEFigE8DqO1YJQgTYElnsxk7mTgOTeOsrCJpG3qaNhnzrpN7fp6Tu/Mq42fCmv1Zi+ake0omzgA41hVwa9GBCfqvErE6MToCyi3lpMJl5ojMZ2AfOAhB5MAV3S7ExA3r20V73/FkMprZK4V1f5rtTWw67ozldRMR2uqEzo3EsLqj5+Txo/uRHBEycmPK7c8O5cjtzeBmtCBtDKfBgCD4I4sRM/rsEML9MsqwPp2DJhBIXUnwlYGDpD6SG/29Q0rrVG67MLKFkZpAawVAv2ZoCunE8PzAC2oCvgaqnX8Vt9sKv2ck3imn3XCufavL1phkwDpDl0Ih8mULMltDXRTAj2qg6JAkhVdSIfqqrpDNqa04/nL67yLCPVlAl8qCjmOujWTFAug3mqdekWoq5FWDmR5EwhtRpyqoixHpIEYoaK6skc8UgMNY3hVSSUQ3I7dCoa8jWQLUHHhMBWyVwUzk2JT4R4Fc0DnKYwpeAqTQgezGJNpi3R52A2YTL6MqZhTK8XR1MoE1E5VcjpKK+iDzRx8sy0rVnli7E33Zg1hrQTQOXDKFp6m7aq8Ksbdduj0mZ9FTJvCkuHvSeN4QPdM1WOxFw3iL4ijUFkKaPOJDKYKUevrVd069m+SdKtHc3ZgobxZtsTxWapHivQORR94X7bpr1h7KqAdO63VKRkT20tOJfTXEWp2KRQ+AhL/tSDck9F0/kK/CAHXIE4wXD2QQ4o05XUCpm5ypFpersz7QK+UW7v11WYY8ymhdtAJXGruJYJbKrnaANlkp/4Jq0Tuf8S7V/VIf9adFOvzx7jMtl/SatHgYOuuKmKM3WkumqGubYqmj1uviGkfju1j5sBruBIEbAm2uNZqfscmeaz6He2feRabuNCKPOMmf8MAVEg8RAQxayFUz0wIaG9ikCOQXIqRNL7mBgRORXbRjhJEt2YxdgvzJXcoiOMaYb0jXifUCjcKq0muLwdTtE4Ot4rZqeMpnZxLWVCR4KDNbXxlcykOXchNJxIMi75VMItrfmknKCGgAdS+gl1QK7TvidTKUzP2OBmOBulde9v38cm1JSskfiyDdTDcXLjikB/f0dcG+BP6ig9H05G2/d+Q4JZBwaiegZO7KNzBDcFsWBEad2vG5c2A5lkmHGw21DRvYFYWZTF1piZfWcgiEhijGRgBsE1oxkCP2a78AzCaFVOCE20jSR2do1WmrkthMnWFjbVSnuAnnKFPOWCU3sLE3piGbEFsdSLMYhzKji1USlEG0CNdZiESCTOvWXJboLYdXd1AZORhF20qE0adS8mNg/inNreuujWzCm/RTvLplITuIGgZ3NbU1DP4W7VxDNgujw2OdbybyJyOsmua1Se1Jo3b1ylGdsErhHKlm1Sup1FjMJpV0qCSzUOJ7adPxFiBsrFo6HfBArRbfx0zJIpBRuuuEJJTF1mJ3nzq/L49Id0LhZSGaE0byTNPzmrPWnSVsdfKzlHfWLs0oncj1b+VdW2Nfu9UvOgjVbxNmjP3Zl1YhYrg1y0KZR37Q178TbVl9mbN0lEN60Rn3/jD36uh9zCN9bxsZNveK6obYj79pA7MxEd01TFffcMa6LxUR7jsI1Q+hy2tceGuq+L6DtCM9Qf3taJiK+iOpH7z6Ha+0BdTfTpn5lH29nRgqD2ESfQx3Zux6PujoEvv4kTvS7yJ8oeFLkPg8DP//RBhXzN52xT3OUWhhCIt24G4RGhSHtQCIqHODkV0sAHj/rARr5ihLWDteHYJBQoQDofeFhGbfidnwh4hQg4QsOtS5gHezips5PpLSS6LX0LiPmq0ZyDV/L7QNHbQ1qlcUYcqqQq4ErOO1AJv4J0253BMrxsdL4xhkOjOiuzXdC6ntQlW4JPo/KeppqZBkZ898mcAwamxhU8MwEM9L5V14Txyao55qADs/eArAZiNwG4Xzdpb6VEzbh4nR/KqojT5kGMeho26jckPbrLI6w+sdvgnpfZ3DMyJ0FLTvStGnENan53xqGbpILfrKyuVGUqj2u8agMKb/Nw4SY95wPsX5zXon/MAq8ZdVscxDNF5xl2SD8F4hAmpMVnJ7gKoLjwBBPX/9KrTbqFzmzRP6KAR6vaNqjXqM4z7JDlC0QUTEiLz84jFkBx9X0t9riovcsFjAKu2l5grEXeK+uGCNMbZA7MPqUngwdUip5WyyoXWlijiEPxrAmacS4yr9WF53r86c7ZRu7/Mk/iJkpvnzAdKv/ieddIY/raz0kxlr143r341/9Q/1kvXeuV3/t8n2Rl++uL559Ode2npPvrVVKmD1MTL+o2D0n7NunU6EDz9nCfD+s/RaKBZCgeXxKv4n1cxVdFlTaJ6uviXT37bN+ybHdqmsfsvib7t4ePp+p4qmqVk6evmbS6fPGc5v/iuSbzi+70eelChVrMtFYh+Xh4eUqz/Sj36zgrlRU11sR1bf03Sf1758u6c1bJw4+xpQ/5gdlQb75XyTE57OtONzytXn483MbfkjmyfS6Td8lDvPtx06TMbjeesEbMjpDN/uJVGj8U8VPZtzHVr/+sMbx/+vt//x+rvS3/fzgIAA== + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201507200832223_SortFilterHomepageProducts.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201507200832223_SortFilterHomepageProducts.Designer.cs new file mode 100644 index 0000000000..2f16779b2a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507200832223_SortFilterHomepageProducts.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 SortFilterHomepageProducts : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(SortFilterHomepageProducts)); + + string IMigrationMetadata.Id + { + get { return "201507200832223_SortFilterHomepageProducts"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507200832223_SortFilterHomepageProducts.cs b/src/Libraries/SmartStore.Data/Migrations/201507200832223_SortFilterHomepageProducts.cs new file mode 100644 index 0000000000..c9e3918b69 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507200832223_SortFilterHomepageProducts.cs @@ -0,0 +1,61 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using SmartStore.Core.Data; + using SmartStore.Data.Setup; + + public partial class SortFilterHomepageProducts : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Product", "HomePageDisplayOrder", c => c.Int(nullable: false)); + + if (HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer) + { + this.SqlFileOrResource("LatestProductLoadAllPaged.sql"); + } + } + + public override void Down() + { + DropColumn("dbo.Product", "HomePageDisplayOrder"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Common.Unspecified", + "Unspecified", + "Nicht spezifiziert"); + + builder.AddOrUpdate("Admin.Catalog.Products.List.SearchIsPublished", + "Published", + "Verffentlicht", + "Filters for published or unpublished products.", + "Filtert nach verffentlichten bzw. unverffentlichten Produkten."); + + builder.AddOrUpdate("Admin.Catalog.Products.List.SearchHomePageProducts", + "Showed on home page", + "Auf Homepage angezeigt", + "Filters for products displayed or not displayed on homepage.", + "Filtert nach Produkten, die auf der Homepage angezeigt oder nicht angezeigt werden."); + + builder.AddOrUpdate("Admin.Catalog.Products.Fields.HomePageDisplayOrder", + "Homepage display order", + "Homepage Reihenfolge", + "Specifies the display order for products displayed on homepage. 1 represents the first element in the list.", + "Legt die Anzeige-Reihenfolge der Produkte auf der Homepage fest (1 steht bspw. fr das erste Element in der Liste)."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507200832223_SortFilterHomepageProducts.resx b/src/Libraries/SmartStore.Data/Migrations/201507200832223_SortFilterHomepageProducts.resx new file mode 100644 index 0000000000..cff0d41d5c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507200832223_SortFilterHomepageProducts.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/201507210952098_PaymentMethodDescription.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201507210952098_PaymentMethodDescription.Designer.cs new file mode 100644 index 0000000000..4412803c98 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507210952098_PaymentMethodDescription.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 PaymentMethodDescription : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(PaymentMethodDescription)); + + string IMigrationMetadata.Id + { + get { return "201507210952098_PaymentMethodDescription"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507210952098_PaymentMethodDescription.cs b/src/Libraries/SmartStore.Data/Migrations/201507210952098_PaymentMethodDescription.cs new file mode 100644 index 0000000000..b71dd98ac0 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507210952098_PaymentMethodDescription.cs @@ -0,0 +1,66 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class PaymentMethodDescription : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.PaymentMethod", "FullDescription", c => c.String(maxLength: 4000)); + } + + public override void Down() + { + DropColumn("dbo.PaymentMethod", "FullDescription"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.ShortDescription", + "Short description", + "Kurzbeschreibung", + "Specifies a short description of the payment method.", + "Legt eine Kurzbeschreibung der Zahlungsmethode fest."); + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.FullDescription", + "Full description", + "Langtext", + "Specifies a full description of the payment method. It appears in the payment list in checkout.", + "Legt eine vollstndige Beschreibung der Zahlungsmethode fest. Sie erscheint in der Zahlungsliste im Kassenbereich."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.PriceDisplayType", + "Price display", + "Preisanzeige", + "Specifies whether or what type of price to be displayed in product lists.", + "Legt fest, ob bzw. welcher Typ von Preis in Produktlisten angezeigt werden soll."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayType.LowestPrice", + "Minimum feasible price", + "Minimal realisierbarer Preis"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayType.PreSelectedPrice", + "Price preselected on detail page", + "Auf der Detailseite vorgewhlter Preis"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayType.PriceWithoutDiscountsAndAttributes", + "Price without discounts and attributes", + "Preis ohne Rabatte und Attribute"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayType.Hide", + "No price indication", + "Keine Preisanzeige"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507210952098_PaymentMethodDescription.resx b/src/Libraries/SmartStore.Data/Migrations/201507210952098_PaymentMethodDescription.resx new file mode 100644 index 0000000000..04f8e677a2 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507210952098_PaymentMethodDescription.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/201507242008201_WebScheduler.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201507242008201_WebScheduler.Designer.cs new file mode 100644 index 0000000000..d9e3e0bca9 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507242008201_WebScheduler.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 WebScheduler : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(WebScheduler)); + + string IMigrationMetadata.Id + { + get { return "201507242008201_WebScheduler"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507242008201_WebScheduler.cs b/src/Libraries/SmartStore.Data/Migrations/201507242008201_WebScheduler.cs new file mode 100644 index 0000000000..d9a437cb3c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507242008201_WebScheduler.cs @@ -0,0 +1,97 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class WebScheduler : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ScheduleTask", "Alias", c => c.String(maxLength: 500)); + AddColumn("dbo.ScheduleTask", "NextRunUtc", c => c.DateTime()); + AddColumn("dbo.ScheduleTask", "IsHidden", c => c.Boolean(nullable: false)); + AddColumn("dbo.ScheduleTask", "ProgressPercent", c => c.Int()); + AddColumn("dbo.ScheduleTask", "ProgressMessage", c => c.String(maxLength: 1000)); + AlterColumn("dbo.ScheduleTask", "Name", c => c.String(nullable: false, maxLength: 500)); + AlterColumn("dbo.ScheduleTask", "Type", c => c.String(nullable: false, maxLength: 800)); + CreateIndex("dbo.ScheduleTask", "Type"); + CreateIndex("dbo.ScheduleTask", new[] { "NextRunUtc", "Enabled" }, name: "IX_NextRun_Enabled"); + CreateIndex("dbo.ScheduleTask", new[] { "LastStartUtc", "LastEndUtc" }, name: "IX_LastStart_LastEnd"); + } + + public override void Down() + { + DropIndex("dbo.ScheduleTask", "IX_LastStart_LastEnd"); + DropIndex("dbo.ScheduleTask", "IX_NextRun_Enabled"); + DropIndex("dbo.ScheduleTask", new[] { "Type" }); + //AlterColumn("dbo.ScheduleTask", "Type", c => c.String(nullable: false)); + //AlterColumn("dbo.ScheduleTask", "Name", c => c.String(nullable: false)); + DropColumn("dbo.ScheduleTask", "ProgressMessage"); + DropColumn("dbo.ScheduleTask", "ProgressPercent"); + DropColumn("dbo.ScheduleTask", "IsHidden"); + DropColumn("dbo.ScheduleTask", "NextRunUtc"); + DropColumn("dbo.ScheduleTask", "Alias"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.Delete("Admin.System.ScheduleTasks.RestartApplication"); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.NextRun", + "Next Run in", + "Nchste Ausfhrung in"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.LastStart", + "Last Run", + "Letzte Ausfhrung"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.AbnormalAbort", + "Abnormally aborted due to application shutdown", + "Abbruch erzwungen durch Herunterfahren der Anwendung"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.UpdateLocked", + "The task can not be edited while it is running.", + "Die Aufgabe kann nicht bearbeitet werden, whrend sie ausgefhrt wird."); + builder.AddOrUpdate("Admin.System.ScheduleTasks.CancellationRequested", + "Cancellation request has been submitted.", + "Abbruchanforderung wurde bermittelt."); + + builder.AddOrUpdate("Common.Waiting", + "Waiting", + "Wartend"); + + builder.AddOrUpdate("Time.Year", "Year", "Jahr"); + builder.AddOrUpdate("Time.Years", "Years", "Jahre"); + builder.AddOrUpdate("Time.Month", "Month", "Monat"); + builder.AddOrUpdate("Time.Months", "Months", "Monate"); + builder.AddOrUpdate("Time.Week", "Week", "Woche"); + builder.AddOrUpdate("Time.Weeks", "Weeks", "Wochen"); + builder.AddOrUpdate("Time.Day", "Day", "Tag"); + builder.AddOrUpdate("Time.Days", "Days", "Tage"); + builder.AddOrUpdate("Time.Hour", "Hour", "Stunde"); + builder.AddOrUpdate("Time.Hours", "Hours", "Stunden"); + builder.AddOrUpdate("Time.Minute", "Minute", "Minute"); + builder.AddOrUpdate("Time.Minutes", "Minutes", "Minuten"); + builder.AddOrUpdate("Time.Second", "Second", "Sekunde"); + builder.AddOrUpdate("Time.Seconds", "Seconds", "Sekunden"); + + builder.AddOrUpdate("Time.DayAbbr", "d", "Tg."); + builder.AddOrUpdate("Time.DaysAbbr", "d", "Tg."); + builder.AddOrUpdate("Time.HourAbbr", "h", "Std."); + builder.AddOrUpdate("Time.HoursAbbr", "h", "Std."); + builder.AddOrUpdate("Time.MinuteAbbr", "min", "Min."); + builder.AddOrUpdate("Time.MinutesAbbr", "min", "Min."); + builder.AddOrUpdate("Time.SecondAbbr", "sec", "Sek."); + builder.AddOrUpdate("Time.SecondsAbbr", "sec", "Sek."); + + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507242008201_WebScheduler.resx b/src/Libraries/SmartStore.Data/Migrations/201507242008201_WebScheduler.resx new file mode 100644 index 0000000000..7795c02827 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507242008201_WebScheduler.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/201507250039446_Merge2.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201507250039446_Merge2.Designer.cs new file mode 100644 index 0000000000..8830d1f8ac --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507250039446_Merge2.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 Merge2 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Merge2)); + + string IMigrationMetadata.Id + { + get { return "201507250039446_Merge2"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507250039446_Merge2.cs b/src/Libraries/SmartStore.Data/Migrations/201507250039446_Merge2.cs new file mode 100644 index 0000000000..a26a4dfe70 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507250039446_Merge2.cs @@ -0,0 +1,16 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Merge2 : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507250039446_Merge2.resx b/src/Libraries/SmartStore.Data/Migrations/201507250039446_Merge2.resx new file mode 100644 index 0000000000..483dbdf042 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201507250039446_Merge2.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/201508042207146_RemoveKeepAlive.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508042207146_RemoveKeepAlive.Designer.cs new file mode 100644 index 0000000000..fbd14a355a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508042207146_RemoveKeepAlive.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 RemoveKeepAlive : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(RemoveKeepAlive)); + + string IMigrationMetadata.Id + { + get { return "201508042207146_RemoveKeepAlive"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508042207146_RemoveKeepAlive.cs b/src/Libraries/SmartStore.Data/Migrations/201508042207146_RemoveKeepAlive.cs new file mode 100644 index 0000000000..8c0dc62ad6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508042207146_RemoveKeepAlive.cs @@ -0,0 +1,40 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using Core.Data; + using SmartStore.Data.Setup; + + public partial class RemoveKeepAlive : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + if (HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer) + { + Sql("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.Services.Common.KeepAliveTask, SmartStore.Services'"); + } + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.System.ScheduleTasks", + "Scheduled Tasks", + "Geplante Aufgaben"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508042207146_RemoveKeepAlive.resx b/src/Libraries/SmartStore.Data/Migrations/201508042207146_RemoveKeepAlive.resx new file mode 100644 index 0000000000..483dbdf042 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508042207146_RemoveKeepAlive.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 + + + H4sIAAAAAAAEAOy92XIcObIo+D5m8w8yPd0ZO1cqqbvM+rZVzTWSIiXa0cImKemc80IDI8FkNCMjsmLh0mP3y+ZhPml+YQDEktj3iMjMyhcpGXA4AIfD4e5wOP6//+f//e1/Pq+yV4+wrNIi//31uze/vH4F86RYpPny99dNffff//b6f/5f//v/9tvpYvX86kcP9xcMh2rm1e+v7+t6/fe3b6vkHq5A9WaVJmVRFXf1m6RYvQWL4u37X375H2/fvXsLEYrXCNerV79dNnmdriD5A/15UuQJXNcNyL4UC5hV3XdUckWwvvoKVrBagwT+/vpqBcr6qi5K+OYDqMHrV0dZClA3rmB29/oVyPOiBjXq5N+/V/CqLot8ebVGH0B2/bKGCO4OZBXsOv/3DbjtOH55j8fxdlOxR5U0VV2sHBG++0tHmLd8dS/yvh4Ih0h3ikhcv+BRE/L9/vofDSCfvudp/foV3+DfT7ISAzMUPiFkRmBp/uZDWsIEfX15QyP6t1cc+L8NXPL+zfs3v7z55d9enTRZ3ZTw9xw2dQmyf3t10dxmafLv8OW6eID573mTZXTXUedRGfMBfbooizUs65dLeNcN6Hzx+tVbtt5bvuJQjarTDvM8r//y/vWrr6hxcJvBgTMokpBRfYQ5LEENFxegrmGZYxyQjF9onWsL/9u3hlgRLanXr76A588wX9b3v7/+Fa2hs/QZLvoPXQcQWdECRHXqsoGSDuob/QCrpEzXLQ+Ftm1oKq3WGXj5XCQgizBQq8a+lQtYmqZQj+m8+gDvAOLJHs1xUWQQ5AZEv73drCjDOoMNXJyiRZMd1TVI7leIXTwW3BdYVWAJqzdShIeFp26LIpi5WT0q3BM0CZjHmSWFdps33IwIoI5NoWHea1bRu19+ibGOztIMCkQxLD2yz3ZrJc1B+cJ0DP2U9ytIOr63HK0jkb8gtQO3OUHLX8FjuiSsIJmC168uYUYKq/t03TGUdKHftOBnZbG6LDKVfCFQN1dFUyZ4bIUR9BqUS1jbd5pC5dB3ppZpCNRXy5HQNWQDspbZH4qnPCvAwktML1LwpkdwEMsaOdLR6GOTDq22vx0XMVLte1zfy8xtG1d3i0IlkQpKKeemxXRt9WI0tlBFllSNJsMg4eKMBYuRXC/E4zR0+ozGVOkV2jgtnVdf4VMoP51X1yVA/SVaXxiq7+sFXmnf8u910uNCuzG8TlemDchHXY2kpB5koLqtizItSgIXopXinVRrcI2iO+FWjTZtDB31uph+cNfFJEO7hOvsJcr4rNqZZEwnJ6M3cZwko7dx1dz+Eyb1+CbYcbF4GX0rOymh79YhIQ3WKcoUVoHGNEKj6JBBAyB6f5IUTV4HW/QwX3wBeQOy7MV5f1aaRxujpJrCPOqNHXvzqDeobAdEk9w0ohsWWDoQBkZn3rGAQVYd2y1/3YbGc1BuDMtU51n59ddRXN6ta9jkULJsXN/Wp6LS7RAjjfCiKOswqYdM5tJgrI3Vd1BVT0W5mL7l0xwDX1XBHgLsb2iPDNCuStYCqjXS6cEFeMGy+wus7wsfd1RXv3rDIDpILR2DUoS6eqlqaLJy/mqrB7oy7HOSNQu4OCGTDku8N54vqtE136FdvMGVL/om38dRgvs2r5BGsUZttOSfYrTdKEkH8AogPrPnYL3yS5qnq2ZFDiiPVq3K0Wm5MElXAEmhixL96iIt/vb61RU+N0XcZMQMnkfC3GK7hBWidVLHo8UZArc7iLZeSn7CtCwWTVIfN/kCrSS0sJFyXKa3TQ3P0qzGB8nO8vUE1CArlm9MqA8SV93WhmihjDYQPRqiHyBrgrGdV2hBIisJJog88YzMDd2kFpmJJW/o+hsjzaGaYLe51A0y5YSGYi7dw1pVt9URK3RFtMSOhKwPQAvDgszGJOZm2uO7gGiJ5DVYwlC7w6yGxnB73iPj0m6zjnZuiFhwEBSOhpTEJE8X8Pq+Wd3mlPfBF9kPNPG3WfDMEeFQ3XsI/9HC3qI6hAMOJg1eVHr3kLtSJ9jmePeqzxbptql3LdgN+IaroxkkC2revzl41yght2FYDcC+67pOuyoa4erFQanQiMeWRLi5UF3gApSozY9l0axRR+LoF90mcJ4v0sd04XdSNM5W7nrWNfnebm34x2nvaLFK85NitaKCbUZrrOdauEJbcrjJiSbn6Vv+qVjBiwi6Yo8nnsLwBdYACQ3su9c55eJoobixKRkHt3ed1tp7C3FGdpRlxdPg0YWPKXwKVnmP1uuyeISLSySN8+VVswqb6a9FHRljj+4abRgZN+jgPsZD2oV8XBdHSbDl8DldpTXuHNkbg6f46qEZf9GBvLkDCVZBSrSP1l+REbWRGqM1+xGx2OiNnFcf07v6BJTBJliPJ4a2cgn/aNISfqvvEcXb7SSYUzqcG+VHJ60jhTAdNVg3rtME60ZHiwXXh+AxnVebiPw40d2hM/c9z9oF3iMMHiOalXbBfbsTcHp6tDo0p8/rtCSK+gfwwuO0Q3GU1OkjQRGD7T+B6gogrQnGmlUWm+N9JtQbHA5xtCwhpBXHgPCADbJr+Dy+JnpeXcKkKVvsodKjQ3TykmSw7VSojKMxXsAyLYJX34CT7P0EceBaOa/wyXMbIhLMjufVGZr+/ig7WLtbLFK88kDWYzy5xy4Ne0e1+40F8Hz6jOya4LVwXp1WweREvTlBBtaywLEIgUYMyJFRdJ4/oqWJ0PVRBoFqY10kD9EOIbC5RjAePYIU1U0zCmug31jaU+8NK80jjvxz8dSOGu80CFvopCADIb17IQ6Bs6Ls+3gMkfUVeHQFkocCm9M4OUXwOTO2BjHG85aWyAIZjN5gjYIY/V0cSpxJajG28SdxMBICwEWHjAopH09ZRWsBd/C4eTlu6nrjXAhYWxj6Z1rdZ2lVx0HaLf4MIuZFcp3x3/jiPEHaOUGXJuEnWjSS6DvQt2wxbgOdYXJChe9Gb+NqjfCAzHEg9jivamSj96dsHrcoaFyn+SIAU+/OOs3xKV0U/urEFoMZjswUnWCbtE1kfFynsF2TwQIf7aGwqofDyJNidZvm3blcRCZEfe6DG6qj9TpLwzXmnzBd3o+3FFk7Jjr6n+liROyfxqXNsNOEypMBUZgwiXdiES/qAmbQJ3Jve0MuxAGmj7B8wZUdvSd09i/Hqseggt3uE8XsHvB9gaBqSthmNlOqk1HyUA1txo3EHtDiHyxqO9KSMAhyomXwQMVxr3dhIrDEkRexHB8MUkyN2Div7ovWnwJKd0+HOmqp3RKHLVIa/dKX3nTQ18XGV74Jf1FDCTFJGlDXECRaGuhCd25YQCFqhylXBeywQJ4BRpuoH8sYMf+QKWMsmCLEynFEncOLGMTqAfVuMU3oFA+iGoAA59lx+hBR23caUNN/GZhqDFJYz3FcpASPdggdjKb3HISq4zyYZ5+HQ3B1l1sQTY9ZAFWHOSjP/hIL9C5tE+TREcDq/suraMajr6Aan6GW53ivwVI7uBsGTpSnVLFSnNIwntL0ByhTpNTJ7Eht9zX1NBNkUUs1SzZVPaeKR+00cIfRug7Re1xsul8ND7KAIhPS5UouZIBc2ZA/G9Z0lgcVu8tCKDvMgbl2mfbhSLo7FMs4QygUeEGEkM2+fXbD4aaLR9bnTqkdfh3imtVtGaJ8R8pp2s9MjEiN7xW2lSLdX1LfiIru1OqbcrXKnU9+O5eZTxKj1kvmUbOLrqpOimaN72gvgmdGxCQ/fotz+kZmhsRHklUWyqMbTJh4rnEgJjP+ujCYYqLhTVfQ2fIbOGFb0gK7bk4SF8MYHgnzGATnhbVHokPVcb46r9bQpryGZBwyQPVQpNBBF4x6uzvghlGP4rAVq9sybMUjXbjB918itWxy4E93V+K4qBGfTnqtB7/pMvrO1C+jWBd6DhdmooysvdEXKwCw87Q5nlThm1RX6b9gWNvM1Z/qumgzgvC4vSNyOjzf2OCxALvDIgLoEuRLbcxWtKQEMe/GxY9f2OKbQ9t2Cr6XKQwEUpH0gD9S+PRFb1R5SuPYh48eJov5AFJi31h7YVsxLe395qSqB9r0mC8TNHkBICw7QN8Dz2dCuvoHxV0jvloSjfVChsUDQH8d5y4+LCxezPhLpMuPe/OOReyTW1GEyCGMJ7ecvPETJwzO8JwjB/EyVT4zlVkxnW7ktZV6rwbFMZpq0Tiu4LgxF6a+xkgTJPELRjldo/AdVrF23UR5DEJC9ssmg1ZZoOP4Va7WcPRTq+M0y3DUY5/eOfD2Y38pNQ66PvswMSCYxNdOHpsNmkHC69wUcSaPapROIj5lw/1smL1AsVvu/BWO83T6jAQV/aLoBDbyJv5hvBMkpXWsPW/y23uYKNyAh7xpRIfdRt2WYSMY5yHvkyIryk/wmSSknrz1nXjb29XOinD0ymE6rBp1W5EsrVgHQefVGQR4InoFYUa/tnKn2nCoxR2ADbD6EsDwQ2EUiYAjmHA+txZM/Y1ixV2I1wjCZQON7SAfRpcPNLn/FDKC5VbL+zZsJf2lG+YPxTpUVxhBfoTcHLLpfxRZEkmIHKTHNsd3TRpxQ3HCIURpu0KUDkFFOxhUdIiz2b04G69TLnZzF8+4ZOWCoiAFimFttFdpw+2MFk80HeEVzjzRtU+ibjoRQ54EzOvXU+jyJukdy4WNCTdJotY2nXoYUT7BbH3XZP8JK5L8NAqyr4UPrhAd3vUeukJvV9xWd+xoi6WjRQ6rCq1VU+oFoc4Nt54VI1LW0F+1V1cLOjvoV7WP+Ol1juHXwT5Rt9XT6GOTDq22vz2ySxuCxiK9Amx4vT1OKxbPgcdt6KwoVyA83qfDdgUyfYqpKNn1p3zVJmLy56O7uzRLI5ipJCdwsC0TSa8+r9pwkKOEeS7S++aATWxJHD76DKr6fH20WCBrZ4LrVDGtBtz1z8UyzT2vTOP6fWZpJQr/q8ItSRUKQy/6bygwKmJdKBVj1kUQV0WnC/kZ5l7XTR5W0lcWRN1hDs6117zNoet2B3Oz0WjEfvMwgsqlBHS9v0BHLhnIzYFKqM1AqInNgrnS+vQZKzQgO2rqe6zStKmPLmHS+gYlI9DVkE6DVQVhTuxquU4QUgOa1UVRKW7IDMXSgYilQq8lIF5dvC7WaaLpIylXd5ItlveSg3HtJnEwyXtIiqSdY0uEfnHFrl26hMjGyHGEFVRNMAMi7aIcQuiqAsy9y0+gXFwUaV5Xn9KqVh36S+AUvVfDScagAXYdSR+BaLXXCMAS8cfBqAUgD+icd4pKB6rOJclDScmvBBJor4YMMuwHgrrb9cjQKPI3HYKDTa9u6ywtq3qSqHCsvk7SkMnoj9MKYrE1yF8maEcekm6wyBB34PStj2meuJ7pnVAPooz4yi1Zmu+mauj96A39V7rGuhLIDCmoIj3ce1/k0Pi4ZaQXkPtX/cZn9iB7W23JtGtIuhX2G+0As9kAuSJh0+bLnfdqepFqO8dBil1kAJQdZaHCUj715AqI1e9wHLZndVuGDdPWy+UTj1F1jo9gDypBFiuf/vVT8RliIp5XhUHQvo8gkK7vSwhtG/xLhAaRoIVlmnCNBT4F/QMEu3jjRYZEC+kIjX3R2LH8TbThDpzUlqJhbjaVW/HGpSMxQ0usK2MVZ/uW3ggUw2L2CsnuKAUQOy+FCjILWXp47EA9Am7qDvuQRiztfWhqPLl0vswRrdv3bYMj8k6fk6xZoLXOXCPWnXzFOWfrVippHi8Bcprw7H7mbCFkKTk5hXjllWMHiewnrljdPUBfZjAdpJWReYOf351H+b69LeFjCgxSL84B9zaodV5mu6d6Iix+rRITFI42HPhGiErrcB3WvGbNd6QKvi9nEVwS557AOU7ciVhvrx5lNEYTxA+AUJ2lKSMlvJb1cVYsh/g15yWNa1dvKBzbEePedWaSuHE8eOwgj6dC9hilLEXR+mYDuGEnWbnASlKgYDZquxLAQxjBYS9Qt/UZ5MsGLINjNmPd3XDNbVUsYhzAeV3Ca5k92Gztd7YO34nk8VdXZbyo4yNtH6kbWfDN88DO4VrsNl6wHOmAk9qn5F4Ur92Q9+Vqt0zbrvayWdlNgnUDxfaRKZRu1yxE0F696YTzXk2STKX/IsBvejyHLVvd1jy+ln5mOqprTb4x2ke1/2jgFZziKPMsA8vzFRosTtltvKsR46Jovb/PSIzg7Wrz0rWTcQkrItrkwlwGKZWYWkBBvOuhg47sZKiDpaoE50HCmmVdhOS2hNYGETLSw6B966YUkiMZSecVXl0XWbNMc0GIGC8lFk0iEz5RVKdIQoFXqexEiJdQuFDcDY+VRoJCuR3eNqZ3ocvwJ6i6AcYLfLlgb/9PmThAmxvBnG/AiwG/wqfK36WLa7+hUGwHj/Ue3UmyjkzpPsaUxndL4rmPe4xSRqcm9mYDuGFrWbnAxFKgYJZtu+LPr7j+QVFSt7Xj3uOr+6KMsRwdmz0jomvqVqNZdPM4af+MPvet8qgenOZRRuZnsgxbosxMEQqluysL4XoBh9qf5Z4WLy2A96toVQU/y6nIsh9FTUyyhCQectYGMIrqDY9oO3RY3KujvHryCOZRmzYDTrldwxHihoanjBk1mGjBaGDD7GaqawHz3qI4KIJ6Phw5hnQktai9rvntDvHfZofdouf8CiwiFMuwWyItDLv0qCLpcqPLndMocstVkTgxXE4IKRItZIq3nAiSEAfZoG4rlpE4j3xoE7Z1mu74d9WjGWn3xdO3/BNSJC6IthhoL2Hj6yNJr3NdYEm5PVcb57FGR7tQqbUCiLSWWQBMgVTe+2v+GxmrlvOWG5FMnss2qqDLBBh1lJsEGNFBrqvbMp1q/vrrKM6zEsJoN+RjpT5FiDqJhvFwU2AQrVGSnfZZSlvmD9w6bFKU2s6utZSB5SqtKkMOQh5KnU1RDylKIj14mHLJ4fa5hg2TpkRYBQIc5JO3fBpVX5xHNm6eBBy95YBUqNEWsaDn2K15r0WsS0sapG3oEB9Wt4bXI11inCYFXj/L7eju0gkShPVNdorJhC1/w7xMuG+apo6SBFbVNA1e4AvQyGi1EvOxXzLXXgwdKT8zL2Tdsjp7ydohmbKHYCV1q01C54MIVbdFMkEHP9AcSQ5HioxytZsudvJ9hDkunjvnSOclhyaNum0HNwnMDV2kAWWdpDKhq7tJAYXLs64zQQKN4DhINHVbhEpbI9HwbGEEoYi65IS6WBTLW0juZ6Pdmwkh3f+RwqdAFDghtldCBAWeWFPc42vlrtsRwn5sCG4PUkhlreLRCqc9wdTBDkjaO/JT17UWIHwLCJT+B8GvbosQ6GNZNOuRT5lHuqs1ZXDj180zM0GEirI5YAkaxQ7Zxy3iz/Wm82YNq6X5DQ3ESXOqTC7NaYBwad51IkikExwHua4RMnsvjfdyjU8VLdOupXBxIX0oTSZPvMRFN7fOkqJ9aq397yAk1G0RAk307AppK/hVZzLgbfFTsK92Bjsr2FfZHB9UIuTFmWabaB2Jg+0CvOBbKVGRtWm0xzxJUnDMSVOWME9eJnl+qW/sEmzCRj/AJF2B7PWrixL9IlLs9bu/vX51hTMo/P76b95rgQ3ECp2nH6CeUqxcNbc1EonZeZ5k1xitFbH+6jwspjGctn6Kxq5xY2huyCvaU42QaXSakXZSZ5oRdo1NOjLUkMNSdm+MEY5oF0mxigCyMwjHpqm65bEJrG55bGp3+MdP7Ul4aHQm7df6uK0QsTJaE0jxbvIFXBytRh0J/QrwT1hCxHYR0qvfw+ShaOqjGvHRbVPDKU1pofHqP1YTvG7aqR6xrjkd3d2lWYrWZDT7YD06Dcj9IGzU4JeNS4jk1wniLVZ38mYphImOqh9vInGXJ1HDBdqMl8gAVA9woZqSUUd48vg4/kuyuKHT53VaEkfClyKv7ydu8z8hGJ+e9PpqHzn6AG/T4GsrFKqjhOybn4psMQF/iA1PxJhUw8cgf5jEAObanETE0G2en0zZ3PC+0HRNnt+CCZSLbjclCuCQCm/sdd8gg6Ds0rhelyCvQIJ/blSDyZueZMmoGr+EVZONT/YTsMaO9GkJLjY60WivmttBR592yBdNmdyDCk7pwL8Aqe+lb/411bHnpWsO2+dI4Kyb9rRkcq/xB5jBeq9e3tJYwpcQH71Rd4utji0+georfOq8OF8LfM8mYV4lDD/qZE9ppEeeZBnd8ICbY09ZuRApIQVyjcnTxg22LchCBtkSRde8AwV739D3Clnpn9KqLhQPJsoAb7pD3E1n1VDCcbIG1PVNkI/pHTHkjIOQAd58r+DiZ1rfC4MxQwuDsqjiOjhSC+d80/A3SQkn9J8rEjrLl3v17GubIkrVM1ys6NmmSN4zqty1Z5dwAeEKLmghdqp8C5SGMjKFEVgYjLmG8/PrqO/qpIN9qUh2tkT6UDxV7NOrtZU0FiB5accBKIQeDxUUviYXhB6vLbdoKqnAPASsqNsajkEDPbytVAt1E4/zztbmJMZ2h93UMGyy/UdhreihXfWFTqaMoSBYdZyVXF4rXa4t+AaiybAdlrm6rZ5eoSuU2UFjIGNehIl+sDiSQOmpaa/tbmoYFN3+o7Au9dCuAoVTtKZQ2q0GJNfpggROuJA5CBZ1W73fjJIKsuc+7KRTjEC5kUMVzqu+syRbGojgjeoRnhTNeiKn9iUixjrFDqIpvHZDa9MkuLmCObZjpxhZ29Q0w/qCjC0qf+po7ZxXG+4g7sO5/a0jOaDct2Qrt5OwgduORi5JteO5UdURR6QAVe7KKvigLZnqYdANlMNbSDbGOCJS8O2QKHr+Rfv4WyiafzSgG32Q0ZGn9UWZJqOH8A4NjR6xO8VoJhnJ2BHGvW+j1RHHJhnb2ti0myXMddrw1g/FU54VYBHhOavzqkcWTY3/jBZIXsEer6MJhKX1T5gu7x1MF8NduiZfZBCpWWD8QIZWwJ+QPHmx+FupKB1VVZHgqORFr6zITz0iakkqzc+kVYX7dx0OGqXHNZKDSGuFtJ1VQ7cGKFnHukJN13qIcL3yK3kLIkivxCgOeqW6rSja4FfqzY7psiv2VzGLTbDHVtq0Bklgd7AvXW6Sg3+vxSY5WA/KOi3Bd1iDGr6LdNW+JXgMHMcgA3kCA82yavRbZlM50EaSDNpANlm0iyysTQcniA0tcPwTp8gBQTajiXjc1Ifx+Dyp0UXUDDFEB/mnbiuKDnJdguQBUXyiQG9ySTeudUd4BvqGj3+AWfoIyxfP6pPrPtbBdfyiV8TeuYb8qQNBaYibjQwQO8gAKIMAWaigVE80yhhC6eB0N69IQqcokXSyQ/Rp/OXGxRBzHajWq3y1eNoriB3zS/gHfjPS30HAoDmsA806iJFQLNoiiGUuxTl8uoSgKvKzomy5aXo3SMe/kLi9oxwQhHVAf3nKbmq7G7Dj5425qsHd3ddiihQ1R4tVmpuv9v71lygPgjCyLU5euS26YudrUVM0UdjSMgiJ3SkFCzQ2C6KcnaBNKCyegsd02Nl06z/CznYByk6/cTwhbM/zPCrSUxwjwjKaBzJOWMg8KZbQkoQl4iEcFjGaxzKO0rELonjD3ArrgpVSNzQ8bWMowSSWhho26h1joSHZXqIEMvfb2yOrO98VWpEc86pgzF2OcujbIblao7XW33EfZIHPiRSoQVYs32jxHrbH0eW5nPDf1mwiEM+9AievOkszNLAIb7Yjvn76lnfDvqCOlvyTYHkn4Pda5VpWly15qwriY8hWtZx9sxo+8RitHp0tCXRYHOmiRRVmPWiH6i04dWgPctNV4I38eNS4YTZjSDA5mRTvus+91PnznQhSI0wuyrU8bbsKPJQC6F5d1A09cIwg/KKLvYPA+zOIJh33Ks6Lp19ywllzwLINsdSuwTLcLENIDkvLe2n9NdIjZaa9Wrsr32ymspJst3Sxah9lYGLw5g9QpiDfpGs/KVa3aQ4CNWIz8gMvq9u6emgisLLh7n2d6g5B4zTyBeTNHUjwJJYXaLaNUWdxmnX0TRtuN5GOxML2M13EQ/Yp7rUpUEFCOddoZEu0+AeL2uoQ56iq0mWOVlFK+Oh8oTuDfhfnWLiPEsS+fcczp/7kAl+Cdb0JV5ErecHOtJ08XkIiM3mIc+xDfJ3fmvrbHUHanvc6E1WdVI1iDt1ur9n8blgcgiZgW1XlWLOuH/PkxKJttWPVopbPYH1drPQq9h0si8NpxHRVn2Ez9WOcNfGNxVcND/rg6AK9QxPN0XoNn2v0abUePz0+Ts3zR5OW4ZfVqfWS12WRxYgLmen0il9CDuLVVaZ6C1Ke53zGI/UH2dZxHaHe2WoxVB4xyfOodQtIa9woJa+RBtr6KseCG5IYnoeIm8lhF7HYRY6yFBhspwiSeiYX92ipX0I1pS7Na2x1iaA9cLu6LQXdwuNodmgRGaIji6wor/5oQAmry+Xt6INqfUuLf6KVQAf3R48dba+bTtDQeYUwoY0cJhGSKAXokBwmIhpiqLWf0/wBB/rOmcPOVdvaHj3LUu+0U9a8dqTrFJadK9x7+xlwHPYanWSLE/8Z45ZFHxSNmc7TdR3qZYh6McAYeo4HKl31A+vesKCbdS2HEFauAiym73TThMSGFwo1PdTZ6faRQpgRfW7O4y9V+99BXqjbmihWgWv1e6k7xRmp0asqO80xfLCCdAUTxCtxBmE4Vi0q7Z3aSIeMn4tlMRxqzuaA5Mder7LjYvEyxVurRV6j9dKfR32F9VNRPkwRHIAE0wtZwidNWcI8eQn3rBOcp8/JPciXkDwW6otas38oG5EH3OER3mhrUeF1JmAxftVYw32DFGfGPDIOXDEkBko/FhY0aCPddMvjYTf8tHZRvrzpkRx2VHVbhh3111H2tn5iDA+X/DpKNgn7bOF/891OPhcYQThZbQyWs6JcoUmn7qaN1d7ndJUiFrsuWm01ONgGL7DqfpscQFt07ZkfI5Ftp/kCzWx0DcvNvPkCyM3cQCunw3IQzeq2WvKHalgtlnkMJw+/jL2P7h6uIHH73WY+JjepX71h0By4Ud0WIdToNkYkPrXw82taeR/HTB2T+49B8nCekyjNq+aWOsL19lcrMB6WRNQJHs0FHivDzhyZqBWsJ816YoIVLFRjhZjeaFVjEt+0AdR6HFH81vR1k2u4Qup0UHCRDN1BkKjbmkc7+5HCJ9TV++lb9rXY7N1HaBqWRfkSgZd5VAc+PvDxZHzcCfcIbMxhOnDxgYsn42IyFWkSFLZcrFZF/obHdGBjdVtx3EdoRB/LollPz5ao5RlWocE1MFIU9piugqMku4RJgV+tdvfY4tABhO7NgOSw4sZecXM6bLUxcM5BvueLzSFY+O1hY9jawKPKsDU5hGBqK8CCLOzvZcgqLN4M9Q8LcJ8X4FXW6M5xR2o1Vv6EzyBfNmA51k52ldzDRZPBa1A9+Jz4oGrVGxrJYS2p2zIGhozCiaZbQrbNGiMii3wR+JIlrqvp6d/GIVCsmNC6WH/LT8uyCH7V9it8ri8bn9dYPgPydEhZe9Y9zRe+rTZJAqvKt12abOOFuZ5Xn9IFWq0RstosSzTaC4i0HMccQn1d80Oo40aeHN3dpVnq5wIb6labnwfJr27raLHAUx4cWg0zGOF6n6dmojQjutHJLYiePW4GKMp44AtFu0GACDuUg6BC/Nc/AhoQCMtgOrC+ui2D0mN7ndd1M36parhCdHgi9uHUzV9i0o0dEjued7lj7g9o884rvxgYYaUMyA6L5bBY9mmxnBVlswqMGCM4qjcCqsNa0XAtRaePTTq03P6eKdaLzGBwLrNinSa7GHUm8K803kwNJah/GtDATILpI6LFYIL5LlgWz2G1alZrjMBOzEOxVup1EQsT4s9/wkT7QOw4d75wzsPpD1Zx+kMQbIOeV50xe/xy1NT34T4zCuEl0hzWKeWX8cU5kgyl2ViRI4YWKzcsPJ0KRgkmyfiihnUN190sHZve09DKvm+ATD2nIIO2AOIEQZ8+Fz73rlCtJVpvbygsB+mvbosiE25yayLzjU97jyRDRxIsHJkVR/oDzI0AT5/rK8Ekh/tq2KiPuNINyYSKrFzb2+iipCVjDHGC4Q8iRd3WzE4Lg6fm/RYdVTqE+K/WIF36ODC63bl60+M4sK4374wVEGk0U0ZqGKfw2bUtlj+iDsnT4OB2J4so4GrCsAw5VIfV6L0aR5Lkx0lyiqYs6441oS5Cx7YLoes/UpBFpPU+TXxdOwlJgp+CCs9RGzWbyxXMF/jaJ8iyl1BcR3UNknts7Lw7S52TcW5qvw+q/RdjbWtZ+RU+VZ8hXuqBRy+DyJRjPEhOdVtyigWfxZAlqROHv46SxSqOPImqdox5h+XkHiYPRRP08kX7mtkbAdVh0WgWzSxB8Dv5PNLVfbpeY/iL9qZtLLzn1TV4Pn2GFDV8USFE/W32aE8cbsdjULH0LbWnj5camseJ5LA3Ehm2cQba1hEeIbKuGPTwkLyViEL48CqLaavmKRa62uYR7YenTEaSgPaSa3qZxR9qOAs7L5l1ie8uY76+AC+YBT6lFQ7x9BdaCoQHqaVuiydZqNAiM7GlwWb8WKXrTMFDN2LlzTqzrSOsM+uKUddZvAV2WFkaJn5JMthuzYGrASO6gGVaLMKj1GqQEXyBVyvJpUC8JGP4JWI5XyPdZzrP0zoF2TZLMrqLVlLshq2hFl0MoFFesdCuwRnK/X9qscybbM7y3Esskxz06b/IM3SELF6RHgQHAX4jIDyIZ3VbcTJR+OVTkOAh7xHMl8ppaH+upqdK66QURv1MSqWPsLJuNuAbeaOGEgSpBjRQ1cvIauiz2XpnI2QRHQSJuq2LPj30uzARMOB5P4+T1t7VWBZVhZZFFs5lPKoDn20rnzloNSEB8IfAd8NeufwMH2EWfMfovihrc8aQkWLpzhB4vMYNxtx6SBQxckCU8haBYTkiKuhffoxDCMTksCxhOUVbE77SZLrhB/9o2lfqwi52kOccP4FKn/zXh3R+VxdUVxa0VxXiXVG4gt17Zh5pgvO7dNmUrcXa4TmIfHVb88ST7mGWXSq2wis14fMbCsOBY705dldzpJM0Cj6cg+tV7X8HttGsfXIDK9JlFpOHP8maBTzPr1LUJBj8bf4RYBegqvDNMVRQRwk06BGOTovrtNY+hBqnlUmC+L/AGnQ3+HTqfrzGPkAqVHyC9qaZq8j3Di5hjkTyUfUzXbSHLEHYWiz/VeTj06Ft6mcJ1p3+L/Tdpj4ytZ+YeQsb+XGBN7gI8qVMi5JI5aDzZDywa6BLiO0zF+o9uVlRO3J72Wa4DoLZFRnT2LXersjz6iwDy2po0f8yh76laLs6ogma3OwF0ZCeFXYKvsDVLSx7uZoVt69fEW3999e/CNPFwOLbMsQLgAxiOFR6p6/UPnjTwUpu17QTQn88qqoiSQlhOhz/aGADFxwNb3B3uKGd5otXramqrrKxatuZw9awFBpxIaJ5ukZURqz0++v/UxioVXPD2fCmuQ/FU54VOGkN28Ivb968e80rYt/yNizhFY5xwMGMJ6BKwEJcWoiYC/ZLN1n4gB1JoArxTZrXoqKX5km6Bpn1UDgMlsoi7uHQFl/yAa6xlM9r6+mz6UR/vUvsyNAeR0ITxX57S3GnNdPe0Lf6bFiWqWBgWE82ZZuQMCnTZ66NreJS6Uim5VHpfNl0gb/uOTenUouNZjE3sUcTZjphS7cqYWfNitkqbtaPaDbRK5lTm75Q1WZj7+688iIlr9XedP8reVoOLmNkFtKFgxVtSNhWgXx+ltUPYQI+1c+TTQe6KlvDmF00hC3T8K9Ij8GY3MvTYhvbz5jsEGZgTHaerBhz82D8LIzZH1IYZSUPKGPGzZmJPRsKeO0lI7GheMxejKTqxAQspKLrLki1D2nVqsFHazQp2A3YjSaFlZKRdJVkTNXDuzCVtg0Jg1kybhBpupXuQpi+ynhkGVqwF/sRaNL/6G7VY63TgiyyWi6Uibpf2fZP5hIS4cyT6CXWrEg3gYyzIpFNP/r6c+txN4hv0kdYvpDAIoOGxQBrdDgP5Y1FLeM1up+jbZ+63kynhUnpbMVVVL25OWtQB4YtycACQgUNh/noZ8pmHLbRrbEUlKOYjkuV82XTBTrhy1ZwqaUhy8OPzKM7bMuqxjADg+6gNYtzFt4BYhSVRotWBixjTBrOhSul+OewbnUdmYCvdHTeBSu3Y2pmGAxPGMSSsqJGCvrynLFJCf/p2toauWgc0XQC0jifNl2h620VZ1vu6LI6E/HzDu/uunHMxMA7uMuT5IxpvvwC6/ticXMJEbYUB9KeYN+A1vloripjYraWC/9atCezo0hhRG/kVY2UOjRtj4hR4E2PXtlpGbSULjSgE1mkLdhTYv4FrR3BBEtZO0dWpmRbZbZF3D+qbWJGDk7GhsOtTXsG5LHas148tVzRhwmYR0HTXWIbVvSYppldLXFZiMUtYSStlIzPTtL+TMhUUlrbtM9UnO9AvL/0unkeQ8UBIqj0UHy4eutwKC5iljCWHdP6jP04zTKktfQNGLvJwkenAofenhTi6vKgRnejY3OH2tRfvoKOHsN1EXeyCM3INjEF2edXoEyjmEBimebLaj+kMjnMIq/alFjtZRpkXpCebzJOqXhIW0vGr7IKLkyrb1DCuZukWdvGuVZDmYB9rebQph90YrpZePg4K5YXRVWb+VaAlPFqD+TCnyLineJJZfcn4EPlnOwM73WP8d4MjKPjEh5YxYHDC79uTCigl/ChisG3gw9VI5iIFVXzY9N8X2duj/wlfEzh0yeYre+aLMc6L1Ngcs4bqmv89EJND2e9qXW1374b3dZxtdvApnPkW86zg0+/rTgb++Nn3c5ruDIrAgKkjKl7IBcmFhHvlCKg7P4EXKmck51QBHDv+71jYBwdl/DAKg70UASk6CV8qGLw7eBD1QgmYkXV/Ng039eZTxEosswsBRko6bZeZE63xlmEOyX5pF2fYi+WzcFOSDzc86O8esJRAIWQKIxlCgpOxWctiCu30YhlmqGEgbeD1yQdn4jbJHNhpeIh+Fl57UdRE1dVUpSLG4pjdOyhqKPiQRrclRNVTSm4Us7u28GbhqFMxKeGubPl2bbWfJwLy1Va4QxN3Uh6lz8eqPpsTF9Nyr9cDSf+1bemORnCACOcINoRSQ4e/fxwJmKcPuOsliA7aup7zIttBjBugpS0saotI5Wuogv57DqwU4eOTkOaQEo6zbFNf2Y/jjwrymZF3K5GBhdBZdw8QLmwrgS1E59GCchRd2ICzlITd3fYiKRBtuQjFlbJSF1iZkdO4pDPxEryXkzFS3IC7w4z3ZB/2yfTtJNNASrZyJmDaKQS9qH6tnV7pqr/UzGeZD5smt7U2gYh1nKNhZBphzyG+Goxq5hvS/lO0vVpBR4zH9Z8twXqF8UvZi2JGnB8FYxCruI+KV9vCQsqxjCpDifOj03zpMJsrNi+82sZSSwDlrFi92SHPRtKEdsHEEfS33S9mICRdNS1aZ6tOTNHGe0BFiwiF+2mz0Pe98m4bicthj7P1Hec0r9/0XvIK6biEm0tXaYyuoJPPjd5g5rUY9vHpVZDmYBprebQph+zpyqTjqSVfE781C7jqbi3bU3CulKZvaV8ywxiLqZl5s2mE6TCvJs7CdPR8ygHp9zeXeP+eLy7w4KKnk+1w4tzsUvMZkp3IkCOwHC7mNBE2ftJ2W4Hk5d8TO/qE4ADIlCX70EFFz/T+n7DQSp2MdSTsWVfxYUrTc2oxKKM++Pdbbfs1QS8ZzkNVpwoxTA7YzJKxMBCJn6R1tIxpa/WqG9Qwp6qVTC/DLUayoQ8rZ1Dm370dbaLh7/TS8yNkZmqk3Ez2+ruKKL2g5mLqaXzadMZpuK8auvXooY2NtIGTqmyYhBnlZXCuzusqej5VMqqOBfbbyNdwie0fC4KhKDq14/R966rJGNDCbwLQ2qb2ykvvc1IJuBWm/nbCQ++bCB2ioCxprXYk9o/Lg35LZigpEc4YSW5NacnEgumypnpeu2Rw7o724u84xOsV/k8bP/m0ve7tZl7VjHxBQOtYzpXX5y8AVkuPwVXbw8LSocwISdK58im/QHBvOEFfcZeu4gVDjpisAGPOSTpXZDWrOrJVLqzisa2PLXegsiVS1g3ZY5fvYI2NyPk4HJ1gIJ005ylTeyYzqwbwyTasm6edkJP3nTaUu6pKkS/tBdTADppJgVp9gSU7V523OSLDGrPZjR15FoKC+6mqaibUp8fUmMYbbOw6NkkaohxKmx6sak1o4rMjcS4bShrjM6Eu7l/GIcxB7/u5C4ijMIUVqGqMDqn7mKshWkQc7DpDkZesLnxTBwqhTZmTvROl7iTjKkdwQRcqZ2jHWLJqzVM0rvu9v9RXZfpbVNDWxbV19awrLyiBwsberCDLG03oulY3G6Od4Hl5SP5tsZ/3ig4Urm7u+OSKhgaNE7Khkd3ZD5mq2U5/1IJGO4UGos/b1h5FqUYtnQj0TK4n2zXk3e2LUfbLeu1Jl/686+48LHPvmPZ8I3/+mvxzL0K+235Giw1+eFEWM268VghDGa1CoaK42U863D+AGUK8nqYlpNidZvmBPAGrZj0EZYv1+lKk9fWDY+GcBoUHjS17pDs7iU98NG8sJ4dnU4uuM6pTc/oenMvft34LA06CxRbwfE7bN85DGs7lsYOWnoWo/pHg0oQZ3zP06BVQePZiqXBdEiyPpiBz7kZyDq6HRwvm1ObntH1to33fXcAD7EfgaH3ScBvj1TfI1HeD8XsqbNFMA+Da91xwhh3jeXn8L65zrfDItgaZxs/uB8ga4YRCtzqyJpaZA6rhOCJsFT0/VEvG9Oq3drVYzXg+VaSFX84rCoexdyLy8k0sNX/fbxoW6XJz6yu74VOfnMFVusMfiie8qwA6swGcvDI3MUhl7kN+36OzlvyvkzIXXJKW50GMDVn47DrFJZo1AlkXhxRcpgcXMZhA6QLjynQOz5/Qvhs7j1aP5QJWFQ/VTYdoOttAYOaHBEC5AhsuYvuBWXvJ2XC3XUhUOH47U9Lj5iqnmY/Vl96sNiZle3tErNajmW6Ld40izY9YSpuETu7M/J0LOzDvDF1S3VXZuG9fRCfg6V+lmY1zu5uvqbmgMOKJ7nqQSyq6YrPtbYtlL3mEc6xFswsYC+T570uVxclVozSFShfTp+Te5Av4SWo4UlToiaSF3UUsammNGYYV3IKDja2IjXMur6PI52t+zQBZ1rPgp20VqLZDgYlf7hxJlMlPkuy6GfmRWlnpmZCKcEduI+pPxvbHYPk4TxHfUkerprbKinTNtbWeJvYVFHGgoo6LkxpbHanrhjbjmYC3radTxdn1tZxtckIM9SbmKd30adgOZYZGXoHrbujJJO8267kYjm4jHkHSBd2VaB3PDiYn1X145iAQ/XztDOnBkd3d2mGvsAbU3IgAVLKkz2QE08KmCd/xE/ZhSk4SUVYm7bnzn5GXq90Uz7VVZQPlPpuzpqmZnroe25N0Uz8ndAR0Xgf0Yr5glgfLOHNWVmsjHynqSN3idLgbg5QdUPTs51FbyZxThqJb9MLut62MN914cp6mxqjMh7VzOxsJ/ZleqYTyW7Th02tGU2IOn1Ek/O5WN5Qv/FkauwIZR25MTGAuJkT6lZkShzX+S00K4zjmcS2MM6dlW7IVt0K9jWKShnwSAy7m25H3Qgm5s2dVB1P7mHyUDR8uL3wWZ0l1xKBNGuutK5TDl3b1mVMLYxx67jbdXgTcLzrfFutAr7yjOnLk6ZEpFhegBeS1v88TzFe00MqmlryZOZsBbd85rrG7F8SiaLXWnVmAq60mgGrflD1toYL+zdzBLax5REVAhve9HqsyrJ5Cbealsb8Qtl1dDOwv2m+bbrE151tNXwuEpCl/4KLvo83n0G+bLA3QMWA6ioyjhegXXhd05SEu4eebx1Xm8cxAR+b582mE32dGTnWwrQzmXSOppyvCbcVt55mst52xGo7JTdKUZ0a1YBlH2W3AmX97fafMKlxEXxG852QjoE8L2qC5e/fK3iSlZgNqt9f12UjikyM+grWkuur1etXbbHyDrDAjDwy2MDF6QqkGdLsQXKPtxE5VgmgEX1/CVKGcXOX1L6Php4ZUbX9T5KikY+SLjci6zbdL7C+L6QjZADM6AzR0NIWjFHwro1atWKLVoPMzDpppZymvsyI5ATUcFmUKZRh6QpfzKNJk7oppTi6IluC6DAxENbkwe89pSVULVsJmBk1lcBQipPJHWk3cO1EsDAW89HCfwF5cwcIuXSLgwYzojbhdELW70FS5hv2egOSLl5CSrghxMTUEcwCqlVAysxE71/AUos7FsKMEO1/+ObcI9IEpD1jAKyJTTZa+WLgQIwoj7NieVFUUlx9mRFJr+nKkGxMDRMSrHTDqxrbepewImqQFKEEzoj8K3yqVKK/LzOvySLLjvLqSbEUh1IrRCoU1kyAdUcdB7QhgqaewHKVVhXS0dq4OWmvOBizBvKMtuUcZEdNfY8VzTZntLoBHbyxMRI7o+LgodAOzXWxThMlHlJqgwioMNj14mNZNGtlL0ipERFxHMpwdF5Yy533Oz6ib704cskqATSL2Y/pHdoIy4URuwTQHrsOoR39VAJjKLRD87WopaMbCo1oxDfI5fSSvlVuseepNKvN48uWSFQUY5+JNo6WeuRTPkzmDVZj39iXtOT94188s1PP5EnxNYqa6jkG0xg0yfel49G+suDVmH0ztsQj2fqVlCJp8u0QaZLaahrQ5ib2a9ihNdsmbHA7I5XmtnPofHc+bmhsyJ4iw0xlsjHq0IXcmuyuXxrVJXIfUGEYDFcsbTrxBRAhoexLV24mzD0yUglNb+UqHANgVuHl13KkGr3qapWDyXgNV+sMyBlSBmfrwHjRIeZhrGWMBicHYlYsYA4Rx2pXJA9jNn37GytS23dzmciA5nupQTMUmtk8uYeLJoPXoHqQsjlVbh5af4VCOrTNnRQT70FQIX76CdPlvVQdYABs0X1IkT5SKZYJD2Ont5sWn+TShJGJ6VBZOQ+zYclGbhvixOT8RoXv2aMifn09ujaw1CgIVmuQLqXE68ss5peQQiulWBAr58FnWCMj1TTFckjzwPnAIykFxDgxV7TKfV4VAWfU1GXn/SrbRBHP4diGDW47XxN1wCvvsuRA3thZzFALzdEAC2GewrKoKlQx06DkYSxGL139Nqse/apVKlBbZNZ+wLPWU74pN9Nb6bCR+2qog03D8eDNWYo1L6qC7oywheZPYDeH4upKw4n7MDL1eaVwxGvVQH8GTzVAnVjy5+gshdyod8OeLGppx8JaDYypYqCbNbVYpBJacaepMelFzRRzvmvLdHQlV9agSTAmC9LtSKirmzIP4rLnizfDOaNIUQWkenjyCjLaCcegGqIpsEoopUIXgUr9rmGmkizXh3Y8XJKPSFTikniIWGNQqd+DdFwkwKjHwIPKaEJvixpyCKjGZZfexX9ztF5nKVxcF3Q/RaJo4dWj0lWTEYuKndDQSotVQjf1FESh3IY/beg2QLuMr68Uk2YDznGX3NCwPPBDQzJpBYsRyupFIJwUrUwRk44zloBnX35Vi3fNC7EyMSx/CNaCI8zIZCRiA3Vi0WaQoRtTQ0kfEdY4LKGKhk6WUl+JeWwpxjds1hcEUPtRmTUGT3JNozTQ/m+d4iCFUw9GBi4jERfZpSGPFOMkaifTMhtqpmQodR0jAyiraljMnozGRiQk1WKPRFfzGpWCuw3UvFbDCDnNkmUj+m4uYVWXiOnhggooFKloUUs9WHNlGU2F4EQNOS1akG0bmxGHk5UObLwZAjAllJQCaoYmg5fSi4u91JFLinNkCvV5xTS04UHUI+AgZfSggmw1pOARTUQELg5WTQoW0DwOdmqDycKikxBHz3U+foj+YhDVUYknQoTSOBAEYKk3YhPZrXNGiLgkVFESOYQgx2mWIRk3BIhrqMKBWgyHrRGBPhzCiYjUhYFvrgFqqCTAmkfFV9HRaRO0bkEuAbFMDqkmwINesqDyzf1eCdH0FdQD1NaTkU8RFq+hob4JCSGp8P1gQvZ3BrTEE4HUoxFgZUSibjFoCCOimoAYJ8WKnIRsLlPI6SHA6cfBgwevPClSCX2UpPY3XS7hYwqfPsFsfddkOZaOTIHGijHVNBocBgTBVLVsRiPdVM14kLu/6KJdmyKQenQCrIxe1NUbDaFEVCOvTdxgz++bG0Byeghw+nHw4MFcJEUqoY+S1D5rs8gyLaOwAJolQMNJnQXtbSjdKmJQjMwYm3tcN+19LPnQaRB9zylI1fCHW2UGItCoZG4RGSU9SfCjqIn+QLKp03fb5ORQgevHo6gVLnY1uBV0U0yBD/W4+3I33I09CQH1NTTj1FaUcpt44U9HRz1+zbbVjTWetWckogLSwjQzEs3R1JucSLoLlFrTz66ieshW9WUUNVwR1VDZrsmRzcXhXqmWuhIo9bhEYBnd6NuuGiJJkE1BERJ1aSYJB2YYBgutJEofCmqiCoduCrLc0Bd3FTShYQwjoEAV1ABmMtBIJDRgLiLHZI7uHrSWM1oYq3lsxxKHJ1pccmIIFA0SGvR1cp3YoOBs1jo1lhiig0Kn4hA5gT1o0ybINHt0pXDqwcjAZbTpr8tr6CJFNbIXt21TJ085CFP3dVLUmgZTyU5ZboGbTVokdTiYvIJ6WNp6unAwPoGBRWyYvA1NbNholOwyQViSUZJ52Dw+NvHwCARkkw0bmdl3CRJ/i5JePIhh/WwglavQ7C7jMU1DAk1YiQhk03lNCIkzIaYJF+kziNxcoB7fgwoufqb1PZURRCSNqYp6cIaaMrJR2U80VDMhVrFTLOeiLLHLzSY5i5qG8grmgUrr6ejnIJn0bUhIqZyjWJT8Tk+oNTnZWo7jZSqPSVi2odHlHs7PYxD9FIhBTm0glRKvSxZkkngUpjFJIEkopFVHtfDqIemqySilyIekIZq2hZH1WFnbxhVqruQ2WM9l44jXd6484zbJgZiShByEelwsoCr20nwFksMz5sKkE2rdbNJ0qYnAAprHwMDrSGJW0eQoZbFzKip7m859HKzRo8ADmmxgDj7InuZxjexUYLKnGaS5FFInIGQV5FKBzfGmlTpSpCML7U3nzSykhLU4K7JgJMfTp4nZiU+dd0On0JbJIzW4ToQoa8llk5DyTyuf1MjVxiSTTjw+FXXrUg3sMEjd6gyk31RrVGhY459QwjqMS+OtCKTYNL4LNubMfEeIA9SECcjg48bOTUsieWJLC5IZKhoHq6+vuWqlTNppJrChzZGvX2kyld6o8otKlrcHGs36dMcmFQr6rK06AeHRAZlCbccU0deJPv2s6+LRYvPlbj2FJ1xm2o5Yz6mCqQKSBzBJepVTxoAZSUBDB2cOoHGpZVTb/1hU0eQMts22YI3CSAFbTBpK6zMnmyfBugvTZXbQdcm8f9vUDqKKeScfc04mVaJ0HWEf8PKaDgZFEFVoTDNNDNMFaaI05kG0sabIY4X4LwuPteBF53m5vm9dq75a1/UevFZRHYPaWsWUhxmR/u274IpCh7nQ43Emkxadwxz1KX3dJ0rfA/WkGbkkQOm03BYcZb+tgLfTO2cS1TdXYLXO4OZFSTV1OEjzkNgKwRTi0I2cgXZ4SYK5dyGjjwJSPSB5BRl96LcuNBRSIJzgisimZc0WLwLZDEWzjTuTZdKtmvLztz/N2o+yinFVqGpqVpvmiMGphamJ6URGHwKOSbqZiMY9ams4RXOp7jB4NRYrUouP+bpQXtP4ZKdw+BUfxAHpCpQvp8/JPciX8BIgU214J0jiejZW0vh5TXXlia0KU8ZkM17plrN5KSkuKckf1jRkoS0HyVSKQTUW4djkUjzMpD0GNtZRj9RUVZpjRfnMlC6riKmhkY+IVe1rtihTFffBararqESdZusaHqUy6tsKSPXA5BWkWduoZ7N0advkCCfQt4f3rW40gUMikGYoPKyULNSTWzqyCLhGDhISnr4yXxJ2FWnqSsrLkPYrToN8ZAHGPvB1c1YWKx3pdOA6XUxZS674cc+SadU8NeppSXddOBCOArYe26ZOZKJRiEcmGfVi243weptMwKvBdUJZWUsu6uk367TCXo1XJt+EB+yikk/HbFI4u4HpWMyLUpMF1EqfmLuRvGonCbC1rasesi0KadSa6h09XTiubXsyskueA4wQ9s2+kXdznqd1CjLNDRRdBfXItfXkseDCC3/acHAd+nEvQ0lfMLwRXx80E1NZ137gKhQ2JLa9NGXZooTqxkn1TdJKv8doztAqh1YPWV1JmZuVf0ZSQ1AN9pGT2pl2JMudyLQDGXeeMXac3962CHBQMUhzWA5lv73FDxavQPfht7cIJIHrugHZl2IBs6ov6F/23tTsvry6WoMEi/P/fvX61fMqy6vfX9/X9frvb99WBHX1ZpUmZVEVd/WbpFi9BYvi7ftffvkfb9+9e7tqcbxNGLvuN663Q0tobaHZ5kpxuvUFPEvLqv4AanALKkTxk8VKALtagbL+dvtPmNQkuvqZkyW/DVTuG+wfJKSPK8WZw2VYP+pr4d+diw+3SNxkb3DX3ugOZTcUPUODxAKBjBcqzk3F6gjBFVo8oOyXTtf98wUiR5E1q3zzN89/6tr4X7Z++8UewwdIPfNLI2IKHPCl1ToDL20OZw4jW+SMs9vpJSi7EnuM59UHeAearOaIv/ks4vrtLccBPK+9FZiNkwA8B1vyt/QpyzBGl6D04ngrPOOwPtU4j4grssfZiTDMoeKCEArt8V6A+p5F1n6xx4CfwuWH2X9zWEho9rkFRL5MKXC+pCvY2uM0ls3XrVl4mxiPkLWmimKxWF7qquOsqL69j03K4WFL7DF+r4Yomu9lxuLky9z7KaAMxHec5gC/YyND2ZfZY+2uiImszhS4SYBcWH2br/aYcAbSvBLkG/XZZSP9ihPcs5so+eSC47oEqGVierKYqAIHrlsv8BP13/LvdcLxHFOyNZKGeUU60sYetp1PJXIuyhSZ8TW36DZfHZZHWay4pUG+uGEQt7fNV3tM1wWLA//tUlvsRf/NHsslXGcvfEeGj854xC4xBQ5i8YSThicutY8TbkWTDw4KXkMMTE6x6z869KNYcDzbfnGgQwkVQootcRgb3lRanxgzus1nN1ySrlGfHbYbYh8kJPkhLy34MqceLvBriyDLXoROUiVbI+TpoYZJeRqTh5jXVx9Hzrebijj1Hl4AURIxBfb4PpHHjGhE7ReH3asoOQztFycFuRSVus1XFxOzqp6KkpulzVeHucrBbQavKn6+Np/dDIDWo4Kk2gJxYwqyShisDGRr1m3n/+9fLQ1ZuAwqj5VrqD+SikY3evVS1VCiJSmBXKySJGsWcEEHMp0vKt5GUQB5tNM+EKpugip3x84+YKtsRALmYmGSHhJc2G7r3Ob8dGvAXLw2ebpqVsTRerRqE1iz/hux3AE7eNZjl5TbY29r9G8Iqwmlg3MwI5osUzrVhcLtEXPGYPggyWfA7iMMnVGOIx83HeDxsCUO/NoPRGBQusADHwnhUCIdSl08NxclvIIZxE9z874bpmh7+TwyY8fg5Om8L6RhHgn12XUZKDAKhS7HPO2pKn/C0391sh8ScWvZfHXHdAHLBE0oid+Q4aTLpzxiubpHZohyDxJLnZzQSLTScV2cM5ordbDG0gW8vm9Wt7lgKXJF9jh/pFV6y59CDx8d1klzm6XVPS/iqM9znmXH9yTtnAN9uB0SQZT7C/CJxTYeiUJ090UuVl6JRkYezCLRXdJ9QQHjvCDP80X6mC4kvjspwJ6IzkC7QKndLVZp3j2uy2l2TIk7f8EVkkeiFiopdqLv07f8E7LdL4Rtky9zceW1ddTCVQ7hYKHCGvw7fHlqb2wxtilT4oZRyQ9CoRve67Tmtz3qswNvZVnxNPhaSB5TbvRyCIcW1uuyeISLS4Afu71quDM8SbGDNChqA3o5hHvvr4saZHL6SCG8xqBuRAnkfDB2XRwlnPbFlthj/Jyu0hp3qL+4TCMVCh16+tBwHcQfXHxNeXMHkropYYl2tPorUix5aaGCsW/lI+ImFmf7xcW23rwnxBrWm+8O/enqyJQGvszlcPiPJi3ht/oe5z/pb+iyp8QyCOcWNuqGHD9T7rB+GyS3AH6DFykaR4sFh41fy0ZopwDUIRESF4E6fHePYOJnlv7uoP7nWbs8qSxHjBEgKXfy9bbL6dudAr8cwp0ap8/rtCSxmh/ASyWnDA/j3gq5i0YwyNaWGspBuwEVnzuLUW3EYgdpytTkey+WOvUan2MeLUsIRd1ULHU7NB0qXuOLAsLpKVfssi6Hiz/8wqQKXORXV+nkJcngZ5gv+UhgOYRvCxewTAthHlUwHq0QBYOgESSxDMKF8vgwrD1lFoQiU+SC8wzxQX/IxiNly1wsrkWKVzLI+ton9/gmEG99qaCcIiPB8+kzsrTEyMhNgQu+00qgLfnkEJ4Gnk+Q2bcs8NkoF6XGFjnpZMg2O88f0YpFlfvTUEEpkwM5xfcnD3KPMlfk7NAj9Y8eQZqB2zQT0Kuh/FqSD0IO4XTKq8EuljpYA8VTO/bu5jY/ubJyJyspvXsh/o6zouz7dwyRbSpYSmpAh8MOkDwU2IWAb+EJhx18oaO9rU6pIxje9tl31G0SV0h3fC+fdzmEawvtEb6uBR7CkW5w0VUVQiAlxU6rDu87x83LcVPXwi05odQZ88+0us/Sqtag50EcKNPKmgwidr9AdpjoGZNDOBw/IGuIVE0T/qIDU+IUFM6jcsbxLVtI0Gy+OntDT4RoQabAYQ/C71+ATNI7tsQP41UNSnzRFuJXCcT4XS2kX4un+cKqPR7OgcM69+JpjuNQZIwmA3CObWKwQBn7aAGdo50s2tMBOllidB5bzgzTpbhV40T7NaykTwFIxmEEdhpLf7peHa3XWcrbClIAe/w/Ybq85xZ6/82BOhI7z92y+5kueCTdJwd6ScbzyXk8wx6hFy8aMI+2dIJFCTTnQXzUMAGYQSGmavi4m8EBmrEOD+kI/jOuzD1wCKdjEC+Is2Uumn8Fu+1M4qgQSz0wf4GgakrYPiEgxc5AeLQgC7EVCj3w4h9a3DSAa2gZObwUvX1CoXPIGixxzJ3cSaQA8WwD00DfxADhPYrNw5zakdBgWxPGM2zcYTfu+4g+90AeddVxInnwv2HxKn2PpWcPXJmTh10V28gVbUvQZF9bJn74Mhd/YafHiJYNU2KPsdNXBHz0d+fT0eqkaNb4usJCuKMrljvoDgqsftj6WSBH/0T5V/ErC+EYcdCesbVP8gkRB3Th1ki9zmWO3VIhYq/3vHuIPXXVbRV7OF5OxLL56qJ8xs1/dVzUyFhXYpUUu7g5UyC4NsknF3ddO9eq6D5Z+SFMTo+rDYxVnYqJpQ6YUxKEJKDcfHbp5RJepf8Sbm72Xz3DAqvror3mI8dvgnXv/zfZcYhQ6OhuvsQPjPA46YLZQ1xHdnztTgDgNrpZ9vtWh5p65N7+jxQ+fRG0RKFwaxSuTnoG3hZpkfjcFlHVHEfZ6pqTJTfjilxOTFxz+CnFDizkic2YApcIl0M+MrHc7zpVnHXC4PK/XDX1qol4NTamEue/0czs0+tcEeHJayUIAzx9WizjsFbftMr94YeNGshlk0FVohYLcKcwALnHjSlwsJ3TLMNe6T7zCmc6C6UuOnkXgilHLSl2ccK1CUOwNsvmpeGdcWo4n9YulFciZBBeLbCpj1StcFA+LfUTILXtNGB+s0SEgW56BgAHx+5zXQIxvzL1eXtEMnWmGiiLKUw+QlhbfVt9j6huUX6CzyRfCu8ZZ4qct/UtyZo/r9oZyR/OYvNxixsxbL/qqfJJ+nkjz6szCLDO2u8swo0Kvnj/uZW+KhqFYWmE/kyrx7L9jEv3X3IRhCk7MLAXA8fj3ECWnYNXw9WA2AeHNBVUh3MqmMMBnR7X4Rht947RDsdH+3J8NFdcTbdauvdWoihnbWYVf7VMVX/7FTKJKHYWw+3oxRhe+rsDNpKxh8PUfXO5IJGt75rsP2FF7q3zNyW4Qme8Xws12qEs/pIxcPd5hX9/u/tvQhAZu2b+D59gMhbFVLzeNytYutR3B0NhfbRYoI2F23aozy5GR5+cibc2Nt//nILdUQYfZ8WyT2fnzpfa2iPxZNucKPCYAofzETSEi6IS0+tS32efJWan6yRdjpdM4KapRTXq/tm2r9hFN4X2eH+CqhsNi5L+Pvs8foVPlf9q09Yed7WJigpbMt/6xUSRJYqnv88+7xdFlv0osBp1CRP8dIzHujWiGGm9onaP8upJ1ALYkq0zUQIj/xXvnjsoa1NraeITo2yJ01WlSC8oxXijKt4rTH2ds6JcAUkqZr7UHfMV4J+/ZkscHFnRUxDHTnx1dHeXZqnEo8oUOODDaYp4F133bXr30XnVxu/0j7pxFOMKHaJmFEFEftFCnwHSUOV2FVc0pzWEu/K5WKa59JqeWOqGuU9vpUQuAGzNXtXNTuCpfD/H7luVsuY4O9VZWla1yPrUZ7eZF1Ftvk67RyFxvAb5i6DRth9d8Ehj6fzi82rE7+jbY5ongowWCh36KKQaO3FNLday3Tt+X+u/OmN6L8X03gXTf6VrbO6DTLybyxU5aAX3RQ5lubCZAof10yfx5dbP5vMUUn4uvZ6sgeAbve1K8lHrVTW39TCdHG9WXYSv5ORzU+SKU57Xgy9zOAd5Kj7DukaGZFWIy08sdcB8X0Kowy0pd5ijBhlXaSLFzJd53Br8AeSPanclMx3PRj9WDT2Unkkesc+MhkklFpeHcDIh2FYZFTvgJz53ni9zROo2zTR/csUWucS1T/Ms7xiP2c612mh9NXCx0ah81pq+/liO+HiGQQTV4va2hI8pENctW7JrW9RMzN2fNYbxdY/F8xxXXnUcbv4M8mWDE89zWOjv04bRHBcLzqhtv7hGHrZuWemDXkORA84umKGreyJ6IuUQDrJg88yVuhElkMMcgSVHlPaLkztjq/OG/TmjgMeKKN0590QvusJkeI/FQ4arq26r8t/3+KTJSBoC6VYwFDocX+bpHw28ghKrnCtycL1lYHm+Qv3BCTTEoUuKHYIua879TD7Mvwb/1DpZezf0qi5JxEVVNGWo3SHD6LPOrdDsgt7W915cTmyJO0bJNWKuyOUg+KwsVhdZs+Rfn2RLnA7iiyYR1hX1eWtWQR9FFcb5PRbPmDd51V3g8BiWCXkfm1N4208Ou1eTcVtM+2WevWDbNfmDJadrZ/s0/j+nRTbXFf0h6jPw8teAxzMYVlV5nH0BtygLgJ3aZuof8f1R1Pza44r2WDfHhA/nPk++201NJJzz2gDFTmrKYheHopm0ilFSBJMN/2MDqxpp53hlSfQBtnjOw8pt16x29KifPoKNc60AYwq4WiCvvq1+PvV70b6vRSsC2b3C2FGlbrmJ2Xf5sqmD2fs4czLf0gj0tmTs8PO59nlYrtIKhyS0d54C93wOm8/+b0SxrWsw7rWDIX0ba6cNX7eGg06fa3xpKTtq6nvUaJqQwIQY3KTD7MFZbujG4bK41/1jhNX3VDlfYJrcpbyeJCt3x96JeFMjEjD7tr7hib0uHiDnNKC/O2I7ShJYVSqcTKmDRo7jmpDWqZIXsvKtWe1nRdmswgNpBjQe61hTd5xFe12s04RHMXyca/GL17td73WfX8izhVxsxY22nUvcRPiSsEWEtUHw+C4OReVxVgdpkUcxfJxtdWASyF6XZAqcbwxILws4Ohdb6cn7Fbuv9pjwCzUcmu6TiwOsqmWZWejv7thUMykrd8dOsm5L8bYlB5nlJrNAuLDylVOTiqiPZdGspXJqKNnlaxlfh72Hlyz95zmEFF6aUvWJKTgILEueOeTbHEltIyIggtpG8PiKQ0XlbfV3bf/Fsm3i7j07ryFzEbZg2ul0XyuKeuMsE9KYLCMAU+CIT0wjRX12OYqUvAfk8QZQXMuru/Le+RCEnI9Cqcvxc3uMpUAtKXacF3ztsBHwckXu/ZWjFUsdXIjt21VyxEKhM972irPSP6kCcue4k6YsYZ68iLcI5BAuLbT1LgEfT8CWuPeZPbBUrRkRysGyB7VM4lCfXfm6ua1xyubzPMlQx2TszUN4toAvOOtbGCDcWyBpp4fnBnVjkUMGtqgdmxzStcVOJGjGxkN4tqAZCw/h2QKqK649OYSnfEJyPsU6JsjOIJSSzAI8RttSYlqAx2hbSmYLcKfgDVxFuFraf3XkDznX+XBav9ok6DZFrr0Ts97T312uydw1+QIuZM+M8mUuWJ9Aubgo0ryufsISornlA/sUIA773D1MHoqmPqrrMr1taqi08/SQAS1W/7HKDE21IO67tyrkU1buEIoUOU/noHmvFRr52jnAEtsFSOgiwxeJgxPEIjIVQw/pErxSLsRQr81XN0yiCrr56ohJMma/EX4B1QNc6KmpgnHr88nj43uxx+1XN0ynz+u0JEEvX4q8vheRCgC++P8TAgmV+XI/Dv6QljCpP8DblL/dowJycUEN1bq8s5+KTOKOUkGFtCTjIDWUV0vHIH8QTS0pgDd+cbFKAfzwn5+oUeMyL6x91kEV5qHcC/v5LeDdo3yh+75AdBJp5mo5hMNKa5DOWKb/Isv0ugR5BRL8U9jYNHDhrYlMqocMb/ESVg2fV9wE6yId1zjDgoaecoiQFmQjUkM5RUIMWp5mQBowl8srZXIPKqj0wkoBXOyqVH73gilw9/h1OQKl/r6+zB0rNrhwwuOmbvdorZfOupLLgU+cvO+7cCxDm1GXcAXSXLhZoQCxb+MTqL7Cp85Y/1rgiNdEku9NA7Y1x0G9wf29QrbTp7Sqwx9Cl6D0ySlsh2acw6PBA8mvGeq7o4NCejjhuFXsWt6nj+kdsZ8iMpcEpQ9z2aEZh7n6tnks9HcHCVrBxc+0vpcymVDohleSKob6/Cdg3Di8GsCfk13h79Qxilsk74apody5X3YGx5c5WAcSb627l/a86ntAriMCQWeSAriPHRmma5mtJCt30XySdJ1iVUPQKbkiD5ySG0t8mYNeDHOs9YuqL/XdFZukg0yBg4cQVpVwS3746MJNG7IT5U/kJgnAHkvVQWJECD/yTJmlqTtiGBJuUBE6tCmaXp+8iPhc9z8aQKjMotp8dVA88rS+KNNEfjIslnpglp77iqUulFT117ev6n769lF64OxxrNzbQO32Kh20AsS3DSkZFCAOKoPxmDT0eFRzLOp9HPqheMqzAiwk+cu4IpeNqq+qVHskAC6RsgnMK9jjEIL1xWKHviP5+ROmy3v+FULqu0MIZJMvMoh3GS74kfruLF9P8I1XmYRtC7Zre/5KEmQFb88Yje/2LK875vYcvpV+FbIfffVLeHRdDE/kyiLp6eI91hRpD20cv5GI0cdtZIVlHE6NG+vdDoGTSt03VyzHqAS/9iJBNhS5+Z1k1jz9fXpbbudWED5FahOxhqyaHovnq1vyqtstya9LkDyggckOLfkyB6w4DFGmpzAFjieLUH4Eypc5nROmj7CUvxYsFP4JVk+4s4LGFLCKpnRZDG1KzsH77x4OEPnadPYfuzsaZtNh6qbML+EfJANmoPJCofJSW7T1R2KjSJfcYjNQXEUqnt/rEoKqyM+Ksp0t3tfOFbrgJdMOie3OuziEQm+86kgOLaD7vMmTz4ulLpwK7u6+ikmj6e8OXp/FKs2lIX5siQulqeUrvzqoANnN0J7ZNv2C/HmChG+MjZ/F5rX5m1Bst+S+AGWnuog3XekSV/+cDCNb4qLsbGgsO5KWlc9mtEc8rxnDN92PCrEwLOGCHFTIx81CzLHbHuRdzzdXa5gMkYmbuQ9LJ6xD7ZNb2A3fOGIx5vKTj+TbWhqQbYB1vI1zlmZo8QlBsXyZkwh9+pZ3ZFC9L8AUO3vFd+f5Dd1kBSoUGsw+yoUTupEUDWkf7Ph/8qRJ+8GJY/BgNO7boRRau8YNnfwlD0tH2NkRHv9tXFp57pmed15+gDJFSuywEk6K1W2ag/Bdw4zffx6dkI60gzw03GaBPzhE5Nb8w6XtF4cTRpA3dyDBt/NKZNZKbwOrYBzDwSRxYE5hODBf8pfH+2/2WH6mCx5J98nh/pTkIOyTe7AOqCAhguy4WCj0wIt/aHHTAA5aeFWlyxxZvynhiHP+AUZZufsZHk5RKlxf4srcDW8cJMlj5ctcAs9kTwFtvu6PawRJ0ORB7r3gihytuW9N/e2OoOgSO4pGnQiy7bvfKFtevH1uFx0OXSWlkSUrdwhvgM81+iQ+OkZ9d7s48UeTlrL7Ev13j+V8UuR1WWQyL68KZv8NgrhrLsJim3yVHWUpEMQm+TSpQemZk3m7BDe5GTqK9G7vnEYT4Qp0o8pxvg8KESwD2zVuRnWL8uqPBpSwulzecgcsfKGjGXS0+GdT1eLxulDoYNQQ40OFWCx12csuSngFM5hIblUwRVPsNSqMZDnINkamwOXGR/6Aj/ek+otQuIfBX9cpsvQxP4YJwwGNh+TT1N1+dTV2hnT8FKzqEL4vm+MY2sWvM9c5QvuCQdCxAf7pc0ogrze3q1gZAVBy7gLywYHpq+w0B7cZv1HQ3x2wwaQpodAn6rODw058VOiT84tCxbIYXFrcjsAWzbkTfqpX2XGxeOG7SH930YPyGjF273f7Cuunonzg1SE5jJMQQQvuhayWPkm+KIXlMM6tnD4n9yBfQpKeTN+YCnRrRFvXqdC7Zf3YvB6tV1XdViGne/nB78UH8Va6x5V0vNg/F6hQyGHKFLlqDmdFuQJ1LUTLiKUuynHYw0HKBdrcZml1z+8e1Oc5Bes2BdwpR13gZICn+QLNKDcvXNHWiDDCJt2nCEpaB+arqymrjyPN2uZ5HJuvrphE6Uh/H896mctIvYcrSNxNSLcMNFRpVD7Gqr7+OOxDGuUOLNpPU26okiR7zgn2doThjkHycJ6To0g6y3AY6ymQejChNaZx2DHatYuIfpi4Fxh27h4wHbhzDVdI8Qk9KJNh9GBVOzTbakPg584vAB9GtPm6x8euJ2iilkX5EoebeGw+FqgRxYGLto6L+kDaKEzEIQuI6z2w0O6w0EeYwzJNIsV/8Nh8Uh4bUWy7Efnv8KV9CZzBtPnqhElAcrAppHx8lGSXMClCc3UPaDw4V1N321k2rt9jnNPW8+p8wbt1+29bw4bfyyhsOKDxYENN3T8XG15lDeeub7/MEZiuerZS/1zlXM7k5B4umgxeg+oh0JlMYfJxJmurb6s2GSP27gqt4Jy/ETJ8dPBtCo94uj7gKQ1F8IlDqIv1t/y0LItS0CU2BQ7zBJ/ry0Z0IdHfXdYnyZdT1gI+tsQN42m+kOLrvzv2r0kSWFXyHlJljn0UZ4T67CIrP6WLBcx5Wdl/dXJcLks0mAtYJpLoTq7QHa80GahQuDXSeHiCOFCz7dH4aLbquuMI4aPFAk+GcD9j89npJlyUV+9kqoBaEZjLUw1B1ZSwDVUOdFHTqHx80/r627p7t08rIkP7CavQ7E7FFjmFtqSFENuCP+2xZ6mb/w8parIKPt7jsfkzpAbFgSf3nCfPirJZxTtzFtB5cKUFjnHYkm5TfJVILJ3rtJgQiEc1fHQwhYp1mvB4ho97fHp9UZK3WjrNNvSQiMbldUakRzASr0eKqTgri5WKu/kyF85U4WRLnNb2P2FSC0u6/ejQM2RMcz0iX1zMwksIJBe4gaPvt1PYj1/aR915hEKxF+7hHT4legpijyUGMWtQweciMJiUQuR1oKKpPZLhuWlSmiBALJ4tiEqWWtojq/QuMyfGHI1BiWM0iEnlGEba0UYwCcINFVdn8WzRV6s1SJeB2n+PxSvaSlV1a03QSBoFvjzHImm/zHmDJPQmzmwOFqJDRwpIZZF5uVcMGLaVtY+ThDwO3XmXeQaQFO/isol3lt1SIyHPrgqn/lzZ/Nfh8OvfOEwaZBmfcY0pcdAR6xok93gNvDtLxQgXSbEP7vd63O9DcP9Fj/svatwzibqv8Kn6DOsalvGcd3KcHoLPFtFI8k/auujQ08E5rn3Jkh/vIG06NWBHIh1P7mHyUDSxUrYJ6HzUWTOObd38tzllIXnBEdtOXWC9HLcayqXf1+D59BkKZGAKHKgKnvsLJYJLky1y2r62OoFjPMVrWwRLhDR+cpwxRMykqfuE1gUfngxgSjl2SIkXtnpnex00aUr80s4FeMGtxnrlXIbV76VzS0zjrDu+eR6brNwee6y3onfOhc6TLS63RWCzyeT6S5JB2YMITIEjvgtYpsVC2CHYIpfT4RpkpDbnFGEKXMwcNH3yF8OZkjlcS7FiNc/ztE5BJl3gfNker3OS7ir9F8myigcSuq/w+Hx2FAsc46z102jXlPwu8yixkZxk8kujfJkHVgVCd1ySy6RMwdZw/SXE5w99ZuHQvY3G5bWz6RGMw+sXfZKXd8Ith+G7B7b3Cmzv91pHPymLqkLWSBaFo3hsPrawEcX+ctVsO2lgJJRfBNSEkU+oqc/wEWbCpkZ9d3FdFmUtvWjFljiEXjZZJkXIFDioiOvuQJcj2ubzXJFd+HFcIV/z8NHFeL6DZQlLARdTMOeJS/zUnWcl/KMhmXS52Nzhs0uEHcnI/AlUvHFIF2yNiEIfcArawAvbLRKfu9qqmtt68vMnSo6yOXMJtQCp0xuf9J662tvKJzunr5I7LYHTjFH4TLC83jhT28a5ihNMf3dxGSVZs4Dn+VWKagPOEBdLnQ46QFXhYFxUUMuPO0QAF32hrcwrDP1XB1djWvMZdrpPU0aqfYE16KKXOeWMLXHDqHxZTSh0wyuhGfV5/vi3S5gj8XRU/UwXS1jziiBb5nAsSGr8V5FzQ6e/u2L7WYJ1p1rJkDLFrriR1fEkmSmh0BXvcYGFP7+g+TKnY1y0SfNPCm2+Oq5l/MS6uJzJ1+m3q6OqKpKUvAsu7FloKyUxozdH63WWYk6nFBbNRqWvx29PPbRKCUF75ELc/ZUN3FwVTSl9lMtqtxtQy7Y9TMyhL4HdvAYlXt6e3eyJ5djN395K5zuEJQb/mxNDbGqp2EHhRrMic499q3lh6GQYJ3RopmYEEuCG/kTb6n2xuLmEVV2mWEk6waM0SQib2oIty9RpAW2EhbmtQDbhGojALBZdDhQfLfWmZpre9XZDXdvQPd0kQotvNLUwvX/QTFsRa+D0DwgjTLykc2ET3eGZbaKP0yxDnwf3rcVs81VUU+4y1yzOQJpyyGJOO4c6jDGdO4YU+UWK5/PVefW1ybLfX9+BrIKOow9mngtYrtIKJwNqU6be0OlptTxkqinkveDgaXAL9tI3Fzh7AvIIfGbocOCeQuGaTd5Ys4qqhkraOLKGHP027jT7wgKX8AmUi4sizeuqC4i9+V7Bxc+0vu+cwbqIEmNlMYZEqGLBF8aGAmeAxRWBT8wd3sZNykSGeAKnV9VdNByhTgwVh0MayEc8tpgSh8e9jQxkHn+4mtMZ7t3/12Cp121k4GIiL1K4gbHRYkTEoaqLl0vCunNR/CUIU/Qdqve1Yoc4SHNY8iCDM7f7Mvxd9R/whIIl/FIsYFZt6uGc6ytARlStQUIs+AU8S8sKh4aDW1DBFuT1K9T3xxRJvf7IrXO4/JGdZG2KrB7gC8jTO1jV18UDzH9//f6Xd+9fvyI50XHcQHb3+tXzKsurvydk3YI8L2oy9N9f39f1+u9v31akxerNKk3Koiru6jdJsXoLFsVbhOsvb9+9ewsXq7d89Q6tFZZf/kePpaoWGT3j1MFpN8//aAD59D1POd747d+hwGn9DF/Cu1cqOfLbW77ibxLewl34/XWKKUtWI3kqh0SXAnyvO8dQkPTs9SssbvCl0EHkvNWib09L2wbyR1Am9wApIl/Ac3sH4vfXv/7ijJM5VLNGXZdiCIaAmX3NeQzclB7Xkdxx9Dhp3B1osrpHcpuakNAHLAYGhA1ctLk2hqwNe8KJ1Ni4lvjl/ffzfAGff3/9f5Oaf391/h83TOV/e0Um8e+vfnn1v5y70UlHzGI0A/twQvuGmIZJ3/3yizOb9ik63OjT1lIRxmp5IBHft4rGcpvmoHz5byvw/H+4YjKKnPe/uMucL+kKti9cBCG2Xokfiqc8K8BiTxZfP5w2LUjbUJOnfzQwJSjvUiwU1Yzmvs6Q+dQ3SsJRbWWluu8Umn72vfizx3dMWDwOz3fxCzIO9cKH13MuWUdeyE6f6y4xegxsyOKBTyHzeV5dlwD1h2iSFBoLMTdED99QSAap986HTZl45LY3+EudYuIHd8m8Q/noBnsilDbRL/57ME7ObFAU3TcbjNRCcXbe26+L6F29Lsbo6CVcZy9uvXVAO0aPT05iYzxOktgoh/yAkXXGNhAzgmxn73vw0tBV08Yb4iaKw299YyTG7lhtglwCQzf9mq0dZoAw2Qmjm5B0T/dkn+gS0ekU/19/dVcrW3+A2Vphcdvw2qei0i9yn/5eFGUdspKQQl7KVMrwjg0B6ZERt/mlr6og6wHbIa2/Bgk3woGo1gjrrktw0oZb7cnCY8ZE377QTPRff/HQYk6fyS0MJnjifFHF3n+HZtrQNVML7z224r4JNvxuhLF0YyDtYduOWKDP/Nbm7GXJ01WzIrvb0YpsIf22C5N0BTLs+ke/KuLDf/c3xHLYU4tm3bX7aPATtdRi7+MfY1EKX0629YPzS0Lstb2UaU+ejpt8gVYIWo9DXrqzNKv5096dFTyb8bmranTdIEWNSfnnzylsYsMwXFz+vPibGM9ee8JPF33qCVdmGioGcVJLT+9OcNWDutIfcYZwYX8PYSyJPVzfaJ8CJvkf/BVAG43F2ZDHuS404t/X54vYnMr+HDDoT+kCXt83q9ucMp18EP1A83mbBU3ARXObpdW9i8Aa7/Q2qodD7zyOKpL3SxDjFsJ2wgtQoi6Q7GlDEq4wjB2nI/GbPqYLR/dMuNix8x2NInf0qqwXzqPFKs2HR8siIOwZp3uEJmyq8b3gb/knZGleBG4uPY5Y4om9CB930xLuw0eYFupGfNzOHmVZ8TS4A+BjCp+CNsSj9bosHuECP4WcL6+aVcgsfS3qqPh6ZCTxKzfYwP7FQtkdXVwXR0mQPiEkPPBHdfXQRF8iIG/uQFI3JSzRDlN/RRrUZkHHauUjYpnYOM+rj+ldfQLKIC2rxxG+P3ePVXyr72F5wVzv9utZ//jFsNXrxaPPKdpRgw2xOk3w1n+0WHBNBnX/vBoCiiJEwoRNzfc8axdhjy5oZIjo7Sr5difg8zIuOySnz+u0JBbxB/CiwugS7kOSVhOE4dz9CVRXAGkiMMasspjc3QN8/ZAoQDQwfE50tCwhXPEBOh4HTgOi9nnpKOFHQ075MHnSIWESwofIOxrfxZAOPgJGJh18iO8Qn0kMb7aGyLIzNKn9AUeQUrZob9GArMd2co8vZvg4lmxDzzavHoVQ4LQKIiH3VlKAvQBynJc8f0RLDCHrT5uCNL26SB4iuQmxTUTwHT2CFNVNMwpngBtI2kev3SvNo433c/HUjrV7ODpsGpAOn969EDv6rCj7/h1DZBaFoD0GyQNJX4WvBwUebGATDeM7b2nIvuAYMClk++qOImNMTIuvPXCMgY8MHC46VFSAUzRVFLE5Bj5uXo6but7Y6p5LBkP/TKv7LK3qcITdWs4g4kskmBmfhw++E6RvE1RpEuZnphFE3zq+ZYtxG+jMjBMqdih6G1drhAdk3gOxOhuh2hgenrlOVzBCEB+N+zRfRMTcu5pO8xqWVTAvdtKLwQpHZqBOwk3aJrIXrlPYrt0gmY82T1jV1DOPK3wbA28lozIr6n9/2lh1WdVCRtE+mzcatVkzJTr6n+liROyfxqXNsCvFljkD4rgCJ9aJQZzT1f4lLgpFzMtg23LqKh15+gjLFzyr7q4XtnaI44W+eu5zPZauHdKPY1DBbnsNdhQMuL5AUDUlJNfq9WGQ3t0dN4ZwaAb/YJvy8Ei28Txtxl6JWyz0KKALF4IlDuCK4aZhEGIyxMR3dV+0nh+wCW+PGNE2xCrtR/yEMarA5251T6Nwv/j3CsYJ2lKHgEVXHPqm/CWIpVuNfoMzTHfoNJEImLozruqkaNY4MnoRNGsiFpVHxDckkBwfk10wjFE3ePDW7eBXtxY88lTk+yt4fMKZcOSRK2Irbokc5XJc1EgDix4jhfMkxV4oPdvFCZQ6hCMx4YZxzmwuUhJg4hGM3lcMUfBxtNpV+i8YMgImOKu6LtobCTxmLx9uh+Mbe4pgo+rYRREiHf4S5EuDz94vJDxWTGFcn9QWRm7thM9i3yLOZRQk92J/pPDpi0Rns5DA9lHsrejaE2WoG03MBEJW+a3+6hExDgtVLiEG9V98Qg4PiYDmSATUHRTu2ZKa94beeDrZiHuNsxOss/b3KKlkPzIP1/1QM3TmeNJeNhlUJ0vw2h2u1nB0B1X3KMeQGyEohqwP54uBbPN00XXBJogI8MFvkF4oI8q9popCTGfPiI28p7DCUArE3in5QRQ+fa5LQGcVHUOvpA/g9kSkGR1iHhmbT4qsKD/B5+4d7LjIty9ps6sqtWe+2pl1KYWXzKL9Tc2gDpxXZ8jkbjZXdyJEHI/MgfTFswMXxuBCmqLufWBr/2m4cQ/ZcJSDq+jnFhTdDyc3cU9uDqctxt7t3mnL4TBitw8j7MMo+rcT2wT7e7Ip9aPyUJGHmmFKyZp5bC7emj+v+swTQYFEu8qme8af+udC3IMDzQmIfbK6GtNDeyC1SmocgPesKFcgMGNVj+sKbB6jitXP6AmcIl29Prq7S7M0WD0md3SDdKexd/zzqj1D6FO5h0SAaDI3a7scxkKfQVXb7TM+cW4xdwjc08/FMs0jRdVifP0tcAuULscz9GOibpqDgCDErpG9berWHRGDfX+st+We+fZjVyZvdUY7U8RMGg2ZdA/2fElrtQZ5pOdMpMePNlp2XzFojSD0OGvvY5onHv4HrnpIT06ohAeBaR3JenoXE9n7KMj+K11fFBVOlOMXySbRru6LHMoTwPml2ewTZcVhbf/9z96oaVfBnkhPoyP4nYcjmPjmqm5rDdJrCaIYt/Wun4rPEBPrvCqMV3Heu/Ld9X0JoT3+v7jiR0sElmnC4Q7KWPkDBGnPW5W+2tvvab3o2YdL/ixrfxsOgaI9UL3MEfXaTGpBnvGJXuWJ8JKNPXfTStWeMPd4Wu6M++rtbQkfUyBbXKGugxkFujWfHmfFEuuze8Kin0G+bHCiQmce3dQMYlLp0bIgpe0cMTaPbToc7LY+1ijJzTtcJ5r8FHZLepM/PBbKa7CME6S6nffoD+EW8Q/mpzBxe/GyJ3J2lL24p9FJk5ELR/oTY/djQXKueAVHMFbPMrA8X6Gu4ytwRtq4Rydf1vt1o3ZcnaYNEL+qcU7rS1gVTbk3Kvjs+k1PTyOT++QH6pFLbw/4qT44h3ixusiaZarKRWt5qFw0iZrtA46OvsKnao9eQJydQyNq4OQVrDiocAaaOJiiSODtVG7/THbKduivBwOjR2t9j6bIsqO8etqbEDw8II+gdVIruj/PTyT27wL9KOqwp0vGvoOFiLYnXDP7Nh+Pe9qotU7mxTnRj7JFR8vzRLbVjw2saqTF4jWyDQeM26mBjH/sSZ9x7YksMNuCv/7qrjBHevopSjAwQtLxPcahYHsrDTc4HLgP1yX8M1KsrnrqQhQnWK7SCp+7XsIES9r9Zf6QfWiUxTTkHQhDbD3Xp8+YqiA7aup7TNeEKAN7Ne+z36yKFwnbz9Y5deUmItpOckbG/g3z1nXxAOOYkQTdUZLAqoqHlMR/oAkOStJkvejOirJZ7dFZ/nWxThP35dVVC8ssMvfitnrF1E5fuNiFizFTXJ0k64Nwx54sEDIgdxbtqu34AsHziFsOzB/Xxu7qfYceqaq+tpI4yBmF0+UGIcAXb3AvAl8w6LAoptwTG8mjFmis76YE2ifh87EsmrWnBOrqjhsS6vVKTeQTiq/drhO0lmMIFLz2ZErVQayM4uHcTfFEluWeyKidEA/7xm5zONVb4u0H05KxRLzASfBFza+iT9m7G/YDm6QgNMlBYGe4FAXBOQ7CLnGTmvj+UsP3xHNYMXB1Oa4jomrvHUbOnz5kEWvKEubJS7RL4j3CS7A5vjVkZv+b96pkj7vCqP0D1LGFGTLaa7QWsvM8yVBXLcnxVz9J3DeGbw1O0dg1bmx4M2CiETKNTjPSTjZMM8KusUlHhhpyWKzujTFiDAn+FO8NIDuDcGyaqlsem8Dqlsemdoc/ztUywiejM+LwIvGorRDRMVoTSFNvkLazGPlJkkv4BMrFRYE2uOonLCFircC8kvcweSia+qiuy/S2qWFsq1FooPqPVaS8SJ0SoIjwc409i5NGb9D611HGSCLisBmDn44pIZIjJ2j+WT3Fa9oRFjpCKGwicJeiKabC+ALziFcPcKEiXXBPTx4f42RrwshOn9dpSSynL0Ve34+A9z8hiDN2mi8/pEi41R8g4cOQeM0BTZfh8VORLSLNlYg8IiNQyI9B/hDNlOLwRltiNN7zk9go+xxZkdGe34JIG1InoYlSEDOfKw7MQQviX+0j6vi9Q5Cw77KPgj4au6kauIRVI6bT9ZRGa3xhOj5xRMQRe43szkEnit/1iwbXrWBsd+oFSGMFm3MZsKLQtEOJbRWc4LJpXYaj+LrGTky8vQcatLlwCVcgzakgdY+o8E8Av3DbGbdfC+yWT5iMRBGPSnqr8HuFdPtPKZquvUm8OPsbnaSqe+tdtbCArAlymHxM74iyv4es0w/NffI2NYPm73sFFz/T+t6ThbjqwV1hMjBEd7dMyax7wqC9OkPNMtrLPe7tKvCEHBj2pA4/LBrZzXde9V0l97AAr714Cg1kmq0jGgyXaLBr/KZ7NE1twBjvzsgVzLFeHauHLbp43fsCq4q6NRv4oMhmRohuNtuTQ27xMHuUVGYYT9Q4lhnVtYt53yz9RwO6ifCX1d/ztCYP74193Dg0NPrp4hSjmWQkY5+G9vZSu1+PTTK2tbFpN9qRXfyjug/FU54VYBGcjOi86lFFUYw+IxbPK9jjVOqEVrsvkvo/Ybq891ILbVo4bvJFBj+AGsRxEHZPveOLmHF41G3b/4oTfOzTtj/LBv2VSpMSdgGzj6Qrhqcbt1x5pL2b++XlmT3quCVqUPAqwXAMMoDfFQhR36rRo2piGmFxDwNo/4fHk3J8/REelMMHOav9eYd4Rkl+XYLkIc2XEc8ASczbuEoJOciDsU4aP8AsfYSlzRuR2/ImWL8A9siLMQzJ635HWzPC+ZToNx7de+Cge9RNmV/CP3C2un2Z9vDrUpEmbnb9J4bf6RKCqsjPirLllThKesdxkNi/Fva+D1LbEASne0ZcsuTQF0/u7r4WsaLcdS9788l53V9EoAVFjFth2xJt4rBBFiTU5wSUe7VJhkvLC1B2ikOQ56n1C/kds9J1w56/3kxy+PHq7PJ/5mOP8S5LoNUJS8T+2Lk/mj0dY/fcOTHXzfzVGtGwD4sb5nFPZN7My0JO229rNhrX2lbRYgtbv/h6xFmaIXIHZgVuk2t35AvNrz1y1ngdQfdkAciHGIv3tibT/CyMsicssjOz0AmVa7DcY8o7vmntSrwfoEyRrjPwMLIib9Mc7JPEe2hcaGoVhlmn+ieMfV7sAXlzBxJ8CahEZpXiel9oK95qs9WTDqRfY2H/mS7GQ/5p3LgHUEFCef9jQKdm8A+2KZ8HFKoqXeZoBaaEKU2vxL/zcC31hyU4l6DHVRKmdojt35t7OP7NvR9s7ZB+nFfhr1bsneGP9oPkIYZBTmyab0397Y6gJIMcI0WeYmfdk+00lL/6/we63HwBxPG2YcAbWm8cuPGdvxcswM4RMQRnm0efqEdyggPm/2jSMiyIkFL+8rossnD3p1Lxn4BP3hunxXUl79sSPspSEP2RgohWY1A0cKiYbu/j7cdEK4boLQWltFIVxpTj83Gs1dFEkRXl1R8NKGF1ubyN3c/Wclj8E00TfcAb/dCjDbOaoKHzCmG6ghlMAqPfI2wzUVjavOWIfScNhe+0n9P8AZ+GyZSy7Qk2uk5h2boe9kqwznaMFC8tNH7c0P+Quq0dw/QOikKIeBZsfwSA52BP2DmC31lyZF0K9n8wzqsqO80xfNC2cQWTpoSO/bPyItLvhkTzkn0ulsXghBvFKnPE86leZcfF4iVW6itkdSLG7d15X2H9VJQPI3i+V6B8IUunT/HtI79lWAIjdQjK02c0ynwJST6p0P7JkTl00+GR5Rb7n0USerxQpstQz+J2j3sdM0d9Jy4+FxiBA1Hso7nOinKFpoyKMImE3vsdFMnyjPHC/P6/PPOhwMnZTvMFmszQLdBNB+vcgnsigNoxhu3zLY5RlDpny8PeRLyHK0gMboRqTyaTjCm2IuM6rzY4mSRkqve7nLGOxyzHIHk4z8kxHp3bdE/YJkZg/rwOitnD36e4N0lH7FzDFdrg9+aEaJS9A7+yi7ompIUPRjxymOEJoumyKF8Oc7y/c9yJvcMU7+8UE7qmyb4d5scwGNAQ2sdoY08pQhyf/2S6ql+kwXgK6lGSXcKk2JukuFtulk5xmmV5tH2+2DjLIgYVfi8PDDWlnyNr9D5JH6Qxwnt93gqz96cl93DRZPAaVA97wmcWDn33iTSHI/3q4S1ByztfBKUswzXFbllIQMID/4sZwd886CI7m7Vo/St8ri+b/KarHhQphvhn/S0/LcsiKPtf1yWp9yBgRB4BEp8BybpR1r59GRCQX6d5cG8Qivh9eedJmSZJYFVFyPNCOkSzTbTz+/PqU7pA8ivwVscSvzd+AcuECs3zuNbTY1KkDBzrqGZ4nXJP9hXvB+TjvGMvexHKdQSOuoi9XxSCqilhl7VvP6bbqEa881EyySthaGRPWK+Pjf0Sc+DYwQFjOWs6HvqABHle7c/JzoGNpmWjs6JsVvt4QkgNKepDHbMf3JEZC3MLXBfrNJktRZ3DkQNJ/9/rYXvCmOFH12dlsfJnQ7Z22F3Nwr8bdN2wUP/m9p8wMWR19AhKxBdR4zjR8R1UEKSJnledOnv80j7hGwnZ8JhUCL4pRAFRxdGnz8W+hLFRI8KteJhJPIIdj0exfKZ7G1mSeBX3gy3HVZWN2v37SD7VWHEtqzVIl/uiE8uo73ksLN9z/ZDhizKzL3uJu9M3Qt7BhCeK7Z8tpMZnjR8nCXmQs/MNilnBdS1YnS1ZaJE+rm0r5p4q81FLwkT2MPlUi0N2qpcvcKAoyLKXEDxHdQ2Se6xDvDtLhSAHN2JvcL2PiOsvjrisBcnX/7+9K2uOG0fSf6XDj/vQ3pndffNshCwf7QjbrZHkdsy+MOgqSGKYKlaQLMueX78keOHIBBIkwKPsl26rkEBeH5I4E+ypeM/qjnuGKzmwcl7XdMC3fonbmE9FqgkX8Qajdqov7+CJYep/3O+zTQ8CXj+S0GqGb7N5TFd0+cB2X7PT2eUrCnLCZ5WZuPhbVDV9ex7ZR5vvitv4++vvTNB0TDNVI91B+Gkf1xmzjQU/nzW+a55TmilNuRHLLHoT3jOJTw4ZvxIx+e2BDm+V7U55/RDBVfyjNsh5vZWqaufee/QWPDxet/oNNFXrM4HD5Y9dyuRU2qM8UDdzxfIkw18FJ26DlXHKW5t0NpUf+yO8ijnbSWoPR6jeHZIyidORT67KtVd/3ZXnAUn+zZPNcd5n0t/AmwiUs6yqPaLLajaRnfBDrVMO+PsR6L/HCMTzv+BXxPxI9l/K6sGY8VkvaSgh/+5NyNGX2By+jinvH81E8kz66lWXneBvUz5FfSt/n3/WSZ8x5llRVGPz9JcH/XjQ4UN3LqcwKk3es28snTYAvHnI8hK5LjBuJ/NNRU5tkLT4dmx3znzvm4U8N0K6rFFZCMhTOcpIFVJZnrPcV3uL5CKjCPYmr98qrnMSTpk+NUkp/4gLW6IDT7tcN6xJhHcekSfINvkmbv4L6+Q/iy9XmOyDnwE/E/s3h9ZIx5tIH8vDLj3t2bvDTVI1G/czunF7S1dxUdSH6aqCcvIycteYFz1vk1JPWzqqJW+nXD6wMm4PH2pDldENTnsuBW7Tn+08HqW5ZtUoK78oPif7ezZpL7Np4f+ygx8dm+Y+5/GxHTUgstHbqh9NlnwwXsuXWR1iJ/bLPMnyiUn6uTr1U63uFidH/eqjwk9/TfkAd21E+pcApO94AfT2L4fEzG2CIVSdtnQsyu84xxmqhkgt3mk4YQHE1Zktq636UhTfTYKhZghP8mMt1eSm+rQ8ZPvLWtVxnVNuiNxFG4ajvApwdLOs1sDEeySDKo6dta8Z5hmAZrmjW4cZ4duuCapXW1ajvCrxGrewM9WTovhuEgw1Q3jyiuWPSVEfLmnyjYnJ0ca4VW2P3GkFvqN8DDJ2jIp6E16uofU6jYNeWz1kP57qddfOPNnbK+jR6/du+5Vv/1cPxsd0aHSMZCKvmI3rxfMOaVABOvlHydBUDujRa/YtYU9/sPR4d0oPIz/AHpa+KKdevLijUXi0Q7rqk0T5HBetxQMcGH6ZZvfdpeFz9WWrHykVAWmdrrLZVVaMOEA51AzRSev7ID+LL70tHPoGR+2EdyVTc8sQr/M0NYNE8CxN/8rqrb9F8/vOELkrRS8OxdOY3XSxbvDP6Pl6YNlHYfzFhsZP3kJDneJseIJu1IuYzTjgX6zgp6U9NPUxc2wJw/pFUWS7hHu25fDPEzuxfXNbur+9G9W3dxXkvz7sf6tnI/Xrbk+HNBuy/Nyw9O734ccPp7RMjmmyq/j/49l//v77356pXebPQ3Pe+rf64HZ9ceUyLnbxXjdHpcYeEwOUXJIJoZAF/A+NKT8bU2/PxulldijKPK4Mrvf65LBLjnGqGkUhJAaIWte+SbXkFTvWu12H0qg5hXF3K1tn3vNQHGAzxovnAqbIUIvE6/ko0CQi0bFygezP8GjDMBYIWbipgqOLxE7NtLA0toQwhqoyzbE/RUBzhcFiMU2ouRj8urW05mHuqP0/irmuXPRt/9ssWJMFlgVRioKgC7RQGGQp+lA4Cg+srwJO4J635kzIiz8LnCALrQdOw1RrETj1RzM8xiU+rld9N7TUH7oRmxp+3HpMgc8UYe5fOJr0B0oujpXJ6+N3rfgJK6Kb7JTvDHO9tq481+t/nCW4aAe5IFkCY6tXeQZwmQ+uITzNh5sWh9ltnNfHNL2EizOGmZPLl4CZ4YDjYjBrP6+bimXQkE0rO5tI5jI8W2Mg6xFmCWPLD8SXx9eMQ/Ex8DIeWJkVXd0/2rRu9XqHelJjnbFLEBgURyo/mxgmauUSx5ZeRIgqzyffWP7jtr49i/pWJJKcKhU4zQEdoqEvXKCqhsGG03dNkG1pTPSjOORuzLoG5orQEJ7OaFiuauQyKl8NrjazYrkgqOZfs3RbtVp40bLOk34X85WzfMaFS5Gt1JxcsPUFTEmbLSxitnCUUIErMdWhYYMOKhJYHgRqzv73E4Gc2YoVVoW9zXzhVgC2+b907uFt4a+dcjH0mlWtJXUGjeamJmGnRm5B8rRaNAv84Gu+uFg9RRAIKiaYAYmEa84IY8q95pVg0ratA3gd8fNPgUIXFCwGP/yC/Fy4K6t5SiXXt0pVFoGirwtjorwytuSSzWNKUscBS4tBqbsd7xFElvmjknuSt9T/tnUAgAkdVu56A2in9+HVgcG9i84ICUm45U7DdTf8+wcWbUPrroYcHvofZ/nIqDlVIFECx5lO4RlQZcwgg7A0Z5lYCFmWAbJDgDhbXLn4eAFY4ZmF5kbVyyRNqxE9nFVpFKZsh3hd4t520UDiJRt/eTS0ySUjWImVfryUjJiSKH3Z2Xy8Oo1cPl6L4ap5ieSmzHnugYIPiKLu+RkUXD2B6NHhx1nABUkuywMSBIEZbLAwMAPVovAVHxVaBGtdgpVt4KuTVpJh+HHzOOpV2QR2uvwr28BOJ60kw/Dj5rHTq7IJ7NSJY7aBm1pSebOW/7B5vHA1NoOVJslQpAvt6qmAKGlk1CTofg6zn0/1oge0tIpQuNXky+FFzQ4rZvC0rjCqtWV/aoXzoMuU+9ckn0wYBoGqSeZAIzkXMoZPUvLjFYDVtquPAcHk+J8QpM4AWRqglrzLc693OkXQVS1z2TrIjOCbdcFrLOBWB7YNRcA1gm6uiDcZeMtGutff68fg4vTiVD7ULe54ofJJXnXEM2kgZ+wzEm4+AhrVcwHkYlh8k+WnR74o6xt4+P5iz1NqSfh187gYdNkOCPhrmXOjoHmiU4NB+/N54AB4hnTVQIj4f9/m2emIokAg0ZzX/jzLh4gz1EUIBB3MMAHBQ2I0yLWGEALI7e6xkHhZIOjQfTlvxOHkKxh8YGJPcV1ICM0+enH066zjFy7WYiDijxEEOCRnGrtIL5bzRtpftn42rlGDwkg2+MLe38SkeV7QzDnMpaNm8RFul57pUxHfsz+SSpr8RwTnlVppGi5RclAemeBsEnFJalH4Lp6JC8Qa0FfcY8RPADF6TFkKX1zCZT98/ETcmhHVS6lLEPAo3GzYGRTZEmDWn8NkGdjMmK/EDThLZyl5m9yVl3G+j65O9QNrBdt/TsoHRIfxbrRcS+qkkBobfgwXSch+mgiKXhcSJkBXLA4RaawDKzTWpYFiDSQ5KM8Mgx4nBHhCm/O4p6u4Lqx9ErvCSkdDa4LabGOk0TiTPLrssOljVrL1j7NrKXUJml+3jaFBkfWPs6/ZU4X2q6xqoOiC0ybWJwHBJXHA8s2vXUJabWIlE8KZ14+gZTi+HrjMFobGYkVyy7KJ/fj7qGv+knVCasn6Ar4rMRuAej3W/xnrROUrRrDcY70WGDnagoNcECzPKNmxnkBEXpboKyy7Sdulppwvkc2vbfpnitEXHK2Up/xQP6bDApw1DjQeFkRWhjZSyRmMgUV9NjH67S/wzB9VfqXHQsPLkmPbjAtyGefNkOXl6bBPmXF7ot1WESiBHS+x1AkmqkTKkEQtDLmjhdkiDIA03Sg8BxHXA6FNfKXWALM5v1Wj0LX450rD1vr34NcArBl35EfhaumN+Zb/zZHtkrv29uFFWebJl1PJNoAxo/yQXBjl1tFnVm8LUIRF//NY/xlR9BJDjx0PNCCECo0GXQlSdoRhVqLGQ2hqBDUpS5pMgA2sNLjStQ0NnJ8yNk8G2+IB26TB0pjvhg638b09h9laRhGDzJAsYumZjBYElRxGCAumHwPAZcm4Y/Yp4MyfAWJUhy+Jsop8caD9FedJfCj72HqZPX5JDpwwwp+MF+/DiETSPRipwGlZ0C4bBC0jeZgrXqiBgqLNpChFClHsNeNvM3Pz1QN1/u/uVIwuPVUnwPOfp6qk8tKnQ4JjVCISsSAXnGV4xA20auiJYq8Nf9uNiRRInm3023LI6yBnX5LUCAGXz70MuVo0zrrs6AGWq1lrVJX4K05PPUjNGobBxbzI5epSxGwJQ2J4FJ7CQLnR1gHPagNLw3rx4SRlyLDtgeHmRn/RTfx4TNmr7OmQZjF+RbgnkNZf+h9XjgZYveWRIBt/MSzcJiyv9NsxKQ+79bwVJ6LmX+eYCPIV66WXZBF+PYtk64M+FGaidCuA1fqnlMuAaMZpoxt+VjJRHI6jtgeJ1w8kTXK308zbA5auEoWp5M4VwcwrwKxDoF8oMTNdHT76udybJC3rfLfBrzfMFaYUzcxCasTnc23Cqis9ui17jaLM8nrglTzG+Y/X33cP8eGeXccluzzlFYvdD8PwviWQh/bdj/Qgx0WQz9M1vwQakEN6hQFJowctgqEOWAc0+B+/MLEAJiTLL/cafLz7+u5QybL7enP6UuzypDkrvolbV4jw8lvxGM3m72BhmrksEKwOd+uf4a0PdDOO46dgbunR/MUuBR7c9bvMGQhzvejyxfnh17NY4xz02cwa58XdXZJWv7BopjdPeoZyQ8OvW098MKhCYbZ0QhX+RE/QgRMOBY23/hTSmY14dJ02Mdap9PtWAfpDBdT4nkVv8uxxPpTIzJVxk1y0eXwoClE4is5YC0Bus1/wWAk8BlcsOGwtk2+Vyd9n95Hw79pR+GBDoZNHjUrZTCPYnismTaixi8lmYYAmKkUaxcgirgJqm1gAWg5Vc8YtVzgtHrIuH9jua3ZST9BqP+PY0iglkOmls6ANVsssWsgzshZ7hgEjoiEJl2rVBfNg7k55pfH9VfyD52J+d0jqdj2lZMaHWypjJaGlWrjtPMuaPiSegidWg48us7tZI19+DpZJFVTKKFrYlPHjAOIXlC4Z5NW6i+HzfbaL0+TfbN/JGL2PD/cnbVYjeL8nEN09/DgLAjWxZWH00iCog00VBm26ThSmnYQL4sv3BCDc+W917H8eY/7NjPXbXb1r9i1hT3+w9Hh3Sg91EuLaJUPjfNWzx1T1e1kPu26zCKtvhV3bBoi+vsz5SKYuB7DTDBARbsBMwlyn0SzLZBSPOPFeEodSge1cQUuF+nwxRPk9KgAZY4Vgkiovd1Qlze4vs0c+TyQGNaHK3HFMZC0dSRF/P6NohZraid3MMKr/fZWprzwoXuQEqgubH+c56OSOJE9RCjbPOvDTybYYjj6yp8IxHAlV5g5HImuxSen3MwpHqKmd2M0Mo/rfxkssPYHqwhmvrIxAkqdwBJtnHfjpZFtu3J2l6V9ZmRzu28OV1HmfUm/2+Z7KXxqVa4VnFKLMlnfiuQS+6h8uDsWTYWVKIFG92v08zy27KRjzNeNDzLUibA0irmP1YNTS1aLrVfQFi21HLk+rF7MDav23WkbCaXs3WNwxNO+9ldc8N1TdqaoaLO+W+bM9e5PkRfkqLuMvcaHv89W1blgJ5Jh69ltTiubyutk9sMf4H8/2X7LK8/GXVGtAi0IqN3Zi+9ePcZJelGW8e2gG4wBbkA7mD5JaBBkyUmm8hyKI3VBKV9WsoFUtK6tG+d0uO4HGlIshZjKFhVu7pf2BlQ8ZZD+lHOKnkNgYWrMK6DJYq4BiWWu5SkoRjSgLlTnO0sjI3mOSAgPYUAT2mL7UwuEyLtk9P0OicRiKIA5Dqc1Eya481ZfIdRN1JaCJukKaBwxcFAKDP6KWKPoQ83fuyP6pnwVOcoYEVpDK5DWJ0CaC9MyCzlsqBplKFDRrG1CjUZjs3VGRDd5W/BAfTncx95UhEslUJjlESrIsFiHs3GUKW0dVJyN6f1UpwG6rEhHZGviZGVk59NcsNQZ9CdR+X2hToO5QcHzrSkDxu0JL893jzuiXWSWAmKk0Np5l1W0q6H6rBtVQn1fKQY4yiYXhsLWl8RqKIDZDqYXDcNRN4zAUQRyGUhuH+qwXuylzvvpRNO91AdxAMpAzSGmRYliV1zgPRRC3utQeHYUlND0oCoVgLBTKCXwQDnjb5FDT3NxHw01TbAo5DYVNB5Y/JkVRze+6BT5dH40E1E2jss0cvlfD2kOcXpzKh3oO2rxuh0phJgdnFsYaFun4LV6kswtlEF9eXESkDs9pb7NjssP4tIUGRi0FhRPGxNh+S0Fp/22enY4Yk7bQwKmlsHBq7zNoTNrfofbbIuIw9lN987I/VI6OY2Uy00BWprRI8Ta5q4aN+d4iBUwGSQFTEqUwcDZzo7kR+QgIZag7SRNTTvkxK6FIKpShTJpiC5Nr9lQpfJUlh7LAHQZSQYxBQsL4C5l2DUXYmIs0weoIEY/JxSZOJL9ds2o6cKgngAwMwUo5bEaJxKqe+ko9oKJKAqupUtHmctiTuNisDqM3ze+Q94mpMz3zS8O6vYzkoO2MNUZJR5bLRSKqT/nznpgDeaHBW7ycxsf4phnG31jJIJex3jh56UJa1q2UN3pc11MIAtEkcUYK8moN1SwtuYPX2hoW6YTc8pooQhnEVyi2zuMzcMWy/R2et2eENdEh9ycwi+qK4BlUV0oRvQcZrEFfjCpCReltVZ9xR34Bp4ZKOegVmcS24IFlCNTXPzBKcDkEI3ZYabxlj8c0BnsqTGZbeRwoiVsFBgl0EtPWAZlz92HAGWsUpu8Lle1bdmBVRzYFR50EnBpoVLY10SF3oL4qOpSB66JDsYXJpxxnIpRBTIRiW6Coau9PKbuNi69QoJCKwUAhUdjsNmTa0+02lIF2G4ptvZHFRdVxPrPk/gEaoivlYP+TSWgMXyXVLKKAw5BOYmArUFHWNiwREKAxrHQ4xT01xxXQ7WUC06qUQmrtgUKOGqAPCqVwLxQI6KyalE8mdg2FhWVDZA3nj8c4uYdcOhTB4bsrteKW29r0xVIpYNQqRIQ19vesLFluQS5GiK2/Q7Q2I+vJcHRr6zSg2XUyV+bY4BojJIlBG1CjqSuAFQ6EEl7rQIgdxSHIQROAtg8l51HQWAM06A6UTGZVu+5Be/zUikoAqyzT2FCYZ0VRNZ7iXHUSEHkaldXUUOxGYzYlVlf/KpEpT1cCDmK6QttEJ/5uOFohlYKTHJHAxgrZezFsu9D2W7q1f4MiOolpEyG6OB7ThO1vs5Y+Yfat0K4yDjqNgiZDS26XQN7Ux48hIHT2cwLXrKhCMD9VTDgG0e6N4qctNArTFmtLRHCEuj1q2eU1k1P2X6OalLwA1p+HoW0+0zehpTT2rutxlGVU4mpqNNC5SgGkPcCEAUgNMgHUhBMo/TVCfRFGLMXOofQEhOEjzkoqxQaKVFb6JSPdvhoJdr5CpnLxsM2tBF9qDIXz85aT6NGbpO5LQgXTefSGWj3oP9yUUM+YV8zx8+NSTeMxed4M7dT7c1l1N7NE8qlwo1FkWlwx6KQ618d0Ah0zDWYQv2YQ/C3xoIJErORVrZUgRj7THPVnm3X7IJS4UspBaq4GchYbuqIEVleKvKvfjeDs6sM3maC7Xrr8q1G/PzVt8LtG49/j6gCfV0TnH+5qmsb/0U173lLXnFLN8P1Qblw03w/kNgVYEzSLXhjWPLdxfs/AHkGp5snnKzNPN3Nzwo5aKSRyoNijlYU0jAtq1Er+Q+pCZgEu4RRGk4AVQuIEuG0kNWK6PjT6YxvJ94TQT61MZ1AGuLfUaGG4jzQWV+PV7r+jQ2hCVddpQwRS5AqWqL7HMKqqZh9raaTBBlszmUC6HGYYcoF0/odd0LUzXtl4nWy055WrcSILFAJ4Hc9qQahAGwLLg5jJ3klA8mAdZWaTyMvUUbdOzdrFbfPwnV4ZVxu+lMc1Nl+1M7SiLOADjWFXBoMYEB+q0Ssbvk6Asoh6azGZeKEx6tsFzAMSBjAFdEmzMYHx7qW76u1ejElplcS/usq+Eq+HXdEdr6JiOlxRmdC7l2ZUvd9P6rfcDMETJzZ4XLnh3bgcub0N1oQMoJWFMAQeBHFiL35cgxleJmlaBdK+ZYMRFNJwJiBhaILSXXq3oWlca402ZBdQsjJIDWCpFtzNAF05H96WAWxhroCrpV7H5/pgV+3lmoZr9k0rlGvz7qbpMg0YzaEThTCBmi2B10QzIbir2iUKMKqqE4VQVU1nwGsOP06fXGVpalRTJgihopjroJkzQbkMxqnWpFuImhZh5USSiUJqNeRUEX09JAnECBXVkznikRjTMIZW0aAcktuhUdGSr8HYEnRMCGzVmIvCuynxgRCtov0DpylsUnCVJgQPZpEG047o8zCasBl9HtMQhtezo2kpE5lyqhiHo7SKIdBEyTPDW3PKF+Nuuj5rjNFOAFUIo2jpbXhV4Vc/6vKj0nZ9FbJgCkuHvQeN4QPdI1WOxFw3iL4ijUVkKaPOIDKYKUevrVf069m2SaNbG5rJgi7jTd4TxWZNPVag8yj6zP2Wp70hrKqAdP6XVKRkT7wWnMtprKKm2KRQhAhL4dSDck9Fw/kK/CAHXMFwgmHyQQ4o05XUijFzlSfTtHYn2gV8ntzdr6swR59NC7eBSuJXcS0T2FDP0wLKIL9hT1on8r8THV7VLv9adFXNzx7igu0/J+WDwEFX3FbFmzpSXTXDHK+KZo8bbwip3w7t42aAK3hSBKyJ9nhS6j5Ppvkk+p1sH7mW37iwlHn6zH+WgCiQBAiIYtbCoR6YkNBdRSDHoHEoZKQPMTAy5FTkjVCSJPoxi7Vf2Cv5RccypunSN+J9QqHwq7Sa4PKmO0Xj6XivmJ0yGtrFtZQJPQkO1tS+r8ZMmmMnQt2JJOuUTyXc0pxPyglqCXggZZhQB+Q6bXuyKYXphAVugrNRWv/+Dn1sQk3JGokv20A9HCe3zgj093fEuQH+pI7S8+FktG3vtySY9WAgU8/AiUN0jsVNYZgworT+541zm8GYZJhwsNtS0b+BSFmUxdaImX1HIMiQxBjJwAyCa0QzBvzY7UIzCKFVOSG0oW0ksbNvtJqZu0LY2NrMplppD9BTrhhPueDUwcKEnlhGbEEsDWIMwzkVnNqqFKINoMY6TGJIJE69ZUluwrDq7usCJiEJu2hRlzTqQUxs/4hTagfrolszp/wW7SibSk3gBoKezeWmMD2Hu1UTj4Dp/NikWCu8iYzDSXJdq/JGrWnjxlWakSdwjVC2ZJOa25nFKJR2pSS4psbhxLbjB0LEQDl7NAybQCG6iR+PKRtSsOGKK5SGocvoJG9hVe6f/pDOxUIqI5T2haTxJ2e1J014dfy1kinqG75dOpH/r1V4VbVlzXat1P7RRqsE+2iPXZn1YhYng5y1KZR37S1r8S7V51mbt0lkbloj9nDjP+MZ/ZLHOP/x+vvuIT7cs+u4Gp73jxgBi4vWSqYQLL+q1IZf+MUkeQ1MfOipWewC33GaaAT+B1l7mXpbaiMPKRm3b6x1QuziWB6S4g1RX4XyZyZDULZV8R+alzVR/1ySdciGUIYcsmnPQDU7y+gLTyPU7149igw74jqR/61w7eWmpib6KNPIaw3kaGGgDhEn0IeWbvprDp6BL7+HFL3Js0eTPUzkIQwCP/3UBhXjS06TTXGbORhCIN66GYQHpCLtMSkoHuLkppAGPnbVBjbjC1ZYO1gbnk1iAgVIFwIP86gNv/EUAS9QAcenqHUN5sEezWrsZHsHy9yWvvxHfNFqzKE7+W2o6N0hKZM4NRyoNVXAlRx3mBZ+AeumOX9nedVqujG6A8M6K7td0LqB1DW2BJ9Epj1LNjIFkPjmlz3/D0yNKzgx+Q/0tlnThPW5sjHmMAfm4AFZDcR+AnA7b9LeyYnq7+JldijKPE7qx1CqYVivX5fw6jaLsPqGlSb/vOzmHpE1C5pyou8UiXNQ+5tDHt0kFTgZXa5JVB3Xd9XmE15looJNesgJWL2Y1mJ4xALvWDULHIYHqqYZdkg8ZjZXT2cWnpzYbAG1hae3qN6XXuvSDTSxxfB4Ah4r422YXiGbZtghu5vZXD2dWXhy9rgF1FZfVSN/EbXX2IAPgK+2Z/jKIq/UNZ8H28tzHswuJqUjWFIgNyvllP9uWZOIH+FRAzPrIGRcqzOP8egDnclGtm9cK4TBNq09qv7iedNIbfjKyyzvy148b954bH+o/qwmrNV870O2Z2nBf33x/PpU1X5kzV+vWJHcD028qNo8MP4a7dBoR/PucJd1sz5Foo6kK+4e3WBlvI/L+CIvk/ppgqp4V406+eulfH2mfr7wC9u/O/x5Ko+nslKZPX5JpTnli+dm/i+eazK/aO4bFD5UqMRMKhXYn4eXpyTd93K/idNCmUdjTVxW1n/Lqt8bX1Zds2T3P/qWPmYHYkOt+V6xIzvsqy53yx6P9SvexZ+Hm/gbGyPbp4K9Z/fx7sdVnSSdLzdhjdgdIZv9xaskvs/jx6JtY6hf/VlheP/4/X//Hxvl9Z21UggA + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.Designer.cs new file mode 100644 index 0000000000..532c012d7e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.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 ExportFramework : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ExportFramework)); + + string IMigrationMetadata.Id + { + get { return "201508091512101_ExportFramework"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs new file mode 100644 index 0000000000..5203e70451 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -0,0 +1,485 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using SmartStore.Core.Data; + using SmartStore.Core.Domain.Customers; + using SmartStore.Core.Domain.Security; + using SmartStore.Data.Setup; + + public partial class ExportFramework : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + CreateTable( + "dbo.ExportDeployment", + c => new + { + Id = c.Int(nullable: false, identity: true), + ProfileId = c.Int(nullable: false), + Name = c.String(nullable: false, maxLength: 100), + Enabled = c.Boolean(nullable: false), + IsPublic = c.Boolean(nullable: false), + DeploymentTypeId = c.Int(nullable: false), + Username = c.String(maxLength: 400), + Password = c.String(maxLength: 400), + Url = c.String(maxLength: 4000), + FileSystemPath = c.String(maxLength: 400), + EmailAddresses = c.String(maxLength: 4000), + EmailSubject = c.String(maxLength: 400), + EmailAccountId = c.Int(nullable: false), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.ExportProfile", t => t.ProfileId, cascadeDelete: true) + .Index(t => t.ProfileId); + + CreateTable( + "dbo.ExportProfile", + c => new + { + Id = c.Int(nullable: false, identity: true), + Name = c.String(nullable: false, maxLength: 100), + FolderName = c.String(nullable: false, maxLength: 100), + ProviderSystemName = c.String(nullable: false, maxLength: 4000), + Enabled = c.Boolean(nullable: false), + SchedulingTaskId = c.Int(nullable: false), + Filtering = c.String(), + Projection = c.String(), + ProviderConfigData = c.String(), + Offset = c.Int(nullable: false), + Limit = c.Int(nullable: false), + BatchSize = c.Int(nullable: false), + PerStore = c.Boolean(nullable: false), + CompletedEmailAddresses = c.String(maxLength: 400), + CreateZipArchive = c.Boolean(nullable: false), + Cleanup = c.Boolean(nullable: false), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.ScheduleTask", t => t.SchedulingTaskId) + .Index(t => t.SchedulingTaskId); + + if (HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer) + { + this.SqlFileOrResource("LatestProductLoadAllPaged.sql"); + } + } + + public override void Down() + { + DropForeignKey("dbo.ExportDeployment", "ProfileId", "dbo.ExportProfile"); + DropForeignKey("dbo.ExportProfile", "SchedulingTaskId", "dbo.ScheduleTask"); + DropIndex("dbo.ExportProfile", new[] { "SchedulingTaskId" }); + DropIndex("dbo.ExportDeployment", new[] { "ProfileId" }); + DropTable("dbo.ExportProfile"); + DropTable("dbo.ExportDeployment"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + var permissionMigrator = new PermissionMigrator(context); + + permissionMigrator.AddPermission(new PermissionRecord + { + Name = "Admin area. Manage Exports", + SystemName = "ManageExports", + Category = "Configuration" + }, new string[] { SystemCustomerRoleNames.Administrators }); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Common.Enabled", "Enabled", "Aktiviert"); + builder.AddOrUpdate("Common.Provider", "Provider", "Provider"); + builder.AddOrUpdate("Common.Profile", "Profile", "Profil"); + builder.AddOrUpdate("Common.Partition", "Partition", "Aufteilung"); + builder.AddOrUpdate("Common.Image", "Image", "Bild"); + builder.AddOrUpdate("Common.Filter", "Filter", "Filter"); + builder.AddOrUpdate("Common.Projection", "Projection", "Projektion"); + builder.AddOrUpdate("Common.Deployment", "Deployment", "Bereitstellung"); + builder.AddOrUpdate("Common.Website", "Website", "Web-Seite"); + builder.AddOrUpdate("Common.DetailDescription", "Detail description", "Detailbeschreibung"); + + builder.AddOrUpdate("Admin.Validation.UsernamePassword", "Please enter username and password", "Bitte geben Sie Benutzername und Passwort ein"); + builder.AddOrUpdate("Admin.Validation.Url", "Please enter a valid URL", "Bitte geben Sie eine gltige URL ein"); + builder.AddOrUpdate("Admin.Validation.Name", "Please enter a name", "Bitte geben Sie einen Namen ein"); + builder.AddOrUpdate("Admin.Validation.EmailAddress", "Please enter a valid email address", "Bitte geben Sie eine gltige E-Mail Adresse ein"); + + + builder.AddOrUpdate("Admin.DataExchange.Export.NoProfiles", + "There were no export profiles found.", + "Es wurden keine Exportprofile gefunden."); + + + builder.AddOrUpdate("Admin.DataExchange.Export.ProviderSystemName", + "Provider", + "Provider", + "Specifies the export provider. It is responsible for the individual formatting of the export data.", + "Legt den Export-Provider fest. Er ist fr die individuelle Formatierung der zu exportierenden Daten zustndig."); + + builder.AddOrUpdate("Admin.DataExchange.Export.EntityType", + "Object", + "Objekt", + "The object type the provider processes.", + "Der Objekttyp, den der Provider verarbeitet."); + + + builder.AddOrUpdate("Admin.DataExchange.Export.Name", + "Name of profile", + "Name des Profils", + "Specifies the name of the export profile.", + "Legt den Namen des Exportprofils fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.FileExtension", + "File type", + "Dateityp", + "The file type of the exported data.", + "Der Dateityp der exportierten Daten."); + + + + builder.AddOrUpdate("Admin.DataExchange.Export.BatchSize", + "Batch size", + "Stapelgre", + "Specifies the maximum number of records per export file. 0 is the default and means that all the records are exported in one file.", + "Legt die maximale Anzahl der Datenstze pro Exportdatei fest. 0 ist der Standard und bedeutet, dass alle Datenstze in eine Datei exportiert werden."); + + builder.AddOrUpdate("Admin.DataExchange.Export.PerStore", + "Per store", + "Per Shop", + "Specifies whether to start a separate run-through for each store. For each shop a new file will be created.", + "Legt fest, ob fr jeden Shop ein separater Verarbeitungsdurchlauf erfolgen soll. Fr jeden Shop wird eine neue Datei erzeugt."); + + builder.AddOrUpdate("Admin.DataExchange.Export.CreateZipArchive", + "Create ZIP archive", + "ZIP-Archiv erstellen", + "Specifies whether to combine the export files in temporary a ZIP archive. The archive remains in the temporary folder of the export profile without further processing.", + "Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst werden sollen. Das Archiv verbleibt im temporren Ordner des Exportprofils ohne weitere Vearbeitung."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Cleanup", + "Clean up at the end", + "Zum Schluss aufrumen", + "Specifies whether to delete unneeded files after deployment.", + "Legt fest, ob nicht mehr bentigte Dateien nach der Bereitstellung gelscht werden sollen."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Product", "Product", "Produkt"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Category", "Category", "Warengruppe"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Manufacturer", "Manufacturer", "Hersteller"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Customer", "Customer", "Kunde"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Order", "Order", "Auftrag"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.StoreId", + "Store", + "Shop", + "Filter by store.", + "Nach Shop filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.CreatedFrom", + "Created from", + "Erstellt von", + "Filter by created date.", + "Nach dem Erstellungsdatum filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.CreatedTo", + "Created to", + "Erstellt bis", + "Filter by created date.", + "Nach dem Erstellungsdatum filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.IdMinimum", + "Product Id from", + "Produkt-ID von", + "Filter by product identifier.", + "Nach der Produkt-ID filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.IdMaximum", + "Product Id to", + "Produkt-ID bis", + "Filter by product identifier.", + "Nach der Produkt-ID filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.PriceMinimum", + "Price from", + "Preis von", + "Filter by price.", + "Nach dem Preis filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.PriceMaximum", + "Price to", + "Preis bis", + "Filter by price.", + "Nach dem Preis filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.AvailabilityMinimum", + "Availability from", + "Verfgbar von", + "Filter by availability quantity.", + "Nach der Verfgbarkeitsmenge filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.AvailabilityMaximum", + "Availability to", + "Verfgbar bis", + "Filter by availability quantity.", + "Nach der Verfgbarkeitsmenge filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.IsPublished", + "Published", + "Verffentlicht", + "Filter by publishing.", + "Nach Verffentlichung filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.CategoryIds", + "Categories", + "Warengruppen", + "Filter by categtories.", + "Nach Warengruppen filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.WithoutCategories", + "Without category mapping", + "Ohne Warengruppenzuordnung", + "Filter by missing category mapping.", + "Nach fehlender Warengruppenzuordnung filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.ManufacturerId", + "Manufacturer", + "Hersteller", + "Filter by manufacturer.", + "Nach Hersteller filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.WithoutManufacturers", + "Without manufacturer mapping", + "Ohne Herstellerzuordnung", + "Filter by missing manufacturer mapping.", + "Nach fehlender Herstellerzuordnung filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.ProductTagId", + "Product tag", + "Produkt-Tag", + "Filter by product tag.", + "Nach Produkt-Tag filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.FeaturedProducts", + "Only featured products", + "Nur empfohlene Produkte", + "Filter by featured products. Is only applied when the filtering by categories and manufacturers.", + "Nach empfohlenen Produkten filtern. Wird nur bei der Filterung nach Warengruppen und Hersteller angewendet."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.ProductType", + "Product type", + "Produkttyp", + "Filter by product type.", + "Nach Produkttyp filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.OrderStatusIds", + "Order status", + "Auftragsstatus", + "Filter by order status.", + "Nach Auftragsstaus filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.PaymentStatusIds", + "Payment status", + "Zahlungsstatus", + "Filter by payment status.", + "Nach Zahlungsstatus filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.ShippingStatusIds", + "Shipping status", + "Versandstatus", + "Filter by shipping status.", + "Nach Versandstatus filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.CustomerRoleIds", + "Customer roles", + "Kundengruppen", + "Filter by customer roles.", + "Nach Kundengruppen filtern."); + + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.StoreId", + "Store", + "Shop", + "Specifies the store to be applied to the export.", + "Legt den auf den Export anzuwendenden Shop fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.LanguageId", + "Language", + "Sprache", + "Specifies the language to be applied to the export.", + "Legt die auf den Export anzuwendende Sprache fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.CurrencyId", + "Currency", + "Whrung", + "Specifies the currency to be applied to the export.", + "Legt die auf den Export anzuwendende Whrung fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.CustomerId", + "Customer ID", + "Kunden-ID", + "Specifies the ID of the customer to be applied to the export. Is taken into account for price calculations for example.", + "Legt die ID des Kunden fest, auf den sich der Export beziehen soll. Wird z.B. bei Preisberechnungen bercksichtigt."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.DescriptionMerging", + "Product description", + "Artikelbeschreibung", + "Specifies what information to use for the description of the product.", + "Legt fest, welche Informationen zur Beschreibung des Artikel wie verwendet werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.DescriptionToPlainText", + "Remove HTML from description", + "HTML aus der Beschreibung entfernen", + "Specifies whether to remove all HTML from the product description for the export.", + "Legt fest, ob fr den Export alle HTML-Auszeichnungen aus der Artikelbeschreibung entfernt werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.AppendDescriptionText", + "Text to be appended", + "Anzuhngender Text", + "Specifies the text to be attached to the product description. If there are multiple texts then one of it is selected randomly.", + "Legt den an die Artikelbeschreibung anzuhngenden Text fest. Bei mehreren Texten wird einer per Zufall ausgewhlt."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.RemoveCriticalCharacters", + "Remove critical characters", + "Kritische Zeichen entfernen", + "Specifies whether to remove critical characters (like ) from the detail description.", + "Legt fest, ob kritische Zeichen (wie z.B. ) aus der Detailsbeschreibung entfernt werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.CriticalCharacters", + "Critical characters", + "Kritische Zeichen", + "List with characters to be removed from the detail description.", + "Liste mit Zeichen, die aus der Detailbeschreibung entfernt werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.PriceType", + "Product price", + "Produktpreis", + "Specifies the product price to be exported.", + "Legt den zu exportierenden Produktpreis fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.ConvertNetToGrossPrices", + "Convert net into gross prices", + "Netto- in Bruttopreise umrechnen", + "Specifies to convert net into gross prices.", + "Legt fest, dass Netto- in Bruttopreise umgerechnet werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.Brand", + "Manufacturer\\Brand", + "Hersteller\\Marke", + "Specifies the manufacturer or brand to be exported, if a product has no manufacturer assigned.", + "Legt den zu exportierenden Hersteller bzw. die Marke fest, wenn fr ein Produkt kein Hersteller zugeordnet ist."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.PictureSize", + "Product picture size", + "Produktbildgre", + "Specifies the size (in pixel) of the product image.", + "Legt die Gre (in Pixel) des Produktbildes fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.ShippingTime", + "Shipping time", + "Lieferzeit", + "Specifies the shipping time for products where it is unspecified.", + "Legt die Lieferzeit fr Produkte fest, wo diese nicht angegeben ist."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.ShippingCosts", + "Shipping costs", + "Versandkosten", + "The shipping costs to be exported.", + "Die zu exportierenden Versandkosten."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.FreeShippingThreshold", + "Free shipping threshold", + "Kostenloser Versand ab", + "Specifies the amount as from shipping is free.", + "Legt den Betrag fest, ab dem keine Versandkosten anfallen."); + + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.FileSystem", "File system", "Dateisystem"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Email", "Email", "E-Mail"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Http", "HTTP", "HTTP"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Ftp", "FTP", "FTP"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.None", + "Not specified", "Nicht spezifiziert"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.ShortDescriptionOrNameIfEmpty", + "Short description or name if empty", "Kurzbeschreibung oder Name falls leer"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.ShortDescription", + "Short description", "Kurzbeschreibung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.Description", + "Description", "Detailbeschreibung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.NameAndShortDescription", + "Product name + short description", "Produktname + Kurzbeschreibung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.NameAndDescription", + "Product name + long description", "Produktname + Detailbeschreibung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.ManufacturerAndNameAndShortDescription", + "Manufacturer + Product name + short description", "Hersteller + Produktname + Kurzbeschreibung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.ManufacturerAndNameAndDescription", + "Manufacturer + Product name + long description", "Hersteller + Produktname + Detailbeschreibung"); + + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.Name", + "Name", + "Name", + "Specifies the name of the deployment.", + "Legt den Namen der Bereitstellung fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.IsPublic", + "Copy to public folder", + "In ffentlichen Ordner kopieren", + "Specifies whether to copy the exported data into a folder that is accessible through the internet.", + "Legt fest, ob die exportierten Daten in einen bers Internet zugnglichen Ordner kopiert werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.FileSystemPath", + "Directory path", + "Ordnerpfad", + "Specifies the path (relative or absolute) where to deploy the data.", + "Legt den Pfad (relativ oder absolut) zu einem Ordner fest, in den die Daten bereitgestellt werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.DeploymentType", + "Type of deployment", + "Art der Bereitstellung", + "Specifies the deployment type.", + "Legt die Art Bereitstellung fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.Username", + "User name", + "Benutzername", + "Specifies the user name.", + "Legt den Benutzernamen fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.Password", + "Password", + "Passwort", + "Specifies the password.", + "Legt das Passwort fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.Url", + "URL\\Host", + "URL\\Host", + "Specifies the URL or host name where to send the data.", + "Legt die URL bzw. den Host-Namen fest, an die die Daten bermittelt werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.EmailAddresses", + "Email addresses to", + "E-Mail Adressen an", + "Specifies the email addresses where to send the data.", + "Legt die E-Mail Adressen fest, an die die Daten verschickt werden soll."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.EmailSubject", + "Email subject", + "E-Mail Betreff", + "Specifies the subject of the email.", + "Legt den Betreff der verschickten Daten fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.EmailAccountId", + "Email account", + "E-Mail Konto", + "Specifies the email account used to sent the data.", + "Legt das E-Mail Konto fest, ber welches die Daten verschickt werden sollen."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.resx b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.resx new file mode 100644 index 0000000000..3f0be8609e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.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/201508121735397_AddSyncMapping.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508121735397_AddSyncMapping.Designer.cs new file mode 100644 index 0000000000..da67ef8766 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508121735397_AddSyncMapping.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 AddSyncMapping : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(AddSyncMapping)); + + string IMigrationMetadata.Id + { + get { return "201508121735397_AddSyncMapping"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508121735397_AddSyncMapping.cs b/src/Libraries/SmartStore.Data/Migrations/201508121735397_AddSyncMapping.cs new file mode 100644 index 0000000000..ce062a376e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508121735397_AddSyncMapping.cs @@ -0,0 +1,113 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class AddSyncMapping : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + CreateTable( + "dbo.SyncMapping", + c => new + { + Id = c.Int(nullable: false, identity: true), + EntityId = c.Int(nullable: false), + SourceKey = c.String(nullable: false, maxLength: 150), + EntityName = c.String(nullable: false, maxLength: 100), + ContextName = c.String(nullable: false, maxLength: 100), + SourceHash = c.String(maxLength: 40), + CustomInt = c.Int(), + CustomString = c.String(), + CustomBool = c.Boolean(), + SyncedOnUtc = c.DateTime(nullable: false), + }) + .PrimaryKey(t => t.Id) + .Index(t => new { t.EntityId, t.EntityName, t.ContextName }, unique: true, name: "IX_SyncMapping_ByEntity") + .Index(t => new { t.SourceKey, t.EntityName, t.ContextName }, unique: true, name: "IX_SyncMapping_BySource"); + + } + + public override void Down() + { + DropIndex("dbo.SyncMapping", "IX_SyncMapping_BySource"); + DropIndex("dbo.SyncMapping", "IX_SyncMapping_ByEntity"); + DropTable("dbo.SyncMapping"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + string attachHint = "A file that is to be appended to each sent email (eg Terms, Conditions etc.)"; + string attachHintDe = "Eine Datei, die jedem gesendeten E-Mail angehangen werden soll (z.B. AGB, Widerrufsbelehrung etc.)"; + + builder.AddOrUpdate("Admin.ContentManagement.MessageTemplates.Fields.Attachment1FileId", + "Attachment 1", + "Anhang 1", + attachHint, + attachHintDe); + builder.AddOrUpdate("Admin.ContentManagement.MessageTemplates.Fields.Attachment2FileId", + "Attachment 2", + "Anhang 2", + attachHint, + attachHintDe); + builder.AddOrUpdate("Admin.ContentManagement.MessageTemplates.Fields.Attachment3FileId", + "Attachment 3", + "Anhang 3", + attachHint, + attachHintDe); + + builder.AddOrUpdate("Common.FileUploader.EnterUrl", + "Enter URL", + "URL eingeben"); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerNumberEnabled", + "Save customer number", + "Kundennummer speichern", + "Specifies whether customer numbers can be saved.", + "Bestimmt ob Kundennummern hinterlegt werden knnen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.DisplayCustomerNumber", + "Display customer numbers in frontend", + "Kundennummern im Frontend anzeigen", + "Specifies whether customer numbers will be displayed to customers in their account area.", + "Bestimmt ob Kunden ihre Kundennummer in Ihrem Account-Bereich einsehen knnen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerCanEditNumberIfEmpty", + "Customers can enter a customer number", + "Kunden knnen Kundennummer hinterlegen", + "Specifies whether customers can enter a customer number if the customer number doesn't contain a value yet.", + "Bestimmt ob Kunden eine Kundennummer angeben knnen, wenn fr diese noch kein Wert hinterlegt wurde."); + + builder.AddOrUpdate("Common.FreeShipping", + "Free shipping", + "Versandkostenfrei"); + + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.ExtraRobotsDisallows", + "Extra Disallows for robots.txt", + "Extra Disallows fr robots.txt", + "Enter additional paths that should be included as Disallow entries in your robots.txt. Each entry has to be entered in a new line.", + "Geben Sie hier zustzliche Pfade an, die als Disallow-Eintrge zur robots.txt hinzugefgt werden sollen. Jeder Eintrag muss in einer neuen Zeile erfolgen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.DefaultSortOrderMode", + "Default product sort order", + "Standardsortierreihenfolge fr Produkte", + "Specifies the default product sort order.", + "Legt die Standardsortierreihenfolge fr Produkte fest."); + + builder.AddOrUpdate("Common.CustomerNumberAlreadyExists", + "Customer number already exists, please choose another.", + "Die von Ihnen gewhlte Kundennummer existiert bereits. Bitte geben Sie eine andere Kundennummer an."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508121735397_AddSyncMapping.resx b/src/Libraries/SmartStore.Data/Migrations/201508121735397_AddSyncMapping.resx new file mode 100644 index 0000000000..3299ad9347 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508121735397_AddSyncMapping.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/201508142203054_CronExpressions.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508142203054_CronExpressions.Designer.cs new file mode 100644 index 0000000000..85ac9e4af4 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508142203054_CronExpressions.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 CronExpressions : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(CronExpressions)); + + string IMigrationMetadata.Id + { + get { return "201508142203054_CronExpressions"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508142203054_CronExpressions.cs b/src/Libraries/SmartStore.Data/Migrations/201508142203054_CronExpressions.cs new file mode 100644 index 0000000000..9f12663ade --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508142203054_CronExpressions.cs @@ -0,0 +1,174 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Linq; + using System.Collections.Generic; + using System.Data.Entity; + using System.Data.Entity.Migrations; + using SmartStore.Core.Domain.Tasks; + using SmartStore.Data.Setup; + + public partial class CronExpressions : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ScheduleTask", "CronExpression", c => c.String(maxLength: 1000, defaultValue: "0 */1 * * *" /* Every hour */)); + AddColumn("dbo.ScheduleTask", "RowVersion", c => c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion")); + DropColumn("dbo.ScheduleTask", "Seconds"); + } + + public override void Down() + { + AddColumn("dbo.ScheduleTask", "Seconds", c => c.Int(nullable: false)); + DropColumn("dbo.ScheduleTask", "RowVersion"); + DropColumn("dbo.ScheduleTask", "CronExpression"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + // Seconds > CronExpressions + var table = context.Set(); + var tasks = table.ToList(); + + foreach (var task in tasks) + { + if (task.Type.Contains(".QueuedMessagesSendTask")) + { + task.CronExpression = "* * * * *"; // every Minute + } + else if (task.Type.Contains(".DeleteGuestsTask")) + { + task.CronExpression = "*/10 * * * *"; // every 10 Minutes + } + else if (task.Type.Contains(".ClearCacheTask")) + { + task.CronExpression = "0 */4 * * *"; // every 4 hrs + } + else if (task.Type.Contains(".UpdateExchangeRateTask")) + { + task.CronExpression = "0/15 * * * *"; // every 15 Minutes + } + else if (task.Type.Contains(".DeleteLogsTask")) + { + task.CronExpression = "0 1 * * *"; // At 01:00 + } + else if (task.Type.Contains(".TransientMediaClearTask")) + { + task.CronExpression = "30 1,13 * * *"; // At 01:30 and 13:30 + } + else if (task.Type.Contains(".QueuedMessagesClearTask")) + { + task.CronExpression = "0 2 * * *"; // At 02:00 + } + else if (task.Type.Contains(".UpdateRatingWidgetStateTask")) + { + task.CronExpression = "0 3 * * *"; // At 03:00 + } + else if (task.Type.Contains(".MailChimpSynchronizationTask")) + { + task.CronExpression = "0 */1 * * *"; // Every hour + } + else if (task.Type.Contains(".AmazonPay.DataPollingTask")) + { + task.CronExpression = "*/30 * * * *"; // Every 30 minutes + } + else if (task.Type.Contains(".NewsImportTask")) + { + task.CronExpression = "30 */12 * * *"; // At 30 minutes past the hour, every 12 hours + } + else if (task.Type.Contains(".TempFileCleanupTask")) + { + task.CronExpression = "30 3 * * *"; // At 03:30 + } + else if (task.Type.Contains(".BMEcat.FileImportTask")) + { + task.CronExpression = "30 2 * * *"; // At 02:30 + } + else if (task.Type.Contains(".StaticFileGenerationTask")) + { + task.CronExpression = "0 */6 * * *"; // Every 06 hours + } + else + { + task.CronExpression = "0 */1 * * *"; // Every hour + } + } + + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.Delete( + "Admin.System.ScheduleTasks.Seconds", + "Admin.System.ScheduleTasks.Seconds.Positive", + "Admin.System.ScheduleTasks.RunNow.Completed"); + + builder.AddOrUpdate("Common.Rule", + "Rule", + "Regel"); + builder.AddOrUpdate("Common.Scheduled", + "Scheduled", + "Geplant"); + builder.AddOrUpdate("Common.Unscheduled", + "Unscheduled", + "Ungeplant"); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.CronExpression", + "Cron Expression", + "Cron Ausdruck", + "An expression that defines the schedule for the automatic execution of the task.", + "Ein Ausdruck, der den Zeitplan fr die automatische Ausfhrung der Aufgabe festlegt."); + builder.AddOrUpdate("Admin.System.ScheduleTasks.Enabled.Hint", + "Enables the scheduled execution of the task in accordance with the cron expression", + "Aktiviert die geplante Ausfhrung der Aufgabe gem Cron Ausdruck"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.StopOnError", + "Disable on error", + "Bei Fehler deaktivieren", + "Check the box if the task should be disabled automatically when an error occurs during execution", + "Aktivieren Sie das Kstchen, wenn die Aufgabe bei Auftreten eines Fehlers whrend der Ausfhrung deaktiviert werden soll"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.LastStart.Hint", + "Start date of the last execution", + "Startdatum der letzten Ausfhrung"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.LastSuccess.Hint", + "Start date of the last successful execution", + "Startdatum der letzten erfolgreichen Ausfhrung"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.Duration.Hint", + "Duration of the latest execution ([h]:[min]:[sec])", + "Dauer der letzten Ausfhrung ([Std.]:[Min.]:[Sek.])"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.NextRun.Hint", + "Date of the next scheduled execution", + "Datum der nchsten geplanten Ausfhrung"); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.CronHelp", + "Cron Expressions help", + "Hilfe zu Cron-Ausdrcken"); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.FutureSchedules", + "Future schedules", + "Zuknftige Zeitplne"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.EditTask", + "Edit task", + "Aufgabe bearbeiten"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.ScheduleExecution", + "Schedule execution", + "Ausfhrung planen"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.InvalidCronExpression", + "The cron expression is invalid", + "Der Cron-Ausdruck ist ungltig"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.RunNow.Success", + "The task has been executed successfully", + "Aufgabe wurde erfolgreich ausgefhrt"); + builder.AddOrUpdate("Admin.System.ScheduleTasks.UpdateSuccess", + "The task has been updated successfully", + "Die Aufgabe wurde erfolgreich bearbeitet"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508142203054_CronExpressions.resx b/src/Libraries/SmartStore.Data/Migrations/201508142203054_CronExpressions.resx new file mode 100644 index 0000000000..62a0f6a2fb --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508142203054_CronExpressions.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/201508211346171_Merge1.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.Designer.cs new file mode 100644 index 0000000000..7884363732 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.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 Merge1 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Merge1)); + + string IMigrationMetadata.Id + { + get { return "201508211346171_Merge1"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.cs b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.cs new file mode 100644 index 0000000000..146e495730 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.cs @@ -0,0 +1,16 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Merge1 : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.resx b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.resx new file mode 100644 index 0000000000..3f0be8609e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.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+w8yPc2snZFK6m6zPm1VO0ZSpEQ7EsUmKemcfqGBkSAzWpERWXHhpcfmy/ZhP2l/YQHEJXG/R0RmVj5UKRlwOACHw+HucDj+v//n//31fz6vslePsKzSIv/t9bs3v7x+BfOkWKT5w2+vm/r+f/z19f/8v//P/+PX08Xq+dX3Hu5PGA7VzKvfXi/rev23t2+rZAlXoHqzSpOyqIr7+k1SrN6CRfH2/S+//Pvbd+/eQoTiNcL16tWvV01epytI/kB/nhR5Atd1A7IvxQJmVfcdlVwTrK8uwApWa5DA315fr0BZX9dFCd98ADV4/eooSwHqxjXM7l+/Anle1KBGnfzbtwpe12WRP1yv0QeQ3bysIYK7B1kFu87/bQNuO45f3uNxvN1U7FElTVUXK0eE7/7UEeYtX92LvK8HwiHSnSIS1y941IR8v70+fV4XZf0BrrPiZQXz+vUrvtG/nWQlrsBQ+YSQGoGl+Rsexb+94gD/beCR92/ev/nlzS//9uqkyeqmhL/lsKlLkP3bq8vmLkuT/4AvN8VPmP+WN1lGdxx1HZUxH9Cny7JYw7J+uYL33XDOF69fvWXrveUrDtWoOu0Az/P6T+9fv7pAjYO7DA58QRGDjOojzGEJari4BHUNyxzjgISyQutcW+jP+zSD5ib1aPD/ewyIn9G6fP3qC3j+DPOHeolW7C9oJZ6lz3DRf+mwfstTtIxRpbpsoHOrpzkGHrp+XBQZBLkzmvOqnexQPBuOw3hCSYokQ5nryfpnS7IaWABU1VNRLkZv6FuZ6duI0cgZ4ubrl6qGK7QWlqOP6RRJnOxosShhVcFq/OGR9q6bu3/CpJ5ocElSoI3QmZ0vwGP6QGSUXOS8fnUFM1JeLdN1uzUKovt2AD4ri9VVkUl2iB7m9rpoygSvl8IAeAPKB1izPf717WYrstighn557k5d/cPWpG5rnj3lrMgWsJynbfTnY4pab+WXoQ/WAmWeTRXrxIsmQ72+AdXP0M0QSXXEPIQESoKgnzF2w7LAspUot+M3RaYbGRb36UNrJ4zc5Nf7+wrWYXPxOV2lgSiOQZ0sr9N/wTA0l2ipYBkTyqsnxWqdQSSjXHbzCLNxUkIkGf+Rro/KZJk+ho8D12rWzmiUW/Vm76zib9f9Lmzcrvt93bbXneiBWPBouj2gZ8D5jsugFJqGFDRI12C75qxq4GrVGxrJQd9Qt2XYb/8yznZLHEMRmjXsxjAp8kUVJm5xXU1P/7rd+khdrL/mp2VZlKGoLuBzfdXk3+rBY4D2bniTrsxbJ6jQ2kML0LPuab7wbbVJErSp+bZLk02uD8fg0vPqU7pAqzV0gtCfD3gLR/pBQlyIDNfb1f2C/gMPBiPAfdDWov/vDSCfEC4fH+iHtERabFG+vKERHcS/ZlWbxP8Ywu0DrJIyXRvMDcu2DU2l1ToDL5+LBGQRBmrV2NcSmTdhm8559QHeA8STbkLBYZ3Bplf86xokS89Dh05eVG+kCA8LT90WRbBQNwHuCZoEzOPMkiJKPzsjAqjrHqP3LkfaEc9kZzOGpUf5EY7THJQvdn6EIOn4fhzd7wvSTwxaZ6yWldbkmcpXLV3ot2ec+auGEqxIDajMitR1mkLl0HemlmkI1FfLkdA1gsziD8VTnhVg4SWmFyl40yM4iGWNHOlo9LFJh1bb3+4HmT0u6gTQ+5BVRDWat7RvqxejsYXqSZHX3Wnx6GPBYsRwnBynodNnNKZqCv/5eXUBn8IP/29KgPpL2Ym+qL6tF3ilfZV7BqKrq5GU1IMMVLd1WaZFSeCCDq/QTjq9YxG3GsmlaXALFtMP7qaYZGhXcJ29RBmfVTuTjOnkZPQmjpNk9DbMMTCRTLDjYvEy+lbWngR6bR0yZz/SKcoUBrr7MRpFh0YMGxI7sfgC8gZk2Yvz/qw0jzZGifxcM7J5xJ902htUtgOiSW4a0S0LLB0IA6Mz71jAsMAqplv+ug2N56DcGJapzrPyl7+M4vJuXcMmh5Jl4/q2PhWVbocYaYSXRRkYH2MR+ztW383BwCO13J7zXlfBHgLsb2iPDNCuStYCqjXS6cElIKEqX2C9LHzcUV396g2D6CC1dAxKEWreQMnnJGsWcHFCJh2WeG88X4wfyzG0ize48kXf5PtIYeddm9dIo1ijNlryTzHabpSkA3gFEJ/Zc7Be+SXN01WzIgeUR6tW5ei0XJikK4Ck0GWJfnU3zv76+tU1PjdF3GTEDJ5Hwtxiu4IVojWJUI1EizMEbncQ7XWJwV6YlsWiSerjJl+glYQWNlKOy/SuqWEb/ushX09ADbLi4Y0J9UHiqtvaEC2U0QaiR0P0HWRNMLbzCi1IZCXBpPaIM1PaZBu6SS0yE0ve0vU3RppDNcFuc6kbZMoJDcVcuoe1qm6rI1boimiJHQlZH4AWhgWZjUnMzbTH18UIUqF+3mGhRjU0httzSWLkbTbraOeGiAUHQeFoSElM8nQBb5bN6i6nvA++yL6jib/LgmeOCIdqGR5kHC/sLapDOOBg0uBFpXcPuSt1gm2Od6/6bJFum3rXgt2Ab7k6mkGyoOb9m4N3jRJyG4bVAOy7ruu0q6IRrl4clAqNeGxJhJsL1QUuQYna/FgWzRp1JI5+0W0C5/kifUwXfidF42zlrmddk+/t1oZ/nPaOFqs0PylWKyrYZszbtYRr4QptyeEmJ5qcp6/5p2IFLyPoij2eeArDF1gDJDSw7378i6u4sSkZB7d3k9baewtxRnaUZcXT4NGFjyl8ClZ5j9brsniEiyskjfOH62YVNtMXRR0ZY4/uBm0YGTfo4D7GQ9qFfNwUR0mw5UAusePOkb0xeIqvfzbjLzqQN/cgwSpIifbR+gIZURupMVqzHxGLjd7IefUxva9PQBlsgvV4YmgrV/D3Ji3h13qJKN5uJ8Gc0uHcKD86aR0phOmowbpxnSZYNzpaLLg+BI/pvNpE5MeJ7g5OnpVn7QLvEQaPEc1Ku+C+3gs4PT1aHZrT53VaEkX9A3jhcdqhOErq9JGgiMH2n0B1DXBGjFizymJzvM+EeoPDIY4eSghpxTEgPGCD7AY+j6+JnldXMGlKOn2Nv/ToEJ28JBlsOxUq42iMl7BMi+DVN+Akez9BHLhWzit88hwt398Zmv7+KDtYu1ssUrzyQNZjPFlil4a9o9r9xgJ4Pn1Gdk3wWjivTqtgcqLenCAD66HAsQiBRgzIkVF0nj+ipYnQ9VEGgWpjXSQ/ox1CYHONYDx6BCmqm2YU1kC/sbSn3htWmkcc+efiqR013mkQtuCEoUWd3r8Qh8BZUfZ9PIbI+go8ugLJzwKb0zhJb/A5M7YGMcbzlpbIAhmM3mCNghj9XRxKnElqMbbxJ3EwEgLARYcs1ebEiqSsorWAO3jcvBw3db1xLgSsLQz9I62WWVrVcZB2iz+DiHmRXGf8N97Zu5B2TtClSfiJFo0k+g70NVuM20BnmJxQ4bvR27heIzwgcxyIPU6S56c/ZfO4RUHjOs0XAZh6d9Zpjk/povBXJ7YYzHBkpugE26RtIuPjJoXtmgwW+GgPhVU9HEaeFKu7NO/O5SIyIepzH9xQHa3XWRquMf+A6cNyvKXI2jHR0f9IFyNi/zQubYadJlSeDIjChEm8E4t4UReQJO8M3n+3J+RCHGD6CMsXXNnRe0Jn/3Ksegwq2O0+UczuAd8XCKqmhG1mM6U6GSUP1dBm3EjsAS3+waK2Iy0JgyAnWgYPVBz3ehcmAksceRHL8cEgxdSIjfN6WbT+FFC6ezrUUUvtljhskdLol770toO+KTa+8k34ixpKiEnSgLqGINHSQBe6c8sCClE7TLkqYIcF8gww2kT9WMaI+YdMGWPBFCFWjiPqHF7EIFYPqHeLaUKneBDVAAQ4z47Th4javtOAmv7LwFRjkMJ6juMyJXi0Q+hgNL3nIFQd58E8+zwcgqu73IJoeswCqDrMQXn2l1ig92mbII+OAFb3X15FMx59BdX4DLU8x3sDHrSDu2XgRHlKFSvFKQ3jKU2/gzJFSp3MjtR2X1NPM0EWtVSzZFPVc6p41E4Ddxit6xC9x8Wm+9XwIAsoMiFdruRCBsiVDfmzYU1neVCxuyyEssMcmGuXaR+OpLtDsYwzhEKBF0QI2ezbZzccbrp4ZH3ulNrh1yGuWd2WIcp3pJym/czEiNT4VmFbKdL9JfWNqOhOrb4pV6vc+eS3c5n5JDFqvWQeNbvoquqkaNb4jvYieGZETAFvUtnNDImPJKsslEc3mDDxXONATGb8TWEwxUTDm66gs+U3cMK2pAV23ZwkLoYxPBLmMQjOC2uPRIeq43x1Xq2hTXkNyThkgOqhSKGDLhj1dnfADaMexWErVrdl2IpHunCD779EatnkwJ/ursRxUSM+nfRaj+EJozg7U7+MYl3oOVyYifR6ML7RFysAsPO0OZ5U4ZtU4a8JMld/qpuizQjC4/aOyOnwfGWDxwLsDosIoCuQP0zwjmHku3Hx4xe2+ObQtp2C72UKA4FUJD3g9xQ+fdEbVZ7SOPbho4fJYj6AlNg31l7YVkxLe785qeqBNj3mywRNXgAIyw7Q98DzmZCu/kFx14ivlkRjvZBh8QDQn8e5iw8Lixcz/hTp8uPevGMR++RWFCFyCOPJLSdv/MQJgzM858hBvEyVz0xlVkynG3ltpd6rQXGMplo0jis4bsyFqa8x0gRJ/IJRTtcofIdVrF03UR6DkJD9qsmgVRboOH6V6zUc/dTqOM0yHPXYp3cOvP3YX0qNg67PPkwMCCbxtZPHZoNmkPA6N0WcyaMapZOIT9lwPxtmL1Dsljt/heM8nT4jQUW/KDqBjbyJfxjvBElpHWvPm/z2HiYKN+AhbxrRYbdRt2XYCMZ5yPukyIryE3wmCaknb30n3vZ2tbMiHL1ymA6rRt1WJEsr1kHQeXUGAZ6IXkGY0a+t3Kk2HGpxB2ADrL4EMPxQGEUi4AgmnM+tBVN/o1hxl+I1gnDZQGM7yIfR5QNN7j+EjGC51fK+DVtJf+mG+UOxDtUVRpAfITeHbPofRZZEEiIH6bHN8V2TRtxQnHAIUdquEKVDUNEOBhUd4mx2L87G65SL3dzFMy5ZuaAoSIFiWBvtVdpwO6PFE01HeIUzT3Ttk6ibTsSQJwHz+vUUurxJesdyYWPCTZKotU2nHkaUTzBb3zfZf8GKJD+Nguyi8MEVosO73kNX6O2K2+qOHW2xdLTIYVWhtWpKvSDUueXWs2JEyhr6q/bqakFnB/2q9hE/vc4x/DrYJ+q2ehp9bNKh1fa3R3ZpQ9BYpFeADa+3x2nF4jnwuA2dFeUKhMf7dNiuQaZPMRUlu/6Ur9pETP58dH+fZmkEM5XkBA62ZSLp1edVGw5ylDDPRXrfHLCJLYnDR59BVZ+vjxYLZO1McJ0qptWAu/65eEhzzyvTuH6fWVqJwv+qcEtShcLQi/5bCoyKWBdKxZh1EcRV0elCfoa513WTh5X0lQVRd5iDc+01b3Pout3B3G40GrHfPIygcikBXe8v0JFLBnJzoBJqMxBqYrNgrrQ+fcYKDciOmnqJVZo29dEVTFrfoGQEuhrSabCqIMyJXS3XCUJqQLO6LCrFDZmhWDoQsVTotQTEq4s3xTpNNH0k5epOssXyXnIwrt0kDiZ5D0mRtHNsidAvrti1S1cQ2Rg5jrCCqglmQKRdlEMIXVWAuXf5CZSLyyLN6+pTWtWqQ38JnKL3ajjJGDTAriPpIxCt9hoBWCL+OBi1AOQBnfNOUelA1bkkeSgp+ZVAAu3VkEGG/UBQd7seGRpF/qZDcLDp1W2dpWVVTxIVjtXXSRoyGf1xWkEstgb5ywTtyEPSDRYZ4g6cvvUxzRPXM70T6kGUEV+5JUvz3VQNvR+9oX+ka6wrgcyQgirSw73LIofGxy0jvYDcv+o3PrMH2dtqS6ZdQ9KtsN9oB5jNBsgVCZs2X+68V9OLVNs5DlLsIgOg7CgLFZbyqSdXQKx+h+OwPavbMmyYtl4un3iMqnN8BHtQCbJY+fRvnorPEBPxvCoMgvZ9BIF0sywhtG3wTxEaRIIWlmnCNRb4FPR3EOzijRcZEi2kIzT2RWPH8jfRhjtwUluKhrndVG7FG5eOxAwtsa6MVZztW3ojUAyL2Ssku6MUQOy8FCrILGTp4bED9Qi4qTvsQxqxtPehqfHk0vlDjmjdvm8bHJF3+pxkzQKtdeYase7kK845W7dSSfN4CZDThGf3M2cLIUvJySnEK68cO0hkP3HF6u4B+jKD6SCtjMwb/PzuPMr33V0JH1NgkHpxDri3Qa3zMts91RNh8WuVmKBwtOHAN0JUWofrsOY1a74jVfB9OYvgkjj3BM5x4k7Eenv1KKMxmiB+AITqLE0ZKeG1rI+z4mGIX3Ne0rh29YbCsR0x7l1nJokbx4PHDvJ4KmSPUcpSFK1vN4AbdpKVC6wkBQpmo7YrATyEERz2AnVbn0H+0ICH4JjNWHc3XHNbFYsYB3Bel/BaZg82W/udrcN3Inn81VUZL+r4SNtH6kYWfPM8sHO4FruNFyxHOuCk9im5F8VrN+R9udot07arvWxWdpNg3UCxfWQKpds1CxG0V2864bxXkyRT6b8I8Jsez2HLVrc1j6+ln5mO6lqTb4z2Ue3fG3gNpzjKPMvAw/kKDRan7Dbe1YhxUbTe32ckRvB2tXnp2sm4ghURbXJhLoOUSkwtoCDe9dBBR3Yy1MFSVYLzIGHNsi5CcltCa4MIGelh0L51UwrJkYyk8wqvrsuseUhzQYgYLyUWTSITPlFUp0hCgVep7ESIl1C4VNwNj5VGgkK5Hd42pnehy/AHqLoBxgt8uWRv/0+ZOECbG8Gcb8CLAS/gU+Xv0sW131AotoPHeo/uJFlHpnQfY0rjuyXx3Mc9RimjUxN7uwHcsLWsXGBiKVAwy7Zd8edXXP+gKKnb2nHv8fWyKGMsR8dmz4jomrrVaBbdPE7aP6LPfas8qgeneZSR+Zksw5YoM1OEQunuykK4XsCh9me5p8VLC+D9KlpVwc9yKrLse1ETkywhiYectQGMonrDI9oOHRb36iivnjyCedSmzYBTbtdwhLil4SljRg0mWjAa2DC7mepawLy3KA6KoJ4PR44hHUktaq9rfr1H/LfZYbfoOb8CiwjFMuyWSAvDLj2qSLrc6HLnNIrcclUkTgyXE0KKRAuZ4i0ngiTEQTao24plJM4jH9qEbZ2mO/5d9WhG2rJ4+pp/QorEJdEWA+0lbHx9JOl1bgosKbfnauM81uhoFyq1VgCR1jILgCmQynt/zX8jY9Vy3nIjkslz2UYVdJkAo45ykwAjOsh1dVumU82//GUU51kJYbQb8rFSnyJEnUTDeLgpMIjWKMlO+yylLfMHbh02KUptZ9daysBylVaVIQchD6XOpqiHFCWRHjxMueRw+1zDhklTIqwCAQ7yyVs+jaovziMbN08Cjt5yQCrUaItY0HPs1rzXItalJQ3SNnSID6tbw+uRLjFOkwKvn+V2dPfpBAnC+iY7xWTClr9iXibcN01TR0kCq2qaBi/xBWhktFqJ+dgvmWsvho6Un5kXsm5Znb1k7ZBM2UOwkrrVJqHzQYSq2yKZoIMfaI4khyNFRrnaTZc7+T7CHBfPnXOk85JDk0bdtoObBOaGLtKAsk5SmdDV3aSAwuVZ15kggUZwHCSaui1Cpa2RaHi2MIJQRF1yQl0siuUtJPez0e7NhJDuf0/hUyAKnBDbKyGCAk+sKe7xtXLX7QhhPzYEtwcppLJW8WiF055g6mAHJO0d+anrWgsQvgUESv+D4Fe3RQj0sSya9cinzCPd1ZoyuPFi88xMEKGibA5YgkaxQ/Zxi/hjvem8WcNqaX5LA3HSnCqTS3MaIFyad50IEukEx0Gua4TM3kvjvVzjU0XLtGspXFxIH0qTyRMvcdHNrbOkaJ9aa/85CAl1W4RAEz27QtoKftWZDHhb/BTsq53Bzgr2VTbHB5UIeXGm2SZaR+JguwQv+FZKVGRtGu0xT5IUHHPSlCXMk5dJnl/qG7sCm7DRDzBJVyB7/eqyRL+IFHv97q+vX13jDAq/vf6r91pgA7FC5+k7qKcUK9fNXY1EYnaeJ9kNRmtFrD87D4tpDKetn6KxG9wYmhvyivZUI2QanWakndSZZoRdY5OODDXksJTdG2OEI9pFUqwigOwMwrFpqm55bAKrWx6b2h3+8VN7Eh4anUn7tT5uK0SsjNYEUrybfAEXR6tRR0K/AvwDlhCxXYT06kuY/Cya+qhGfHTX1HBKU1povPrP1QSvm3aqR6xrTkf392mWojUZzT5Yj04Dcj8IGzX4ZeMSIvl1gniL1Z28WQphoqPqx5tI3OVJ1HCBNuMlMgDVT7hQTcmoIzx5fBz/JVnc0OnzOi2JI+FLkdfLidv8LwjGpye9vtpHjj7AuzT42gqF6igh++anIltMwB9iwxMxJtXwMch/TmIAc21OImLoNs9PpmxueF9ouibP78AEykW3mxIFcEiFN/a6b5BBUHZpXG9KkFcgwT83qsHkTU+yZFSNX8GqycYn+wlYY0f6tAQXG51otNfN3aCjTzvky6ZMlqCCUzrwL0Hqe+mbf0117HnpmsP2ORI466Y9LZnca/wBZrDeq5e3NJbwFcRHb9TdYqtji0+guoBPnRfnosD3bBLmVcLwo072lEZ65EmW0S0PuDn2lJULkRJSINeYPG3cYNuCLGSQLVF0zTtQsPcNfauQlf4prepC8WCiDPC2O8TddFYNJRwna0Bd3wT5mN4TQ844CBng7bcKLn6k9VIYjBlaGJRFFdfBkVo455uGv0lKOKH/XJHQWb7cq2cXbYooVc9wsaJnmyJ5z6hy155dwQWEK7ighdip8i1QGsrIFEZgYTDmGs7Pr6O+q5MO9qUi2dkS6UPxVLFPr9ZW0liA5KUdB6AQejxUUPiaXBB6vLbcoqmkAvMQsKJuazgGDfTwtlIt1E08zjtbm5MY2x12U8OwyfYfhbWih3bVFzqZMoaCYNVxVnJ5rXS5tuAbiCbDdljm6rZ6eoWuUGYHjYGMeREm+sHiSAKlp6a9trupYVB0+4/CutRDuwoUTtGaQmm3GpBcpwsSOOFC5iBY1G31fjNKKsie+7CTTjEC5UYOVTiv+s6SbGkggjeqR3hSNOuJnNpXiBjrFDuIpvDaDa1Nk+DmGubYjp1iZG1T0wzrCzK2qPypo7VzXm24g7gP5/a3juSAct+SrdxOwgZuOxq5JNWO51ZVRxyRAlS5K6vgg7ZkqodBN1AObyHZGOOISMG3Q6Lo+Zft42+haP7egG70QUZHntaXZZqMHsI7NDR6xO4Uo5lkJGNHGPe+jVZHHJtkbGtj026WMNdpw1s/FE95VoBFhOeszqseWTQ1/jNaIHkFe7yOJhCW1j9g+rB0MF0Md+mafJFBpGaB8QMZWgF/QvLkxeJvpaJ0VFVFgqOSF72yIj/1iKglqTQ/k1YV7t91OGiUHtdIDiKtFdJ2Vg3dGqBkHesKNV3rIcL1ygvyFkSQXolRHPRKdVtRtMEL6s2O6bIr9lcxi02wx1batAZJYHewL11ukoN/r8UmOVgPyjotwXdYgxq+i3TVviV4DBzHIAN5AgPNsmr0W2ZTOdBGkgzaQDZZtIssrE0HJ4gNLXD8E6fIAUE2o4l43NSH8fg8qdFF1AwxRAf5p24rig5yU4LkJ6L4RIHe5JJuXOuO8Az0DR//ALP0EZYvntUn132sg+v4Ra+IvXMN+VMHgtIQtxsZIHaQAVAGAbJQQameaJQxhNLB6W5ekYROUSLpZIfo0/jLjYsh5jpQrVf5avG0VxA75lfwd/xmpL+DgEFzWAeadRAjoVi0RRDLXIpz+HQFQVXkZ0XZctP0bpCOfyFxe0c5IAjrgP7ylN3Udjdgx88bc12D+/uLYooUNUeLVZqbr/b++ZcoD4Iwsi1OXrktumLna1FTNFHY0jIIid0pBQs0NguinJ2gTSgsnoLHdNjZdOs/ws52CcpOv3E8IWzP8zwq0lMcI8IymgcyTljIPCmW0JKEJeIhHBYxmscyjtKxC6J4w9wK64KVUrc0PG1jKMEkloYaNuodY6Eh2V6iBDL329sjqzvfFVqRHPOqYMxdjnLo2yG5XqO11t9xH2SBz4kUqEFWPLzR4j1sj6PLcznhv67ZRCCeewVOXnWWZmhgEd5sR3z99DXvhn1JHS35J8HyTsDvtcq1rC5b8lYVxMeQrWo5+2Y1fOIxWj06WxLosDjSRYsqzHrQDtVbcOrQHuSmq8Ab+fGoccNsxpBgcjIp3nWfe6nz5zsRpEaYXJRredp2FXgoBdC9uqgbeuAYQfhFF3sHgfdHEE067lWcF0+/5ISz5oBlG2Kp3YCHcLMMITksLe+l9edIj5SZ9mrtrny7mcpKst3Sxap9lIGJwZvfQZmCfJOu/aRY3aU5CNSIzcgPvKxu6/pnE4GVDXfv61R3CBqnkS8gb+5BgiexvESzbYw6i9Oso2/acLuJdCQWth/pIh6yT3GvTYEKEsq5RiNbosU/WNRWhzhHVZU+5GgVpYSPzhe6M+h3cY6F+yhB7Nt3PHPqTy7wJVjXm3AVuZIX7EzbyeMlJDKTn3GOfYiv82tTf70nSNvzXmeiqpOqUcyh2+01m98ti0PQBGyrqhxr1vVjnpxYtK12rFrU8hmsr4uVXsW+g2VxOI2YruozbKZ+jLMmvrH4quFBHxxdoHdoojlab+BzjT6t1uOnx8epeX5v0jL8sjq1XvK6LLIYcSEznV7xS8hBvLrKVG9ByvOcz3ik/iDbOq4j1DtbLYbKIyZ5HrVuAWmNW6XkNdJAW1/lWHBDEsPzEHEzOewiFrvIUZYCg+0UQVLP5OIeLfVLqKbUpXmNrS4RtAduV7eloFt4HM0OLSJDdGSRFeX17w0oYXX1cDf6oFrf0uKfaCXQwf3RY0fb66YTNHReIUxoI4dJhCRKATokh4mIhhhq7ec0/4kDfefMYeeqbW2PnmWpd9opa1470k0Ky84V7r39DDgOe41OssWJ/4xxy6IPisZM5+m6DvUyRL0YYAw9xwOVrvqBdW9Z0M26lkMIK1cBFtN3umlCYsMLhZoe6ux0+0ghzIg+N+fxl6r95yAv1G1NFKvAtfqt1J3ijNTodZWd5hg+WEG6hgnilTiDMByrFpX2Tm2kQ8bPxUMxHGrO5oDkx16vsuNi8TLFW6tFXqP10p9HXcD6qSh/ThEcgATTC1nCJ01Zwjx5CfesE5ynz8kS5A+QPBbqi1qzfygbkQfc4RHeamtR4XUmYDF+1VjDfYMUZ8Y8Mg5cMSQGSj8WFjRoI910y+NhN/y0dlG+vOmRHHZUdVuGHfUvo+xt/cQYHi75yyjZJOyzhf/Vdzv5XGAE4WS1MVjOinKFJp26mzZWe5/TVYpY7KZotdXgYBu8wKrlNjmAtujaMz9GIttO8wWa2egalpt58wWQm7mBVk6H5SCa1W215A/VsFos8xhOHn4Zex/dEq4gcfvdZT4mN6lfvWHQHLhR3RYh1Og2RiQ+tfDza1p5H8dMHZP7j0Hy8zwnUZrXzR11hOvtr1ZgPCyJqBM8mgs8VoadOTJRK1hPmvXEBCtYqMYKMb3RqsYkvmkDqPU4ovit6esmN3CF1Omg4CIZuoMgUbc1j3b2PYVPqKvL6Vv2tdjs3UdoGh6K8iUCL/OoDnx84OPJ+LgT7hHYmMN04OIDF0/GxWQq0iQobLlYrYr8DY/pwMbqtuK4j9CIPpZFs56eLVHLM6xCg2tgpCjsMV0FR0l2BZMCv1rt7rHFoQMI3ZsByWHFjb3i5nTYamPgnIN8zxebQ7Dw28PGsLWBR5Vha3IIwdRWgAVZ2N/KkFVYvBnqHxbgPi/A66zRneOO1Gqs/AmfQf7QgIfRdrL7+zRL/eygoW61+XlYSuq2jhaLElbBbzp8gBmMcMfDkz3V70G3o5NvIz173A5Q1A7CF4qbhwAR5pmFoEL8178EFxANxWA6sL66LYPkt73T5Sr5X6oarhAdnoiSMHXzV5h0Y8dFjedi6Jj7Q7qCeeV3ECqslAHZYbEcFss+LZazomxWgWEDBEf1RkB1WCsarqXo9LFJh5bb3zMd+JMZDE5oU6zTZBdDDwT+lQYdqKEE9U8DGphOKn1EtBgeJPddsCyew2rVrNYY0T2Yh2Kt1JsiFibEn/+EifaVwHEC/3Hiq+m96zgHFgi2Qc+rzpg9fjlq6mVRRkR4hTSHdUqlXPDFOZIMpdlYkSiAFiu3LDydD0AJJrn2r4Z1jdnaLB2b3tPQyr5vgEw9pyCDtgDiBEGfPhc+wfeo1gN+m5vCcpD+6rYoMuEmtyY80/i+60gydCTBwpFZca4zwNwK8PThjhJMcsKjho36kh/dkEyoyMq1vY0uSloyxhAnGP4gUtRtzey0MHhq3o/TqlcOA4c4z9UapA8+Doxud67e9DgOrOvNO2NFxRjNlJEaxnkcdm2LjXlZ18HtThZRQHzqsAw5VIfV6L0aR5Lkx0lyiqYs6441oe4asm0XQtd/pIQykdb7NEEW7SQkCX4PJDxRYdQr/dcwX+C7PyDLXkJxHdU1SJbY2Hl3ljpnZNvUfh9U+0/G2tay8gI+VZ8hXuqBRy+DyJRjPEhOdVtyigWfxZAlqROHfxkllUkceRJV7RgzkPlkCZOfRROU/rx90uaNgOqwaDSLZpZIyJ18I+N6ma7XGL67bhUL73l1A55PnyFFDV9UCFF/pTHaO1fb8SJILH1L7enjpYbmhQo57K1Ehm2cgbZ1hJcorCsGvT4hbyWiED6k5jdt1TzFRn7xfSTRfshnP5IEtJdc08ss/lDDWdh5yawrfIEN8/UleMEs8CmtcIinv9BSIDxILXVbPMlChRaZiS0NNuPHKl1nCh66FStv1pltHWGdWVeMus7iLbDDytIw8UuSQfaZZc/VgBFdwjItFuFRajXICL4qDNF1jaYdL8kYfolYztdI95nO87ROQbbNkozuopUUu2VrqEUXA2iUVyy0a3CGcv+fWizzJpuzPPcSyyQRcfov8hYRIYtXpAfBQYDfCAgP4lndVpzryH6XaiV4SFLq+fJ5DO3P1fRUuT2UwqifSan0EVbW7QZ8I2/UUIIg1YAGqnoZWQ19SkPvlFQsooMgUbd12ecIfRcmAgY87+dx0tq7GsuiqtCyyMK5jEd14LNt5TMHrSYkAP4Q+G7YKx8+w0eYBd8xWhZlPVxXmzqW7gyBx2vcYMyth0QRIwdEKW8RGJYjooL++a84hEBMDssSllO0NeFTHaYbfvD3pn2qKOxiB3nT6xOo9BkgfUjnd3VBdWVBe1Uh3hWFa9g9auORKzK/Tx+asrVYOzwHka9ua5540j1MtUjFVvi8SAKe31AYDhzrzbG7miiXpFHw4Rxcr2r/ObCNZu2TG1iRLrOYPPxJ1izgeX6doibB4G/zjwC7BFWFb46hgjpKoEGPcHRa3KS19jW8OK1MEsT/Bdagu8GnU/fjNfYBUqHiE7Q3zVxFvndwBXMkko+qH+miPWQJwtZi+UeRj0+HtqkfJVh3+r/Qd5v6yNR+YuYtbOTHBd7gIsiXMi1KIpWDzpPxwG6ALiuqz1yo9+RmRe3I7WWb4ToIZldkTGPXersiz6uzDDxUQ4v+lzn0LUXb1RFN0ORmL4iG9KywU/AFru5g2cvVrLh7/Ypo67+9/kWYLgYW35YhXgBkEMOh0jt9pfbVgw5WcrumnRD641FVFUlKCNPP0/O6KHG8+H2KEzonS7hoMNtUP7mhneaLV62pqq6ysWrbmcPWMAONuA/ROl0j6iIW+u31/yUM0KqZ4Ux40wzTc64VkYwdqfHxOJIfFZr1NK9FNS3Nk3QNMusOcRgsVT08WUNbfMkHuMYyOq+tiW/Tia4e5mVUU96loWVOLzXR7te3FJfZMB9qKCvaM/ueTfQ8IVZQM94G1p33JO1IOE/L4O/46fj1a96GwrzCcTU4gPYEVAlYiOIcLeBFNK5VD2UynlXPmk0XuiqzserfG9jABbfX3J7pmFVdRcauUmgXntU0J+HaD8VTnhU4uRfbwi9v3szPs+ahTMC15umz6cTZljDtLX372YZlmQoGhvVkU7YJmWil+7x1ktU0kml5VDpfNl3gr8XPzanUYqNZzE3s0YSZTtjSrUrYWbNitoqb9SOaTfRK5tSmL1S12di7i+u4TMnTrrfdv0qeloPLGJmFdOFgRRsStlUgn59l9UOYgE/182Sl1rZVtoYxu6gxW6bhn1wegzG5Z5rFNrafMdkhzMCY7DxZ2lv96+qzMGZ/mGuUlTygjBk3Z8v2bCjgtZeMxIaK4nJSdWICFlLRdRek2oe0atXgozWaFHxc0o0mhZWSkXSVZEzVw7swlbYNCYNZMm4QabqV7kKYvsp4ZBlasBf7EWjS/+iyj2Ct04IssloulIm6X9n2T+YSEuHMk+gl1qxIN4GMsyKRTT/6+nPrcbeIb9JHWL6QAEyDhsUAa3Q4D+WNRS3jNbqfo22fut5Mp4VJ6WzFVVS9uTlrUAeGLcnAAkIFDYf56GfKZhy20a2xFJSjmI5LlfNl0wU6MdZWcKmlIcvDj8yjO2zLqsYwA4PuoDWLc7veA2IUlUaLVgYsY0wazoUrpfjnsG51HZmAr3R03gUrt2NqZhgMTxjEkrKiRgr68pyxSQn/6draGrloHNF0AtI4nzZdoettFWdb7uiyOhPx8w7v7rpxzMTAO7jLkyS2af7wBdbLYnF7BRG2FF84OMG+Aa3z0VxVxsRsLRf+tWhPZkeRwojeyOsaKXVo2h4Ro8DbHr2y0zJoKV1oQCeySFuwp8T8C1o7ggmWsnaOrEzJtspsi7i7km5kRg5OxobD7XZ7BuSx2rNePLVc0YcJmEdB011iG1b0mKaZXS1xWYjFLQuZ10nJ+Owk7c+ETCWltU37TMX5DsT75ACbZ4RUHCCCSg/FhxQFDofiImYJY9kxrc/Yj9MMX1roGzB2k4WPTgUOvT0pxNXlQY3u5tsm14Spv3wFHT2Ga3XuZBGakW1iCrLPr0CZRjGBxDLNl9V+SGW8mUVetakD20uHyLwgPd9k5lPxkLaWjF9lFVyYVt+ghHM3yQW3jXOthjIB+1rNoU0/6ASes/DwcVY8XBZVbeZbAVLGqz2QC3+KiHeKJ5Xdn4APlXOyM7zXPVp+OzCOjkt4YBUHDi+huzGhgF7ChyoG3w4+VI1gIlZUzY9N832duT3yV/AxhU+fYLa+b7Ic67xMgck5b6iu8dMLNT2c9abW1X77bnRbx9VuA5vOkW85zw4+/bbibOyPn788r+HKrAgIkDKm7oFcmFhEvFOKgLL7E3Clck52QhHAve/3joFxdFzCA6s40EMRkKKX8KGKwbeDD1UjmIgVVfNj03xfZz5FoMgysxRkoKTbepE53RpnEe6U5JN2fYq9WDYHOyHxcM+P8uoJRwEUQkJFlikoOBWftSCu3EYjlmmGEgbeDl6TdHwibpPMhZWKh+Bn5bXvRU1cVUlRLm4pjtGxh6KOigdpcFdOVDWl4Eo5u28HbxqGMhGfGubOlmfbWvNxLixXaYUz2XUj6V3+eKDqszF9NSn/cjWc+FffmuZkCAOMcIJoRyQ5ePTzw5mIcfqMs/+C7Kipl5gX20yJ3ARpcrBZ1JYnZFNXdEvOZtOBnTp0dBrSBFLSaY5t+jP7ceRZUTYr4nY1MrgIKuPmAcqFdSWonfg0SkCOuhMTcJaauLvDRiRdvCUfsbBKRuoS2DtyEod8JlaS92IqXpITeHeY6Zb8v31aUjvZFKCSjZw5iEYqYR+qb1u3Z6r6PxXjSebDpulNrW0QYi3XWAiZdshjiK8Ws4r5tpTvJF2fVuAx82HNd1ugflH8YtaSqAHHV8Eo5Cruk/L1lrCgYgyT6nDi/Ng0TyrMxorte+iWkcQyYBkrdk8b2bOhFLF9AHEk/U3XiwkYSUddm+bZmjNzlNEeYMEictFu+jzkfZ+M63bSYujzTH3DT598SlFvypcheZUxU5m0li5TGV3BJ5+bvEFN6rHt41KroUzAtFZzaNOP2VOVSUfSSj4nfmqX8VTc27YmYV2pzN5SvmUGMRfTMvNm0wlSYd7NnYTp6HmUg1Nu765xfzze3WFBRc+n2uHFudglZjOlOxEgR2C4XUxoouz9pGy3g8lLPqb39QnAARGoy0tQwcWPtF5uOEjFLoZ6Mrbsq7hwpakZlViUcX+8u+2WvZqA9yynwYoTpRhmZ0xGiRhYyMQv0lo6pvTVGvUNSthTtQrml6FWQ5mQp7VzaNOPvs528fA3eom5MTJTdTJuZlvdHUXUfjBzMbV0Pm06w1ScV229KGpoYyNt4JQqKwZxVlkpvLvDmoqeT6WsinOx/TbSFXxCy+eyQAiqfv0Yfe+6SjI2lMC7MKS2uZ3y0tuMZAJutZm/nfDgywZipwgYa1qLPan949KQ34IJSnqEE1aSW3N6IrFgqpyZrtceOay7s73IOz7BepXPw/ZvLn2/W5u5ZxUTXzDQOqZz9cXJG5Dl8lNw9fawoHQIE3KidI5s2h8QzBte0GfstYtY4aAjBhvwmEOS3gVpzaqeTKU7q2hsy1PrLYhcuYJ1U+b41StoczNCDi5XByhIN81Z2sSO6cy6MUyiLevmaSf05E2nLeWeqkL0S3sxBaCTZlKQZk9A2e5lx02+yKD2bEZTR66lsOBumoq6KfX5ITWG0TYLi55NooYYp8KmF5taM6rI3EiM24ayxuhMuJv7h3EYc/DrTu4iwihMYRWqCqNz6i7GWpgGMQeb7mDkBZsbz8ShUmhj5kTvdIk7yZjaEUzAldo52iGWvF7DJL3vbv8f1XWZ3jU1tGVRfW0Ny8orerCwoQc7yNJ2I5qOxe3meBdYXj6Sr2v8562CI5W7uzsuqYKhQeOkbHh0R+ZjtlqW8y+VgOFOobH484aVZ1GKYUs3Ei2D+8l2PXln23K03bJea/KlP/+KCx/77DuWDd/4r78Wz9yrsN+Wb8CDJj+cCKtZNx4rhMGsVsFQcbyMZx3O76BMQV4P03JSrO7SnADeohWTPsLy5SZdafLauuHREE6DwoOm1h2S3b2kBz6aF9azo9PJBdc5tekZXW/uxa8bn6VBZ4FiKzh+h+07h2Ftx9LYQUvPYlR/b1AJ4oxveRq0Kmg8W7E0mA5J1gcz8Dk3A1lHt4PjZXNq0zO63rbxvu8O4CH2IzD0Pgn47ZHqeyTK+6GYPXW2COZhcK07ThjjrrH8HN431/l2WARb42zjB/cdZM0wQoFbHVlTi8xhlRA8EZaKvj/qZWNatVu7eqwGPN9KsuIPh1XFo5h7cTmZBrb6v48Xbas0+ZnV9b3QyW+vwWqdwQ/FU54VQJ3ZQA4embs45DK3Yd/P0XlL3pcJuUtOaavTAKbmbBx2k8ISjTqBzIsjSg6Tg8s4bIB04TEFesfnTwifzb1H64cyAYvqp8qmA3S9LWBQkyNCgByBLXfRvaDs/aRMuLsuBCocv/1p6RFT1dPsx+pLDxY7s7K9XWJWy7FMt8WbZtGmJ0zFLWJnd0aejoV9mDembqnuyiy8tw/ic7DUz9KsxtndzdfUHHBY8SRXPYhFNV3xuda2hbLXPMI51oKZBexl8rzX5eqixIpRugLly+lzsgT5A7wCNTxpStRE8qKOIjbVlMYM40pOwcHGVqSGWdf3caSzdZ8m4EzrWbCT1ko028Gg5A83zmSqxGdJFv3MvCjtzNRMKCW4A/cx9Wdju2OQ/DzPUV+Sn9fNXZWUaRtra7xNbKooY0FFHRemNDa7U1eMbUczAW/bzqeLM2vruNpkhBnqTczTu+hTsBzLjAy9g9bdUZJJ3m1XcrEcXMa8A6QLuyrQOx4czM+q+nFMwKH6edqZU4Oj+/s0Q1/grSk5kAAp5ckeyIknBcyTP+Kn7MIUnKQirE3bc2c/I69Xuimf6irKB0p9N2dNUzM99D23pmgm/k7oiGi8j2jFfEGsDx7g7VlZrIx8p6kjd4nS4G4OUHVD07OdRW8mcU4aiW/TC7retjDfTeHKepsaozIe1czsbCf2ZXqmE8lu04dNrRlNiDp9RJPzuXi4pX7jydTYEco6cmNiAHEzJ9StyJQ4rvNbaFYYxzOJbWGcOyvdkK26FexrFJUy4JEYdjfdjroRTMybO6k6nixh8rNo+HB74bM6S64lAmnWXGldpxy6tq3LmFoY49Zxt+vwJuB41/m2WgV85RnTlydNiUjxcAleSFr/8zzFeE0PqWhqyZOZsxXc8pnrGrN/SSSKXmvVmQm40moGrPpB1dsaLuzfzBHYxpZHVAhseNPrsSrL5iXcaloa8wtl19HNwP6m+bbpEl93ttXwuUhAlv4LLvo+3n4G+UODvQEqBlRXkXG8AO3C65qmJNw99HzruNo8jgn42DxvNp3o68zIsRamncmkczTlfE24rbj1NJP1tiNW2ym5UYrq1KgGLPsouxUo6693/4RJjYvgM5rvhHQM5HlREyx/+1bBk6zEbFD99rouG1FkYtTXsO6Qnj6vi7JGI8gKIvSr169aEIqTeBgJU8qQIjLepxlUY+wAjOiukyVcNBm8AdVPGTa63IiMvq8rQ8ZeejYigw1cnK5AmiFTBiRLFQmlgEb0/a1PGcbN5Vn7Php6Zp5X0v8kKRoFo1DlRmSdlvEF1stCOkIGwIzOEP4tbcEY9u/aqFUrtmg1yMysk1bKaerLjEhOQA0fijKVruGu8MU8mjSpm1KKoyuyJYgOEwNhTR78wFVaQtWylYCZUVMZG6U4mWSZdgPXTgQLYzEfLfwXkDf3gJBLtzhoMCNqE04nZP2mK2W+QbkxIOkCRKSEG2JqTB3BLKBaBaTMTPT+yS+1uGMhzAjRho+vCj4i1UfaMwbAmthEs5AvBg7EiPI4Kx4ui0qKqy8zIulVexmSjW1lQoKtDHhdY+P2ClZE75MilMAZkV/Ap0ol+vsy85ossuwor54US3EotUKkQmHNBFhZ1nFAGxNp6gksV2lVIaW0DRSU9oqDsdAs0bacg+yoqZdYs26TZKsb0MEbGyPBQioOHgrt0NwU6zRR4iGlNoiACoNdLz6WRbNW9oKUGhERT6kMR+d2ttx5v+GYhNZtJZesEkCzmP2Y3qONsFwYsUsA7bHrENrRTyUwhkI7NBdFLR3dUGhEIz66LqeX9HF2iz1PpVltXpu2RKKiGPsutnG01Kum8mEyj84a+8Y+HSbvH//Em516Jn8FQKOoqd6fMI1B89qAdDzaZyW8GrNvxpZ45HkCJaXIuwB2iDRZfDUNaJMx+zXs0JptEza4nZFKk/k5dL4LCDA0NqSLkWGmUvcYdehCbk12902N6hK5AKkwDIY7pTad+AKIkFD2pSs3E2aJjFRC0zu5CscAmFV4+T0kqUavukvmYDLewNU6A3KGlMHZOjBedIh5GGsZo8HJgZgVC5hDxLHaFcnDmE3f/oqO1Pbd3J4yoPlWatAMhebe9Nc8pL3Z3JsxsQsEFWKBHzB9WEp3cAbAFt2HFKkQlYKzeRg7Vdu0XiQXO4x8R4fzytmODZ02MsgQyyZnESrE0B4VOXvQo2uDX41rd7UG6YOUeH2ZxfwSUmgFCwtiZe9/hjWyK01TLIc0D5wPjpJSQIxlc0Wr3JpVUXpG5VoWk6AyJxQxJ45t2OC2cw9Rh9DyLkuCBoydxQy10HjzWQjzFJZFVaGKmQYlD2Mxeunqt1n16Fet0lraIrPCAp61zu1NuZneSh+L3L1CHb4qji5v2ZNFqoLsHJOF5k+JNwf36kpDVMAwMvEsVTh+tkLcxwdQiLlTVf6cn6WONeU2p8S3w/Guim4SWNPgxCpqmjGH2kaySTBLiKafDQ+qSQ+Gb88UdNNAq8enriSjnepIW0NATQMSElKH2jGpd8sePmtpx8JaDYypYqCbNbVYpDJ2Yw/cR+I2JgTAlunoSq6sQZNgTBak25FQVzdlHsRlj6Bvh6NokaIKSPXw5BVktBNOyjVEU2CVUEqFLgKVei3FTCVZ/hvteLjEN5GoxCW2EbHGoFKv8+i4SIBRj4EHldGEVsM05BBQjcsu/SnQ7dF6naVwcVPQ/RSJooVXj0pXTUYsKrxGQystVgnd1FMQhXIb/rSh2wDtMr6+UkyaDTjHXXJDw/LYIA3JpBUsRiirF4FwUrQyRUw6zlgCnn0NWS3eNa8my8Sw/HFkC44wI5ORiI3likWbQYZuTFslfURY47CEKho6WUp9JeaxpRjfsFlfEEDtR2XWGDzJNY3SQB+R6BQHKZx6MDJwGYm44D8NeaQYJ1E7mZbZaEQlQ6nrGBlAWVXDYvZkNDYiIakWeyS6mteoFNxtoOa1GkbIaZYsG/R5ewWrukRMDxdUzKlIRYta6sGaK8toKsSvashp0YJs29iMOJysdOzr7RCjK6GkFFAzNBm8lF5ceK6OXFKcI1Ooz7WnoQ0Poh4BBymjBxWHrSEFj2giInCh0mpSsIDmcbBTG0wWFp3Mp6/lOh8/RH9ZjuqoxBMhQmkcCAKw1BuxCf7XOSNEXBKqKIkcQpDjNMuQjBvuEGiowoFaDIetEYE+HMKJiNTdFNhcjdVQSYA1j4qvoqPT5l6DBbkExDI5pJoAD3rJ7h1s7rxLiKavoB6gtp6MfIqbExoa6puQEJK64RFMyP5aiZZ4IpB6NAKsjEjURRcNYURUExDjpFiRk5DNfRs5PQQ4/Th48OCVJ0UqoY+S1P6myxV8TOHTJ5it75ssx9KRKdBYMaaaRoPDgCCYqpbNaKSbqhkPcvd3obRrUwRSj06AldGLup2lIZSIauS1iRvs+X1zSUxODwFOPw4ePJiLpEgl9FGS2mdtFlmmZRQWQLMEaDips6C9MKdbRQyKkRljc9Xvtr2yJx86DaLvOQWpGv5w8dBABBqVzC0io6QnCb4XNdEfyAsD9PVHOTlU4PrxKGqFi10NbgXdFFPgQz3uSuUtd6lTQkB9Dc04tRWl3CbeCdXRUY9fs211Y41n7RmJqIC0MM2MRHM09SYnku6Ordb0s6uoHrJVfXl8oPYWsTZW0KbJkc3F4eqxlroSKPW4RGAZ3egL0RoiSZBNQRES5WsmCQdmGAYLrSRKH3psogqHbgqy3NJ3uxU0oWEMI6BAFdQAZjLQSCQ0YO6qx2SO7qq8ljNaGKt5bMcShydaXHJiCBQNEhp0xgGd2KDgbNY6NZYYooNCp+IQOYE9aNMmjTV7dKVw6sHIwGW06TMqaOgiRTWyF7dtUydPOQhT93VS1JoGU8lOWfqJ203mLHU4mLyCeljaerpwMD7HhUVsmLwNTWzYaJTskoVYklGSjds8PjYZ9wgEZBNwG5nZdwkSf4uSXjyIYf1sIJWr0Owu4zFNQwJNWIkIZNN5TQiJMyGmCRfpk8zcXqIeL0EFFz/SekkljRFJY6qiHpyhpoxsVIIcDdVMiFXsFMu5KMv9c7vJ36OmobyCeaDSejr6OUgmfRsSUirnKBYlv9ETak1OtpbjeJnKYxKWbWh0uYdTOBlEPwVikFMbSKXE6/JJmSQehWlMEkhyTmnVUS28eki6ajJKKVJmaYimbWFkPVbWtnGFmiu5DdZz2Tji9Z0rz7hNciCmJCEHoR4XC6iKvTRfgeTwjLkw6Zxrt5tMbmoisIDmMTDwOpKYVTQ5SlnsnIrK3qZzHwdr9CjwgCYbmIMPsqd5XCM7FZgEewZpLoXUCQhZBblUYNMAaqWOFOnIQnvTeTMLKWEtzoosGMnx9GliduKzK97SWdZl8kgNrhMhylpy2SRkhdTKJzVytTHJZJyPT0XdulQDOwxStzoD6TfVGhUa1vgnlLAO49J4KwIpNo3vgo05M98R4gA1YQIy+Lixc9OSSJ771IJkhorGwerra65aKfO6mglsaHPk61eaZLa3qhS0kuXtgUazPt2xSYWCPrGvTkB4dECmUNsxRfR1os9Q7Lp4tNh8uVtP4QmXmbYj1nOqYKqA5AF9AlWSx1k5ZQyYkQQ0dHDmABqXWka1/Y9FFU1aadtsC9YojBSwxaShtD65tnkSrLswXWYHXZfM+7dN7SCqmHfyMedkUiVK1xH2jTev6WBQBFGFxjTTxDBdkCZKY97MG2uKPFaI/7LwWAtedJ6X6/vWteqrdV3vwWsV1TGorVVMeZgR6U8yJ98qCh3mQo/HmUxadA5z1KeQdp8ofQ/Uk2bkkgCl03JbcJT9tgLeTu+cSVTfXoPVOoObR0fV1OEgzUNiKwRTiEM3cgba4bER5t6FjD4KSPWA5BVk9KGfQ9FQSIFwgisim5Y1W7wIZDMUzTbuTJZJt2rKz9/+NGs/yirGVaGqqVltmiMGpxamJqYTGX0IOCbpZiIa9+6x4RTNpbrD4NVYrEgtvvfsQnlN45OdwuGHnhAHpCtQvpw+J0uQP8ArgEy14SkpievZWEnj5zXVlSe2KkwZk814pVvO5jGtuKQkf1jTkIW2HCRTKQbVWIRjk0vxdpf2GNhYRz1SU1VpjhXlS2S6rCKmhkY+Ila1r9miTFXcB6vZrqISdZqta3i3zKhvKyDVA5NXkGZto15W06VtkyOcQN8e3lO71QQOiUCaofCwUrJQT7zpyCLgGjlISHhqzXxJ2FWkqSspL0ParzgN8pEFGPug3O1ZWax0pNOB63QxZS254sc9g6dV89SopyXdTeFAOArYemybOpGJRiEemWTUC4G3wmuBMgGvBtcJZWUtuain30jUCns1Xpl8Ex5MjEo+HbNJ4ewGpmMxL0pNFlArfdLwVvKKoiTA1rauesi2KKRRa6p3G3XhuLbtycgueX4yQtg3+ybj7Xme1inINDdQdBXUI9fWk8eCCy9KasPBdejHvQwlfTHzVnzt0kxMZV37gatQ2JDY9tKUZYsSqhsn1TdJK/3+pzlDqxxaPWR1JWVuVv7ZUg1BNdhHTmpn2pEsdyLTDmTcecbYcX592yLAQcUgzWE5lP36Fj+xuQLdh1/fIpAErusGZF+KBcyqvqB//H1Ts/vy6noNEizO/8f161fPqyyvfnu9rOv1396+rQjq6s0qTcqiKu7rN0mxegsWxdv3v/zy72/fvXu7anG8TRi77leut0NLaG2h2eZKcbr1BTxLy6r+AGpwBypE8ZPFSgC7XoGy/nr3T5jUJLr6mZMlvw5U7huUPxoqs0FxRawj9TXx787Nh1slrrI3uHvCs6I8sg1lz9BgMQQZN6RYwIwCIblGCwmU/TLqhnK+QKQpsmaVb/7meVFd+7J9UpRHQn22x4X/z6Jpv9hjOM3BXQa5vgwf7fGcV5fNXYYTDTGUGb7aY9rMCJ5+nkxiqT3mbxXO6MZTbPPVYQ5BVT0VJT+Fw1eHPpUZ1x38wb4+fnv1+qWq4eoS1EsWFV/mwBXkec2NY4ZhDq7MEet1Q2SHBOdQ4trP9tVUnlP4MhHrr285GcFLpLeCSOL2Cl7WOUjCy/5l4XAxeCl/UtlaBirrjyMAw4XWWZHh3AQCHvq7k0B+TFG9dqmIWGXl0wvY7hFvNJv4GW9+BsRSJxFSw5I84c5Jj/6zEy3xGk7x/QuOhsN395lBisZ9+oA5Xj4zdLk99q/39xXkBFH/zR7L53SVcki6T/Y4jkGdLK+Rws7ioT470AwxKl7lHKWGr/aYTgocXlX3jy3LdwMlkEM7JQQ1/Ee6PiqTZfrI9VwsdcCcQZDjZJgMwv7j1mwI3eKFeOmG7Qc0Jo/tQF99W3eDoywFHFd2nxykK0yKfMFhGT7a42k92DSS9ssM+0VdrL/mp2VZlNyo6AKHeUKW3lWTf6s5FZ/+7iA1QYX4FjGvgI8tccOI7H8pvv67Y/+aJEHCTN5Dqsyxj+KMUJ9dDK5P6WIBuX1289Vpl33AUhvtEQlxK3JbLFvojrc7XpLjHQq3RhqzcdUh0lgXPW4hjfXVt1Uaf4DD4TVvuVMFDvjSap2Bl/axKQ4jW+SMszuSkKDsSlxW4wd4D5qs5pfj8HmL+Bs2vbpW1yBZhnvjpCi9ON4KzzisTzXOI+KKnDZh7GvFHCouCKHQxf/E+3pcPTxnEhfkmbP/UTTJXI2wcIHzJV1BUe3afN2ahbe5jBKy1lTXbSyWl7rqOCuqb+9jk/JeXKbEyYPbVxUdp1yZez8FlIH4jtMc4Ad3ZSj7MhdznOSyEVmdKXCTALnoQhu+Opgsz6j9SpBv1GeXjfQCv8THbqLkkwuOmxKglgVllilw4Lr1AtRw8VU0ftiSrZE01H4VbWMP286nO1xLixK1zxsb/VeH5VEWK25pkC9uGMTtbfPVwatQcD6Fwq222Iv+mz2WK7jOXviODB+d8YhdYgocxOIJJw1PnDyvCbeiyQcHBU92muVxkHVcLDiebb+4elFlQootcfGJoU2lDd5hvWLDZzdckq5Rn6c69NP0cPEF5A3Ishehk1TJ1gh5eqiBp4gUJp9DRG31ceR8u6mIU+/hBRAlEVNgj+8TeXWZRtR+cdi9ipJ3v5Evux3i0Dqvryt+vjaf3QyA1qOCpNoCcWMKskoYrAxka9ZtF6j4BdbLItAWZFB5rFxD/ZFUNLpR5Zm7CsjFKkmyZgEX9I2rc/6IRwnk0Q6Wf+WLugmq3B17n225pYiyEQmYi4VJekhwYbuti+/jp1sD5uK1ydNVsyKO1qNV+9IW678Ryx2wg2c9dkm5w4EjqXEFq7pMSUyDglA6OAczoskypVNdKNweMWe8tR8k+QzYfYShM8px5OOmAzwetsSBX/uBCAxKF3jgI3dNlEiHUqco0hJewwxZT/wROFe0vXwembFjcPKEoc24YUloc//ZdRkoMAqFLsc87akqf8LTf3WyHxJxa9l8dcfUHbILh+Wy8imPWK6XJFJesQeJpa6xh/QFNDEEUZcnUI35U7qAN8tmdZcLliJXZI/ze1qld/wp9PDRYZ3goPhqyYs46vOcZ9nxPUk750C/7NNYRBDl/gJ8YrEtu27BFblYeSUaGXnZm1xDk+4LChjnBXmeL9LHdCHx3UkB9kR0BtoFSu1usUqRpbJaCcdnbIk7f8EVkkeiFiopdqLv09f8E7LdL4Vtky9zceW1ddTCVQ7hYKHCGvwHfHlqU8swtilT4oZRyQ9CoRvem7Tmtz3qs0uoblY8Db4W8uCKELkrg3BoYb0ui0e4uAI1krDXDXeGJyl2kAZFbUAvh3Dv/U1Rg0xOHymE1xjUjSiBnA/GboqjhNO+2BLHixa4Q32GNeHKBV3o0NOfDddB/MHF15Q39yCpmxKWaEerL5BiyUsLFYx9Kx8RN7E42y8utvXm4WPWsN58d+hPV0emNPBlLofDvzdpCb/WS5yotU8lxp4SyyCcW9ioG3L8TLnD+m2Q3EILP8GKxtFiwWHj17IR2ikAdcjYzEWgDt/dI5iE27fUdwf1P8/a5UmlY2aMAEm5k6+3XU5f7xX45RDu1Dh9XqclidX8AF4qOWV4GPdWSNIcgkF6/1kJ5aDdgIpP8s2oNmKxgzRlagrXFIVSp17jc8yjhxJCUTcVS90OTYeKNzijgXB6yhW7rMshQwm/MKkCF/nVVTp5STL4GeYPfCSwHMK3hUtYpoUwjyoYj1aIgkHQCJJYBuFCeXwYJr1OxRW54DxDfNAfsvFI2TIXi2uR4pUMsr72yRKnLOGtLxWUU2QkeD59RpaWGBm5KXDBd1oJtCWfHMLTwPMJMvseCnw2ykWpsUVOOhmyzc7zR7RiUeX+NFRQyuRATvH9yU+5R5krcnbokfpHjyDNwF2aCejVUH4tyQchh3A65dVgF0sdrIHiqR17l2KOn1xZuZOVlN6/EH/HWVH2/TuGyDYVLCU1oMNhB0h+FtiFgNMFCYcdfKGjva3O/SsY3vZpgtVtEldId3wvn3c5hGsL7RG+rgUewpFucNFVFUIgJcVOqw7vO8fNy3FT18ItOaHUGfOPtFpmaVVr0PMgDpRpZU0GEbtfIjtM9IzJIRyOH5A1RKqmCX/RgSlxCgrnUTnj+JotJGg2X529oSdCtCBT4LAH4Yc6QSbpHVvih5Fcxf6ANmH8fKIYv6uF9GvxNF9YtcfDOXBY5148zXEciozRZADOsU0MFihjHy2gc7STRXs6QCdLjH5whzPDdG/xqHGi/RpW0jcLJeMwAjuNpT9dr47W6yzlbQUpgD3+HzB9WHILvf/mQB2Jnedu2f1IFzyS7pMDvSTj+eQ8nmGP0IsXDZhHWzrBogSa8yA+apgAJFl6uI71H3czOEAz1uHFXzF/IFvmHjiE0zGIF8TZMhfNv4LddiZxVIilHpi/QFA1JWzfOpRiZyA8WpCF2AqFHnjxDy1uGsA1tIwcXorePqHQOWQNljjmTu4kUoB4toFpoG9igPAeRdG6mgB/M0UDtjVhPMPGHXbjvo/ocw/kUVcdJ5IH/z8sXqXvsfTsgStz8rCrYhu5om0Jmuxry8QPX+biL+z0GNGyYUrsMXb6ioCP/u58OlqdFM0aX1dYCHd0xXIH3UGB1Q9bPwvk6J8o/yp+ZSEcIw7aMzakLcgiDujCrZF6ncs8Dcwt23vePcSeuuq2ij0cLydi2Xx1UT7j5r86LmpkrCuxSopd3JzhmRP7uVZF98nKD2FyelxtYKzqVEwsdcCckiAkAeXms0svH6CYtHbz1TMssLop2ms+cvwmWPf+f5UdhwiFju7mK/wSKo+TLpg9xHVkx9fuBABuo5tlv291qKlH7u1/T+HTF0FLFAq3RuHqpGfgbZEWic9tEVXNcZStrjlZcjOuyOXExDWHn1LswEKe2IwpcIlwOeQjE8v9rlPFWScMLv/LVVOvmohXY2Mqcf4bzcw+vc4VEZ68VoIwwNOnxTIOa/VNq9wfftiogVw1/TtCoolsAe4UBiD3uDEFDrZzmuEnUYbMK5zpLJS66ORdCKYctaTYxQnXJgzB2iybl4Z3xqnhfFq7VF6JkEF4tcCmPlK1wkH5tNRPgNS204D5zRIRBrrpGQAcHLvPdQnE/MrU5+0RydSZaqAspjD5CGFt9W31PaK6RfkJPpN8KbxnnCly3ta3JGv+vGpnJH84i83HLW7EsP2qp8on6eeNPK/OIMA6a7+zCDcq+OL951b6qmgUhqUR+jOtHsv2My7df8lFEKbswMBeDByPcwNZdg5eDVcDYh8c0lRQHc6pYA4HdHpch2O03TtGOxwf7cvx0VxxNd1q6d5biaKctZlV/NUyVf3tV8gkothZDLejF2N46e8O2EjGHg5T983lgkS2vm+y/4IVubfO35TgCp3xXhRqtENZ/CVj4O7zCv/+ev/fhCAyds38d59gMhbFVLzeNytYutR3B0Nh3T2NzHVp89nF6OiTM/HWxub7H1OwO8rg46x46NPZufOltvZIPNk2Jwo8psDhfAQN4bKoxPS61PfZZ4nZ6TpJl+MlE7hpalGNun+27St20U2hPd4foOpGw6Kkv88+jxfwqfJfbdra4642UVFhS+Zbv5goskTx9PfZ5/2yyLLvBVajrmCCn47xWLdGFCOtV9TuUV49iVoAW7J1Jkpg5H+HJUBZm1pLE58YZUucripFekEpxhtV8V5h6uucFeUKSFIx86XumK8B//w1W+LgyIqegjh24quj+/s0SyUeVabAAR9OU8S76Lpv07uPzqs2fqd/1I2jGFfoEDWjCCLyixb6DJCGKreruKI5rSHclc/FQ5pLr+mJpW6Y+/RWSuQCwNbsVd3sBJ7K93PsvlUpa46zU52lZVWLrE99dpt5EdXm67R7FBLHa5C/CBpt+9EFjzSWzi8+r0b8jr49pnkiyGih0KGPQqqxE9fUYi3bveP3tf6rM6b3UkzvXTD9I11jcx9k4t1crshBK1gWOZTlwmYKHNZPn8SXWz+bz1NI+bn0erIGgm/0tivJR61X1dzWw3RyvFl1Eb6Sk89NkStOeV4PvszhHOSp+AzrGhmSVSEuP7HUAfOyhFCHW1LuMEcNMq7SRIqZL/O4NfgdyB/V7kpmOp6Nfqwaeig9kzxinxkNk0osLg/hZEKwrTIqdsBPfO48f8gRqds00/zJFVvkEtc+zbO8YzxmO9dqo/XVwMVGo/JZa/r6Yzni4xkGEVSLu7sSPqZAXLdsya5tUTMxd3/WGMbXPRbPc1x51XG4+TPIHxqceJ7DQn+fNozmuFhwRm37xTXysHXLSh/0GooccHbBDF3dE9ETKYdwkAWbZ67UjSiBHOYIPHBEab84uTO2Om/YHzMKeKyI0p1zT/SiK0yG91g8ZLi66rYq/32PT5qMpCGQbgVDocPxZZ7+3sBrKLHKuSIH11sGHs5XqD84gYY4dEmxQ9BlzbmfyYf51+AfWidr74Ze1yWJuKiKpgy1O2QYfda5FZpd0Nv63ovLiS1xxyi5RswVuRwEn5XF6jJrHvjXJ9kSp4P4okmEdUV93ppV0EdRhXF+j8Uz5k1edRc4PIZlQt7H5hTe9pPD7tVk3BbTfplnL9h2Tf5gyena2T6N/49pkc11RX+I+gy8/DXg8QyGVVUeZ1/ALcoCYKe2mfpHfL8XNb/2uKI91s0x4cO5z5PvdlMTCee8NkCxk5qy2MWhaCatYpQUwWTD/9jAqkbaOV5ZEn2ALZ7zsHLbNasdPeqnj2DjXCvAmAKuFsirb6ufT/1etO9r0YpAdq8wdlSpW25i9l2+bOpg9j7OnMy3NAK9LRk7/HyufR6Wq7TCIQntnafAPZ/D5rP/G1Fs6xqMe+1gSN/G2mnD163hoNPnGl9ayo6aeokaTRMSmBCDm3SYPTjLDd04XBb3un+MsPqeKucLTJP7lNeTZOXu2DsRb2pEAmbf1lc8sTfFT8g5DejvjtiOkgRWlQonU+qgkeO4JqR1quSFrHxrVvtZUTar8ECaAY3HOtbUHWfR3hTrNOFRDB/nWvzi9W7Xe93nl/JsIZdbcaNt5xI3Eb4kbBFhbRA8votDUXmc1UFa5FEMH2dbHZgEstclmQLnGwPSywKOzsVWevJ+xe6rPSb8Qg2Hpvvk4gCrallmFvq7OzbVTMrK3bGTrNtSvG3JQWa5ySwQLqx85dSkIupjWTRrqZwaSnb5WsbFsPfwkqX/PIeQwktTqj4xBQeBZckzh3ybI6ltRAREUNsIHl9xqKi8rf6u7b9Ytk3cvWfnNWQuwhZMO53ua0VRb5xlQhqTZQRgChzxiWmkqM8uR5GS94A83gCKa3l1V947H4KQ81EodTl+bo+xFKglxY7zgq8dNgJersi9v3K0YqmDC7F9u0qOWCh0xttecVb6J1VA7hx30pQlzJMX8RaBHMKlhbbeFeDjCdgS9z6zB5aqNSNCOVj2oJZJHOqzK183dzVO2XyeJxnqmIy9eQjPFvAFZ30LA4R7CyTt9PDcoG4scsjAFrVjk0O6ttiJBM3YeAjPFjRj4SE8W0B1xbUnh/CUT0jOp1jHBNkZhFKSWYDHaFtKTAvwGG1LyWwB7hS8gasIV0v7r478Iec6H07rV5sE3abItXdi1nv6u8s1mfsmX8CF7JlRvswF6xMoF5dFmtfVD1hCNLd8YJ8CxGGfW8LkZ9HUR3VdpndNDZV2nh4yoMXqP1eZoakWxH33VoV8ysodQpEi5+kcNO+1QiNfOwdYYrsACV1k+CJxcIJYRKZi6CFdglfKhRjqtfnqhklUQTdfHTFJxuw3wi+g+gkXemqqYNz6fPL4+F7scfvVDdPp8zotSdDLlyKvlyJSAcAX/39BIKEyX+7HwR/SEib1B3iX8rd7VEAuLqihWpd39lORSdxRKqiQlmQcpIbyaukY5D9FU0sK4I1fXKxSAD/85ydq1LjMC2ufdVCFeSj3wn5+B3j3KF/ovi8QnUSauVoO4bDSGqQzlum/yDK9KUFegQT/FDY2DVx4ayKT6iHDW7yCVcPnFTfBukjHNc6woKGnHCKkBdmI1FBOkRCDlqcZkAbM5fJKmSxBBZVeWCmAi12Vyu9eMAXuHr8uR6DU39eXuWPFBhdOeNzU7R6t9dJZV3I58ImT930XjmVoM+oKrkCaCzcrFCD2bXwC1QV86oz1iwJHvCaSfG8asK05DuoN7m8Vsp0+pVUd/hC6BKVPTmE7NOMcHg0eSH7NUN8dHRTSwwnHrWLX8j59TO+J/RSRuSQofZjLDs04zNW3zWOhvztI0AoufqT1UspkQqEbXkmqGOrzH4Bx4/BqAH9OdoW/U8cobpG8G6aGcud+2RkcX+ZgHUi8te5e2vOq7wG5jggEnUkK4D52ZJiuZbaSrNxF80nSdYpVDUGn5Io8cEpuLPFlDnoxzLHWL6q+1HdXbJIOMgUOHkJYVcIt+eGjCzdtyE6UP5GbJAB7LFUHiREh/MgzZZam7ohhSLhBRejQpmh6ffIy4nPdf28AoTKLavPVQfHI0/qyTBP5ybBY6oFZeu4rlrpQUtVf376q++nbR+mBs8excm8DtdurdNAKEN82pGRQgDioDMZj0tDjUc2xqPdx6IfiKc8KsJDkL+OKXDaqvqpS7ZEAuETKJjCvYI9DCNYXix36juTnD5g+LPlXCKnvDiGQTb7IIN5luOBH6ruzfD3BN15lErYt2K7t+YIkyArenjEa3+1ZXnfM7Tl8K70Qsh9d+CU8uimGJ3JlkfR08R5rirSHNo7fSMTo4zaywjIOp8aN9W6HwEml7psrlmNUgl97kSAbitz8TjJrnv4+vS23cysInyK1iVhDVk2PxfPVLXnV7ZbkNyVIfqKByQ4t+TIHrDgMUaanMAWOJ4tQfgTKlzmdE6aPsJS/FiwU/gFWT7izgsYUsIqmdFkMbUrOwfvvHg4Q+dp09h+7Oxpm02Hqpsyv4O8kA2ag8kKh8lJbtPVHYqNIl9xiM1BcRSqe3+sKgqrIz4qynS3e184VuuAl0w6J7c67OIRCb7zqSA4toPu8yZPPi6UunAru7y/EpNH0dwevz2KV5tIQP7bEhdLU8pVfHVSA7GZoz2ybfkH+PEHCN8bGz2Lz2vxNKLZbcl+CslNdxJuudImrf06GkS1xUXY2NJYdScvKZzPaI57XjOGb7keFWBiWcEEOKuTjZiHm2G0P8q7nm+s1TIbIxM3ch6UT1qH2yS3shm8csRhz+clH8nUtDcg2wDrexjlLM7T4hKBYvsxJhD59zTsyqN4XYIqdveK78/yGbrICFQoNZh/lwgndSIqGtA92/D950qT94MQxeDAa9+1QCq1d44ZO/pKHpSPs7AiP/zYurTz3TM87L99BmSIldlgJJ8XqLs1B+K5hxu8/j05IR9pBfjbcZoE/OETk1vzDpe0XhxNGkDf3IMG380pk1kpvA6tgHMPBJHFgTmE4MH/gL4/33+yx/EgXPJLuk8P9KclB2Cf3YB1QQUIE2XGxUOiBF//Q4qYBHLTwqkofcmT9poQjzvkHGGXl7md4OEWpcH2JK3M3vHGQJI+VL3MJPJM9BbT5uj+uESRBk59y7wVX5GjNfW3qr/cERZfYUTTqRJBt3/1G2fLi7XO76HDoKimNLFm5Q3gDfK7RJ/HRMeq728WJ35u0lN2X6L97LOeTIq/LIpN5eVUw+28QxF1zERbb5KvsKEuBIDbJp0kNSs+czNsluMnN0FGkd3vnNJoIV6AbVY7zfVCIYBnYrnEzqluU1783oITV1cMdd8DCFzqaQUeLfzZVLR6vC4UORg0xPlSIxVKXveyyhNcwg4nkVgVTNMVeo8JIloNsY2QKXG585D/x8Z5UfxEK9zD46yZFlj7mxzBhOKDxkHyautuvrsbOkI6fglUdwvdlcxxDu/h15jpHaF8wCDo2wD99Tgnk9eZ2FSsjAErOXUA+ODB9lZ3m4C7jNwr6uwM2mDQlFPpEfXZw2ImPCn1yflGoeCgGlxa3I7BFc+6En+pVdlwsXvgu0t9d9KC8Rozd+90uYP1UlD95dUgO4yRE0IJ7IaulT5IvSmE5jHMrp8/JEuQPkKQn0zemAt0a0dZ1KvRuWT82r0frVVW3VcjpXn7we/FBvJXucSUdL/bPBSoUcpgyRa6aw1lRrkBdC9EyYqmLchz2cJBygTZ3WVot+d2D+jynYN2mgDvlqAucDPA0X6AZ5eaFK9oaEUbYpPsUQUnrwHx1NWX1caRZ2zyPY/PVFZMoHenv41kvcxmpS7iCxN2EdMtAQ5VG5WOs6uuPwz6kUe7Aov005YYqSbLnnGBvRxjuGCQ/z3NyFElnGQ5jPQVSDya0xjQOO0a7dhHRDxP3AsPO3QOmA3du4AopPqEHZTKMHqxqh2ZbbQj83Pkl4MOINl/3+Nj1BE3UQ1G+xOEmHpuPBWpEceCireOiPpA2ChNxyALieg8stDss9BHmsEyTSPEfPDaflMdGFNtuRP4HfGlfAmcwbb46YRKQHGwKKR8fJdkVTIrQXN0DGg/O1dTddpaN6/cY57T1vDpf8G7d/tvWsOG3MgobDmg82FBT94/FhtdZw7nr2y9zBKarnq3UP1c5lyztH70MlKU9Gh9Zqq47DhMr3qT3eos+1jtLMuZTs95cvhEIqqaEbXBcoFOERuXjDdHX31bro33MC6l2T1hoswKLLXI6TE0L4TQVf9pjW6ab/w8parIKdijz2PwZUoPiwJN7zpNnRdms4p1yCOg8uNICx0gnG1Sb4jsYYulc5xOEQDyq4aPDwWKxThMez/Bxj89LLkvyOkCXYDnULUnj8vJK6hFs9yneWVmsVNzNl7lwpgonW+K0tv8Jk1pY0u1HtyuE4uVB12uDQHJlEDh6GzqF/filfUaYRygUe+EeXn5Soqcg9lhiELMGFXwuAsOXKEReLjxN7ZEMz02T0iupYvFsx/bS98rd85juMnNizNEYFAOFMakcw0g72ggmQbihIr0pobkmMdt5/2oN0odA7b/H4nW+r6q6tSZoJI0CX9dgkbRf5oxZDo39ns3BQnToSCFQLDIv94oBw7ay9nGSkOdIO+8yzwCS4l1cNvFOT1pqJNK32Pmy+S9g4PdmcWAeyDI+xw9T4qAj1jVIlngNvDtLxTNVSbEP7vd63O9DcP9Jj/tPatwziboL+FR9hnUNy3jOOzlOD8Fni2gk+SdtXXTo6eAc175kyY93kDadGrAjsTUnS5j8LJpYSYIEdD7qrBnHtm7+25wki7wZhm2nLpRTjlsN5dLvG/B8+gwFMjAFDlQFz30Is+DSZIuctq+tThkWT/HaFsESIXGUHGcMETNpsiihdcGHJwOYUo4dkjCFrd7Z3qNLmhK/7XAJXnCrsd7VlWH1e1vXEtM4645vnscmK7fHHut10p1zofNki8ttEdhsMrn+kmRQloKbKXDEdwnLtFgIOwRb5HI6XIOM1OacIkyBi5mDpk/+Ri1TModrKVas5nme1inIpAucL9vjdU4SrKT/Inn98EBC9xUen8+OYoFjnLV+Gi0w3i98XImNZMGRX1PiyzywKhC645JcX2IKtobrryA+f+hzWYbubTQur51Nj2AcXr/s0wq84w2BzXcPbO8V2N7vtY5+UhZVhayRLApH8dh8bGEjiv3lqtl20sBIKL8IqAkjn1BTn+EjzIRNjfru4rosyroP8uTclXSJQ+hlk2VShEyBg4q47g50OaJtPs8V2YWfYxQyhA4fXYzne1iWsBRwMQVznrjETxZ3VuLXtnHuRi42d/jsEmFHcoB+AhVvHNIFWyOi0Aec9DAw31yLxCfVnKrmtp78/IGu42/OXEItQOr0xiehnK72tvLJzumr5E5L4DRjFD4TLK83ztS2ca7iBNPfXVxGSdYs4Hl+naLagDPExVKngw5QVTgYFxXU8uMOEcBFX2gr8wpD/9XB1ZjWfE6H7tOUkWpfYA266GVOOWNL3DAq3/IRCt3wSmhGfZ4//u0K5kg8HVU/0sUDrHlFkC1zOBYkNf5R5NzQ6e+u2H6UYN2pVjKkTLErbvzeuWSmhEJXvMcFFv78gubLnI5x0SbNP2Kx+eq4lvGjvuJyJl+n366OqqpIUvISrbBnoa2UxIzeHq3XWYo5nVJYNBuVvh6/PfXQKiUE7ZELcfdXNnB7XTSl9BkYq91uQC3b9jAxh74EdvMGlHh5e3azJ5ZjN399K53vEJYY/G9ODLGppWIHhRvNisw99q3mhaGTYZzQoZmaEUiAG/oTbavLYnF7Bau6TLGSdIJHaZIQNrUFW5ap0wLaCAtzW4FswjUQgVksuhwoPlrqTc00vevtlrq2oXssRIQWXwVpYXr/oJm2ItbA6R8QRph4SefCJrrDM9tEH6dZhj4P7luL2earqKbcZa5ZnIE05ZDFnHYOdRhjOncMKfKLFM/nq/Pqosmy317fg6yCjqMPZp5LWK7SCicDapP03dIJEbU8ZKop5L3g4GlwC/bSNxc4ewLyCHxm6HDgnkLhmk3eWLOKqoZK2jiyhhz9Nu40+8ICV/AJlIvLIs3rqguIvf1WwcWPtF52zmBdRImxshhDIlSx4AtjQ4EzwOKKwCfmDm/jJmUiQzyB06vqLhqOUCeGisMhDeQjHltMicPj3kYGMo8/XM3pDPfu3xvwoNdtZOBiIi9SuIGx0WJExKGqi5dLwrpzUfwlCFP0Har3tWKHOEhzWPIggzO3+zL8XfUf8ISCB/ilWMCs2tS7TpZwBciIqjVIiAW/gGdpWeHQcHAHKtiCvH6F+v6YIqnXH7l1Dpffs5OsTZHVA3wBeXoPq/qm+Anz316//+Xd+9evjrIUVDhuILt//ep5leXV3xKybkGeFzUZ+m+vl3W9/tvbtxVpsXqzSpOyqIr7+k1SrN6CRfEW4frT23fv3sLF6i1fvUNrheWXf++xVNUio2ecOjjtI4mf10VZf4DrrJDcVvj1P6DAbf0sX8H7VypZ8utbvuKvEv7C3fjtdYqpS1YkeaCBRJgCfLc7x1CQdPj1Kyxy8MXQQey81aJHf953F/+pVnii/u08X8Dn317/L1Lrb6/O//N2qPhvr8gu+LdXv7z6387Ntwe2bcv5IyiTJUC60Bfw3F7D+O31u19+cUY65B5q8d6ltTOK84o81pmE4NjwSn8jlaKwIy6kcJS5iVZ/ZmlVl2JcizD9w4FxXLwkykyP0hknTlDRypz2SZi4PebTvUTuPEE/5IAZo+ub/Cu2bEaf7VnIvm7F74ngG0XynBUZEoWjoO63VTrQxYFHpxKcWE1YNNiDdwOqn+7bCl8/aHdBEgNxCQ5Q5Ej131bg+b87y8qywKuX6Bhx0JEJRdrcffqAtagoaL/e31ewDtlrSNxJCIJjUCfL6/RfMATJJWJ1vORDmPGkWK3JNUI34e5K8jYS+h/p+gihI5cfA7qcQZDjW1+2KKyFeLeyIF5XfxQZ/hcPIUhMFBesNixyDZMiX1QhC4IkGhW6ZSFSCQ/8b2YEf420OVi0fgGf66smv+2qD/L8nYc8R/yz/pqflmVRhqyxrkvklkGLBd87qFPMTAEj4nYoG7b4DKqaXMD27cuAgPw6zYN7g1DE78s7T8o0SYIEtrQ/HiOj2Ualljkv7PPqU7pA8iuEH9GfD3hnQlteQlwoUiFhqVMQTMPVqKDRWm8tf28A+fQtT/fFL2KxtXg4BKiAY2vUNvPeXUpor2aPg5s64/Lbv3BC/XvQZPUIus3fG9j0Wt6Q0XJPOJEam7s5xVQOsqU6zzFmMZqBvVR7k/vGRxCfeXkxz7QuTKvlQRluaCx3aQ7KFy/LzShy3ntobV/QbinXHJ0QW6/ED8VTnhVgsSeLrx9OmzK1bajJ098bmBKU9ynUKuHu6+xbBftGKSeql/9ZRBPkWejxHRMWj8Pz3d0OGYd64cPrWeYn90J2+lx3j8bFwHZeXcCnsDOJmxKg/lAqoq0RNtysvqWQBJli7F1tD2tB3yXzDuWjG+yJUNrcDPLfg/HDVdFdJxipq0/GZuncFNG7elOM0dEruM5e3HrrgHaMHp+cxMZ4nCSxUdqcm/nojO0l1Qiync2FofZV2GnaeEPc3HDxW98YibE7gYeLFlKerR1mgDAvN0Q3Ieme7sk+0SXp1yn+f/mLu1rZ+gPM1gqL24bXPhWVfpH79PeyKINOs6xCL7w6ZhN74YO4dUxfV0HWA7ZDWn8NEm6EA1GtEdZdl/y1vYq2JwuPGdOoB/bPJEMFc7HkfBH99Gpopr3WZ2rhvU98TNcEezVxhLF0YyDtYduOWKDPTnEzMi9Lnq6aFdndjlZkC+m3XZikK5DhsEj0qyLxje/+ilgOe2rRrLt2Hw1+opZa7P3d0FiUwonbbP3g5jgreynTRuUeN/kCrRC0Hoec/W2MyJ4Ins343FU1um6QosY8h+DPKeyjD2G4uLcF4m9iPHvtCT9d9mk5PaKD24pBnNTS07sTXPWgrvRHnCFc2OdoGEtiD6kt2nNk6gDYK0rCQmNxNuSXJIZeKf59fb6IzamXsQIG/SldwJtls7rLKdPJB9F3NJ93WdAEkAj0ahkWjxnr9Daqh0PvPI4qkvdLEOMWwnbCS1CiLpDM8kOC8jCMHacj8Zs+pgtH90y42LHzHY0id/SqrBfOo8UqzYcH3SMg7Bmne6A3bKpxzrSv+SdkaV4Gbi49jljiiU0SGHfTEnIFRpgWKltg3M4eZVnxNLgD4GMKn4I2xKP1uiwe4eIK4ES/180qZJYuijoqvh4ZeRSHG2xg/2Kh7I4uboqjJEifEJJB+qO6/tlEXyIgb+5BUjclLNEOU18gDWqzoGO18hGxTGyc59XH9L4+AWWQltXjCN+fu4c8v9ZLWF4yqe/8etY/DDps9Xrx6HOKdtRgQ6xOE7z1Hy0WXJNB3T+vhoCiCJEwgXcw86xdhD26oJEhorer5Ou9gM/LuOyQnD6v05JYxB/AiwqjS7gPedCLIAzn7k+gugb4Tk6MWWUxeVw14+qHRAGigeFzoqOHEsIVH6DjceA0IMJPEkcKPxre2wuTJx0S5rG8EHlH47scnsqLgJF5Ki/Ed4jPJKLcKT9Dk9ofcAQpZYs2wwjIemwnS5y0wsexZBt6tnkROoQCp1UQCbl3pAPsBZDjN9vyR7TEELL+tClI06uL5GckNyG2iQi+o0eQorppRuEMcANJ++i1e6V5tPF+Lp7aseKNpnuXL0iHT+9fiB19VpR9/44hMotC0B6D5CdJ7Y1TpwQebGATDeM7b2mITITBuAxSKcj21R1FxpiYFl974BgDHxk4XHSoUsNFXB9VFLE5Bj5uXo6bui6CbmV1uH6k1TJLqzocYbeWM4j4EglmxufhdVUY6dsEVZqE+ZlpBNG3jq/ZYtwGOjPjhIodit7G9RrhAZn3QKzORqg2hkd5b9IVjBDER+M+zRcRMfeuptO8hmUVzIud9GKwwpEZqJNwk7aJ7IWbFLZrN0jmo80TVvVw+HVSrPBtDLyVjMqsqP/9aWPVZZwPGcUPmD4sx1u+rJkSHf2PdDEi9k/j0mbYlWLLnAFxXIET68Qgzulq/0o5hSLmZbBtOXWVjjx9hOULnlV31wtbO8TxQl8997keS9cO6ccxqGC3vQY7CgZcXyComhKSa/X6MEjv7o4bQzg0g3+wTXl4JNt4nvY1I4lbLPQooAsXgiUO4IrhpmEQYjLExHe9LFrPD9iEt0eMaBtilfYjfsIYVeBzt7qnUbhf/FsF4wRtqUPAoisOfVP+EsTSrdapJTHuMrWaSARM3RlXdVI0axwZvQiaNRGLTdpAl5BAcnxMdsEwRt3gwVu3g1/dWvDIn2nbX8HjE86EI49cEVtxS+Qol+OiRhpY9BgpY4I2j4XSs12cQKlDOBITbhjnzOYyJQEmHsHofcUQBR9Hq4XmtGSCs6qbor2RwGP28uF2OL6ypwg2qo5dFCHS4a9A/hA/eWbEmMK4PqktjNzaCZ/FvkWcyyhI7sV+T+HTF4nOZiGB7aPYW9G1J8pQN5qYCYSs8lv92SNiHBaqXEIM6j/5hBweEgHNkQioOyjcsyU17w298XSyEfcaZydYZ+3vUVLJfmQervuhZujM8aS9avr3PWS2rdfucL2GozuougdLh9wIQTFkfThfDGSbZ51vCjZBRIAPfoP0UhlR7jVVFGI6e0Zs5D2FFYZSIPZOyQ+i8OlzXQLP5yDsJRt1ALcnIs3oEPPI2HxSZEX5CT6TFASxkW9f0mZXVWrPfLUz61IKL5lF+5uaQR04r86Qyd1sru5EiDgemQPpi2cHLozBhTRF3fvA1v7DcOMesuEoB1fRzy0ouh9ObuKe3BxOW4y9273TlsNhxG4fRtiHUXR83yXY35NNqR+Vh4o81AxTStbdK4Kx1/x51WeeCAok2lU23TP+1D8X4h4caE5A7JPV1Zge2gOpVVLjALxnRbkCgRmrelzXYPMYVax+Rk/gFOnq9dH9fZqlweoxuaMbpDuNveOfV+0ZQp/KPSQCRJO5WdvlMBbCjxXa7TM+cW4xdwjc08/FQ5pHiqrF+Ppb4BYoXY5nOnLeemQR5RGE2DX98YN/d0QM9v2x3pZ75tuPXfksLas62pkiZtJoyKR7sOdLWqs1yCM9ZyI9frTRsvuKQWsEocdZex/TPPHwP3DVQ3pyQiU8CEzrSNbTu5jI3kdB9o90fVlUOFGOXySbRLtaFjmUJ4DzS7PZJ8qKw9r++5+9UdOugj2RnkZH8DsPRzDxzVXd1hqk1xJEMW7r3TwVnyEm1nlVGK/ivHflu5tlCaE9/j+54kdLBJZpwuEOylj5HQRpz1uVvtrb72m96NmHS/4oa38bDoGiPVD9kCPqtZnUgjzjE73KE+ElG3vuppWqPWHu8bTcGffVu7sSPqZAtrhCXQczCnRrPj3Oigesz+4Ji34G+UODExU68+imZhCTSo+WBSlt54ixeWzT4WC39bFGSW7e4TrR5KewW9Kb/OGxUN6AhzhBqtt5j/4QbhH/YH4KE7cXL3siZ0fZi3sanTQZuXCkPzF2PxYk54rXcARj9SwDD+cr1HV8Bc5IG/fo5Kt6v27UjqvTtAHi1zXOaX0Fq6Ip90YFn12/6elpZHKf/EA9cuntAT/VB+cQL1aXWfOQqnLRWh4qF02iZvuAo6ML+FTt0QuIs3NoRA2cvIIVBxXOQBMHUxQJvJ3K7R/JTtkO/fVgYPRore/RFFl2lFdPexOChwfkEbROakX35/mJxP5doO9FHfZ0ydh3sBDR9oRrZt/m43FPG7XWybw4J/pRtuhoeZ7ItvqxgVWNtFi8RrbhgHE7NZDxjz3pM649kQVmW/Avf3FXmCM9/RQlGBgh6fge41CwvZWGGxwO3IfrEv4ZKVZXPXUhihMsV2mFz12vYIIl7f4yf8g+NMpiGvIOhCG2nuvTZ0xVkB019RLTNSHKwF7N++w3q+JFwvazdU5duYmItpOckbF/xbx1U/yEccxIgu4oSWBVxUNK4j/QBAclabJedGdF2az26Cz/plinifvy6qqFZRaZe3FbvWJqpy9c7sLFmCmuTpL1QbhjTxYIGZA7i3bVdnyB4HnELQfmj2tjd/W+Q49UVRetJA5yRuF0uUEI8MUb3IvAFww6LIop98RG8qgFGuu7KYH2Sfh8LItm7SmBurrjhoR6vVIT+YTiott1gtZyDIGC155MqTqIlVE8nLspnsiy3BMZtRPiYd/YbQ6neku8/WBaMpaIFzgJvqj5VfQpe3fDfmCTFIQmOQjsDJeiIDjHQdglblIT319q+J54DisGri7HdURU7b3DyPnThyxiTVnCPHmJdkm8R3gFNse3hszsf/VelexxVxi1v4M6tjBDRnuN1kJ2nicZ6qolOf7sJ4n7xvCtwSkau8GNDW8GTDRCptFpRtrJhmlG2DU26chQQw6L1b0xRowhwZ/ivQFkZxCOTVN1y2MTWN3y2NTu8Me5Wkb4ZHRGHF4kHrUVIjpGawJp6g3SdhYjP0lyBZ9Aubgs0AZX/YAlRKwVmFdyCZOfRVMf1XWZ3jU1jG01Cg1U/7mKlBepUwIUEX6usWdx0ugNWv86yhhJRBw2Y/DTMSVEcuQEzT+rp3hNO8JCRwiFTQTuUjTFVBhfYB7x6idcqEgX3NOTx8c42ZowstPndVoSy+lLkdfLEfD+FwRxxk7z5YcUCbf6AyR8GBKvOaDpMjx+KrJFpLkSkUdkBAr5Mch/RjOlOLzRlhiN9/wkNso+R1ZktOd3INKG1EloohTEzOeKA3PQgvhX+4g6fu8QJOy77KOgj8ZuqgauYNWI6XQ9pdEaX5iOTxwRccReI7tz0Inid/2ywXUrGNudegnSWMHmXAasKDTtUGJbBSe4bFqX4Si+rrETE2/vgQZtLlzBFUhzKkjdIyr8E8Av3HbG7UWB3fIJk5Eo4lFJbxV+q5Bu/ylF07U3iRdnf6OTVHVvvasWFpA1QQ6Tj+k9Ufb3kHX6oblP3qZm0Px9q+DiR1ovPVmIqx7cFSYDQ3R3y5TMuicM2qsz1Cyjvdzj3q4CT8iBYU/q8MOikd1851XfVXIPC/Dai6fQQKbZOqLBcIUGu8ZvukfT1AaM8e6MXMMc69Wxetiii9e9L7CqqFuzgQ+KbGaE6GazPTnkFg+zR0llhvFEjWOZUV27nPfN0r83oJsIf1n9LU9r8vDe2MeNQ0Ojny5OMZpJRjL2aWhvL7X79dgkY1sbm3ajHdnFP6r7UDzlWQEWwcmIzqseVRTF6DNi8byCPU6lTmi1+yKp/wOmD0svtdCmheMmX2TwA6hBHAdh99Q7vogZh0fdtv0LnOBjn7b9WTboCypNStgFzD6Srhiebtxy5ZH2bu6Xl2f2qOOWqEHBqwTDMcgAflcgRH2rRo+qiWmExT0MoP0fHk/K8fVHeFAOH+Ss9ucd4hkl+U0Jkp9p/hDxDJDEvI2rlJCDPBjrpPEDzNJHWNq8Ebktb4L1C2CPvBjDkLzud7Q1I5xPiX7j0b0HDrpH3ZT5FfwdZ6vbl2kPvy4VaeJm139i+J2uIKiK/KwoW16Jo6R3HAeJ/Wth7/sgtQ1BcLpnxCVLDn3x5P7+oogV5a572ZtPzuv+IgItKGLcCtuWaBOHDbIgoT4noNyrTTJcWl6CslMcgjxPrV/I75iVrhv2/PVmksOPV2eX/zMfe4x3WQKtTlgi9sfO/dHs6Ri7586JuW7mr9eIhn1Y3DCPeyLzZl4Wctp+XbPRuNa2ihZb2PrF1yPO0gyROzArcJtcuyNfaH7tkbPG6wi6JwtAPsRYvLc1meZnYZQ9YZGdmYVOqNyAhz2mvOOb1q7E+w7KFOk6Aw8jK/IuzcE+SbyfjQtNrcIw61T/hLHPiz0gb+5Bgi8BlcisUlzvC23FW222etKB9Gss7D/SxXjIP40b9wAqSCjvfwzo1Az+wTbl84BCVaUPOVqBKWFK0yvx7zxcS/1hCc4l6HGVhKkdYvv35h6Of3PvB1s7pB/nVfirFXtn+KP9IPkZwyAnNs3Xpv56T1CSQY6RIk+xs+7JdhrKX/2/A11uvwDieNsw4C2tNw7c+M7fCxZg54gYgrPNo0/UIznBAfO/N2kZFkRIKX//f3tX1hw3jqT/ykQ/7kN7u2f3zb0Rsmx1K8J2ayS5HbsvDLoKkjhmFWtJlm3Nrx8QvHAkgAQJ8LJfuq1CAnl9SOJMHMs8S8cvf2oH/hPg5FerW1x78ta68EWaxN4fKfA4axx1GnhsmK7v423D0RoVB0dB0Fa6Qp9xfD7EorYmsjTL7/7/HOekuH385FvOeuaw/yd1E7/B633Toz5mNQGj64K2dEdSsht5+t3DZ8YLpO2fHFV2xmj8l/Ztcvxc7YZBg7LlHDa6T0heLz1sKrDOto3kLy109bjh8E3quraPqfeoUwge94LxWwCVDzYCZw/rzsCWda7M/0e3eVekb44V/ajPxh3ZnXPiKB9qFZF/N8TbKtnb7DHrFuGCzMoc2/mjPKSvsv2zr9RXdNZJgdsu570n5dcs/xxg5fsQ58+s67QpvofEb6iVkSd1WJNvvlEtj4+E5ZMaKx/cmIOYDo8s161/L5FwwAtlpgz1Ytvu515D5qhvwsXbrGrAwSj401xXWX6gLuNOmHhqfvA7KED39PHC/PZfnnmdVcnZ3hz31JljP4FuY7BmWXAjAajWcdx3vm4jyKDOeeaBnyI+kQNhE27a1EacyXTyPZBx9SumTSEJme79LudWw4HlVbz7fH1k23h8btONwMbHwfx5FyhmP/4+xb1J/sTOPTnQD/xmdoiCfDuqV3apaEpa+NENBz5meElt+pjlzz98vF0fN2Hvh4u362Jm12S3tc18HxMGqkL9GK1vl9KG/eMPGqsOO2kQboB6sUtvyS7bTFLchU9Lp9jNQm5tX+/7xTKPhwo/5D8ANeU6R3o2r0kOadTH8d4hb4Xho1b7hNhGQDb4lV8/jw1Dz3a4auAIGPzklcQFnbg2qZW24W5rJPhlSCRgT7lQzb5Wwdd367cVAkPv4IQaUTcYep0cyLHYzvLbDxhNC6OrLD8ftriMy6nkNZv67KurzGPjxm732SnZzZZHyGFdiOVobvNrbgSY4/cXrvLsMByGYu1xF2qy4WLwdcedxzx/+ifZWVJvDTg5Ut0W8rPSUV0UikeNRK+LZjj76rl+Z9FTY92LH2PamyIUsKE4/elttpWzBpxGFZcB0yS5gZVvGiLfUl0iJKtfNwLLsENl6+j+1wGNOh9Kdth8PJzi5HErY2LI+gPX7uFv7rDGqtPMs3d7YLFv6DFGhyk8G9h+b/ueQ/r4q92OvZrWrA2qqVtNHJCvNFtHkUPuD6DAPVV6itqEO+j12Kk6B2B5ctxXp3niNH0e085FWca7p2oM8ctVouxEuRm7b+tXj2393bEtdCB5T74Wb0nVcTe4kgMr53VNB3yQETX4rYRLFeEi1mDUTPXFy9d8mPpv90sH44OA148ktJrh22wec0pcPpHd5+y8uaQSQbZhF5kuhT0YUtE3h8Z8tHld3Mff3nwjnKZDmqGNtKcVx31cJ0wJE3wTfXjX3FIuEEW5AcssahPe072ODhk/smX47YEOD8rsznmVLfomfq4Msq0H7WTt3HuP2oKHF4YWv4Ema70ROFw+71Ii5jsd5IGqmRuSJ5n+6VbkNlgZp6y1UW8e3pVxXiKeLpvsuJuHI1TXx6RM4nTgu3hi7cXfSWKXtZN/sYxAjPdG+ht4XBThPsUe0SWdTWTn435UPjLNKUw/Av3XEIHYJX39OX4/kv1dWj0YMj7rJA0l5K/ehBx808Dh65iy/lFPJDfSV2/aK6S/jPkUda38Ov2sEz9jzLOioGPz9IcH/XjQ4UO3lVMYVJO35AtJxw0A756yvMQ+A4074kXJfb4rfX1qds5875uFPDeCyixFLQQkExtkJIpUkuck99XeLAljMIJd5dWDklXiqDHTpzpz2B9xYbuN6mmX647U2Yq2EXmCbJOv4nomt07+vfhygTey2Rnwjdi/PrSGOt6E+lged+l5T66PdwltNu5mdMP2lm7ioqgO09GCcvQyctuYFz3vk1LNLTeoJW+nXN6RMm4OH/p5nblqcFxOe7hNf7bzeJTmltBRVn5RfEz2j2TUXmbdwv9lRz861s19zONTM2rQyIZvq3rZUvDBcC1fZVWIHdkv8yTLR2ZSZupU7+m5Wxwd9elHhZ3+GvMBbtuI1C8BSN/yAujtXw6BmdsEg6s6bumYl99xjtNXDZH/tdVwxAKIqzMbVmv1JS++mwR9zRCeZMda6OSGflqesv1lpeqwzik2hO6iNcNBXgU4ullWaWDkPZJeFcfO2tUMk6u5Xu5o12EG+LZtAuvVhtUgrwq8hi3sjPUkL76bBH3NEJ68IfkhKarDJXVSGD6DzRC3yu2hOy3Hd5CPQcaOUVFtwss1tE6nYdBrqofsx2O97tqZR3t7AT16+d5tvvI3ox631o+RTOSU2bBePO2QRitAK/8gGerKAT16S74k5OsfJD09nNPjwA+wh6UvzKkXL+6oFR7skLb6KFE+xkVj8QAHhl+l2WN7aXirvmz0Q6UiQK3TUZvdZMWAA5R9zRCdtLoP8r340tvCoW9wVE64LomcWwZ5naeuGSSCZ2n6V1Zt/c2ahHGCyE0VvTgWX4fspvN1g39Gt+uBeTP3+4sNtZ+8hYYqxVn/TtCgZ8vqccD/koKdlvbQ1PvMsSUd1i+KItslzLMNhzffTlleTQIeEjrTuNs9kf25WpovPkvIf3Pc/62ajfz2k0DUiHRH0oefxYJ357RMTmmyo3L89tMviqp9g4IMQotSidjkfyhNslMt1cZqnF5mx6LMY2oqtb8mx11yilNIHYkY2b0rg3fNyiWvyanaqzqWoLYYho2EFJaVjDD7josUd2wmefmCwwQGKpRRmrErFhGow3i//iIb5eWfx/p8/t+qg/7VRafLuNjFe7X7UNjvzdL04gMC8YVBsObuey9g4/TC8Gzkmw1o/ziTM9nXGRy6jALRlQlqr7OvxzTrM48xp/Y/is78z59/DoQxUHJBJg1FELR1+k8ANFgvDOOrhUAt4lOG6GMaTyREEKFgkojGCa/DWKg4pjVVcHSh2MnZX+bGFhfGtKqMc+x3EdBcYTBbTONqzga/dn2/ftE5av6vxVxbzvu2+20SrIkCi4JIRUHQBVooDLIkfVCDsv5l7kXACTyHozgT8uL3AifIQsuBU7/8MwucuuNiHuMSG9frlxm6g4B8U/2Pa48p8DlHnftnjibdIbeLEzV5dSS4ET8hRXSXnfOdYa7X1BXnet2PkwQX5XApJEtgbHUqTwAu82FaDU/zgcvZYXYf59XRcS/hYsMwc3L5HDAzHLqeDWbN53VVsQwasillm4lkLsOzJQayDmGWMDb/QHx+fE04FB8CL+MhuknR1f6jSTVZrXfIp8eWGbs4gUFxhPLNxDBeK5c4NvciQkQ9n3wh+fN9daNf61ueSHCqUOA0B3SIhr5woVU1DDacvmucbHNjohvFae7rLWtgLgkN4WlDw3JZI5dR+WJwtZoVyxlBNf2apduq1cyLltXbDQ8xWznLJ1y45NkKzYkFa1/AFLRZwyJmA0cBFXolxjo0bNDRigSWB4Gas//9RCBntnyFRWFvNV+4BYBt+i+de3ib+WsnXVa/JbS1pMrqU98eR+zUiC2IZ4WlokngB6ce0IvVUYQ5eCyaYAIkIlIvaBhjci0sBJO2bR3A6xo/fxcodEHBbPDTJ+2YCnclnadQub5QVUkEir4sjPHyitgSS1aPKUEdByzNBqU2Y4dHEFnmj1I+XNZS99vaAQAmmVm46w2gHd+HFwcG9y46ISQE4eY7DddmHekefbUNrdsaYnjofpzkIyPneYJECRxnWoUnQJUxq5WGpTnzzUzIsgyQHQLEZnHl4uMZYKXPdjY1ql4laXUvFc70NghTtkO8LnFvvWhA8RKNPz8amoS3EazEQj9eUpZeQZSubDMfr1Yjl4/XbLiqX0e6K3OWD6VgA6KofRJLC66OgPdo/+Mk4IIkF+UBCYLADDZYGJiBamH48g+dzYK1NunTOvDVSivI0P+4ehx1qqwCO21OqHVgp5VWkKH/cfXY6VRZBXaqZFbrwE0lqbhZy35YPV6YGqvBSp34LFKFdvVUQJTUMioStD+H2c/HetEDWhpFMNwq8vnwImes5rMKW1cY5dqiP5XCadBlykdukk8kDINA2SRToBGdn12HT1RC9gWA1barrwOCyfHfIUidATI3QC254Kde73SKoIta5rJ1kAnBN+mC11DALQ5sK4qASwTdVBFvNPDmjXRvvlUPVMbpxbl8qlrcsULpk7zoiGfSQMr5aSJcfQQ0qucCyNmweJXl5wNblPUNPP3+YsdTaIn7dfW46HVZDwjYC75To6B+NliBQfPzNnAAPI28aCBE7L+/59n5pEUBR6I4r/l5kg8RY6iKEAg6OsMEBA+KUS/XEkIIILe7x0LiZYagg/fltBGHkS9g8KETe4zrQkJo8tGLo18nHb8wsWYDEXsgJcAhOdPYhfEUGml+WfvZuFoNDCPR4DN7fxWT5mlBM+UwF4+a2Ue4bXqmD0X8SP5IqDT5cwTnlVpoGi5eclAekWAzibgEtTB8Z8/EBWIN6CvuMeI7gBg+psyFLybhvB8+diJuyYjqpFQlCHgUbjLs9IqsCTDLz2EyD2wmzFfiBpy5s5T8njyUl3G+j27O1aOPBdl/TMonjQ7D3Wi5ltRKITTW/xgukqD9NBIUnS4oTICumB0iwlgHVmioSwPFGkhyUJ4JBj1OCPCENudxT1txWVj7wHeFhY6GlgS1ycZIg3EmeHTeYdP7rCTLH2dXUqoS1L+uG0O9IssfZ9+SrxTtNxltoGiD0yrWJwHBBXHA8tWvXUJarWIlE8KZ14+gZTi+HLhMFoaGYkVwy7yJ/dj7qEv+krVCKsn6Ar4rMRmAOj2W/xlrRWUrRrDcQ70WGDnKgoNYECzPKNqxnkCEXpboKsy7Sdumppwukc2PbfqfJKPPOFopz/mxekyHBDhrHGg8zIksDW2Ekg2MgXl9VjH67S7wTB9VfqTH0oaXOce2GRPkMs7rIcur83GfEuP2RLOtwlECO158qRNMZImkIYlcGHJHS2eLMABSdMPw7EVcDoRW8ZVaAsym/FYNQtfsnysFW8vfg18CsCbckR+Eq7k35hv+dyeySx6a24cXZZknn84lWQHGjPJDcuko144+s3prgCIs+p+n6s8Ioxcfeux4wAEhVGg06IqQsiUMsxI1HEJjI6hJWdRkAmxgocEVr21o4HyXsXk02GYP2CYN5sZ8O3S4jx/tOcyWMoroZYZk4Us3MlrgVHIYIcyYfgwAlyXjjtmngDO/B4hhHT4nyij57ED7K86T+Fh2sfUyO3xKjoww0j8Zz9+H4YmEezBCgdOyoF02CFpG8jBXvLQGCoo2k6IYKXixl4y/1czNFw/U6b+7YzE691QdAc9/nGkJ9dKHY6LHqEDEY0Es2GR41Bto0dDjxV4a/tYbEzGQ3Gz0W3PIayFnX5JUCAGXT70MuVg0Trrs6AGWi1lrlJX4K07PHUjNGobBxbTIZepixGwIQ2J4EJ7CQLnW1gHPcgNzw3r24SRmyLDugeHqRn/RXXw4peR19vWYZrH+inBHIKy/dD8uHA2wevMjQTT+bFi4T0hO9dsRIQ+79bwVI8LmX2eYCPIV66QXZOF+3USy9V4fDDNeugXAavlTynlANOG00Q0/C5ko9sdRm4PEyweSIrnbaeb1AUtVCcNUcOeCYOYVYNYh0A+UmJkuDh/dXO4qScsq323w6w1ThSlJM7OQCvF2rk1YdcVHt3mvUZRZXg28kkOcP7/5tnuKj4/kNi7J5TmnLHbPhuF9QyAO7dsf8UGOiSCep6t/CTQgh/QKA5JaD1wE0zpgGdBgf/zAxAyYECw/32vw8e7z9ZHKsvt8d/5U7PKkPiu+iltXGuHFt+J1NKu/g6XTzGWBYHG4W/4Mb3mgm3AcPwZzc4/mL3Yp8OCu32XOQJjrRBcvzve/bmKNs9dnNWucFw8PSUp/IdFEb550DMWG+l/XnvigVwXDbO6EKuyJnqADJz0UFN7qU0gbG/GoOq1irEP1+0IB/Y4CNX4k0VWeHaZDichcGjeJRavHh6QQhiPvjKUA5D77AY+FwKN3xYzD1jL5Qk3+NnuMuH9XjtIPNiQ6cdQolU00gu246qQJNXYx2SwM0HilUKMYUcRFQG0VC0DzoWrKuOUKp9lD1uUT2X3OzvIJWuVnPbYUSgFkaukkaIPVMosW8oysxZ5hwKjREIVLueqMeTB355xq/HgTP7NczNfHpGrXU0pm/XBLZiwltJQL151nWdEHxZPzxGLw0WZ2N2vky8/BMqmCShlFC5syfhhA/ILSJYO8XHc2fL7NdnGa/IvsWxmjt/Hx8azMajjvdwS8u/sfJ0GgIrYojFoaBHWwqcKgTdUJw7SVcEZ8+Z4AhDv/LY/9tzHmX81Yv9nVuyVfEvL1D5KeHs7psUpCXLmkb5ytenaYor+X1bDrPot09a2wa9oA0deVOR/JVOUAdpoBIsQNmFGYazWaZJkM4xEn3nPiUCiwnStoqLQ+nw1Rfo8KQMZYIJiEyvMdVUmzx8vswOaJyKDGVZk6jvGshSMp/O8bilZaUzuxmxhG1b9vMvmVB8mLjEB2Yf3jNAed3JHkKUrB5lkGflrZZsPRe/K1cAxHXJWpwxHPmm9S+H1D4Uhraid2E8Oo+rfxEktHILtwwisrA5DkKRzB5lkGflrZ5ht3Z2n6V1Ymx8fmcCV23ifVm3y+J/MXRuVK4YZClNnyTjznwFf1w8Wx+GpYmeJIZK+2P09zy24MxnzN+DTmWhC2ehGXsXowaOlq1vUq/ILFuiOXp9WLyQG1/FstA+G0vhss7hia9t7KG5YbqupUtAbJ22X+bE+ukrwoX8dl/Cku1H2+qtYdKRv6N99OWV5S4dKs2WKuKTi/qiR3uydyiH/7af8powiIP6VgQ0pEgjhTmz0k1fUYDduuXM+zI7EwrFrYn1NyHxefAX5iMcROpLBwE7OgKdzEYoibSGHlRs5k/+YQJ+lFWca7J40vNXQwf5DUIkif6kvh3RdB7PpSvKpmBa1q2SHKlN/tsjPcMYRiEKAChYVbc1bgHSmfMsh+UjnETyKxMbSma1BlsFYBxbLWcpUUIxpSFixzPUsjI3uPSQodwPoisMd0pRYOl3FJHtnhHIVDXwRx6EttJkp25TmHonhXApqoLcR5wMBFIjD4I2qIoncxe0AQ7Z/qveUkJ5rAClKZvCYQ2kQQ3q9QeQvFIFOBAmdtA2oUCpO9Wyq0wZuK7+Lj+SFmvjJEIpHKJAdPiZbFIoSdu0hh66jyLE/trzIF2G1lIiRbAz8zIyuH7v6qwqArgdrvCm0KVB0Kjm9tCSh+W2gbODavZmu/zDIBOHiUaGw8S9ptKHS/0NkK1OelcpCjSGJh2O8ZKrz6IohNX2rh0J8hVDj0RRCHvtTGoTpER+7KnC0rFfVDaAA3kAzkDFJapOi3OxTOfRHErSq1R0dubVINilwhGAu5cgQfDQd92+hQU6dE0IabutgUcmoKmw4kPyRFQSfO7cqpqo9CAuqmUFknt3RYe4zTi3P5VE3u62cDtVKYyeGpr6mGRTp2PVrT2bkyiC8rLiJUh2e099kp2en4NIUGRg0FhpOOibH9hgLT/u95dj7pmDSFBk4NhYVTc1FEYdL8DrXfFCGHsR+qK63daX3tOFYkMw1kRUqLFL8nD3TYmO8tUsBkkBQwJVIKA2czN5wbNR8BrkzrTtTElFG+z0ooknJlWiZ1sYXJLflKFb7JkmNZ6B0GUkGMQULE+Esz7eqLdGMu1ASrJdR4TCw2cUL57ZbQ6cCxmgASMARL5bAZBRKrehkbe1Lo6lWUSWA1ZSrcXE731rBuVqejN83vNA8/Y2d65iecVXsZyUHbGWsMkg4tl4tEWJ+yd1N1DmSFBm+xchwf42NxOv7GSga5jPWGyYsX0rJuJT1+5LqeghAIJ4kzUjTPAWHN0pA7eK2pYZGOS9qviMKVQXy5Yus8PgNXLJvf4Xl7hlgT7ZOqArOotgieQbWlGNE7kMEadMVaRbAovaf1CXPkJ3BqKJWDXhFJbAseutSL6vqHjhJcDtERO6w03pPDKY3BngqT2VYee0rkVoFBApXEtHWA5tx+GPSMFQrT9wXL9ndyJLQjm4KjSgJODRQq25pon5RRXRXty8B10b7YwuRDrmfClUFMuGKbJn1SQVWTvgzUpC+29Q8SFxTKH0ny+AQNmqVysEeIJDiGrxM6ri/gwKCSGNhyVJjVBktMAmgMaw9OkUhO5wV0RJHAtE4kkVr7BJeOB+gVXCncLzgCPKs6u5WJXU1hYVkTWQPs4RQnj5BL+yI4oLalVtwyW5u+ITIFjFqJCLHq/ZaUJcktyNUR6lbEIVqbkdW8P6q1VRrQ7CqZK3PdcFdHiBIDN8TVZukA1hw0lPDqg4bYURyEHDgBcDtDYsoIeFtIpNHuCYlkVrWrHrTXnyORCWCVRRobCvOsKGjjqZ6rSgIiT6GymhqK3dqYjYnV9F+lZhLSloDzj7bQNvWIvxkOOwil4LSDJ7Cx0uyGGDZCcDsg7Wq8QRGVxLSsH12cTmlC9vdZQ58Q++ZkW1kPOoUCJ0NDbpdA3GbXHwzQ0Nl37m9JQUMwO0CNOJjQ7Fbqzz8oFKZNz4YI4Qh5w9Ky72omx+yIRhUpekmqO6GC2w7GbwsLGftdV8gwC5vI9c2op3OVAsjwoBMGIDXIBFAjzoR0NybVZRG+VHcypCNADB/1rIRS3UARy0q9T6XaVyHRnXgQqVw8bHMrwpcKQ+6qgOagfSQeYucqQMfuRWr5TgP3WhNwtJ4KYD4yL7QA3gZgTZgP+b8QVUabo7+oEHVN64wB0HpXBGhDvZTBNWO4aOFuEvCof3SlMYqBWq+SfC+AqaI78y/UNF5tYM3gbiqMMksknuQ3GkWkNXgZuF1Qe9hwa0BnGp1BgqFD4IEFCV/Jq1oLQYx4Dj3qzqOr9tFQ6pWSDr8zNTTn58V64Nn5urpU5F39doxvVx++1gddfFTlX4z63Ul3g98VGv8el6eArKJ2huqupmmGGN01Z2RVzTHVDN8P6ZZM/f3Q3IABa4JmUQvDmuc+zh8J2CMw1Tz5fGHmaef2TtiRK4VEDhR7lLKQhnFBjVzJf0idySzAxanCaBKwQkicADfEhEZMV74Gf2wj8W6X9lMr0hmUAe6a1VoY7pANxdVwtbvvaB+atKqrtCECqebaHK++xzAqq2YfaymkwQZbE5lAuNBnGHKBdP6HXdBVQVbZeAVwsOel64w8Cy0E9HU8qwWhQtsQWB7ETPZOApIH6ygTm0TcyIjanQzSbH+Yh+/4yoZlQvAiZb1QaLweaWhF2uIBGtNd8wxiQP1QDV/Z8HUClNWotxST8ZdQo65dwDwgYQBTQBdraxMY78u6q97s1pmUlkn8qyvtPLJ6umvVw1WUTKdXVCT07qUJVe92HLtNWUPw1BMbPC7dyq9drrlxD9aEDKCUhTCEPgjqib34cQlmeJWkKQ2kXcsGI0ik4UyAwtAIpdtch33Teq0V2pBdQMqkITSgS4/hbgYoTUD/0BJgC3MFvVpyCgWmjy49gljTkBqhbgWT6sDdNG12CKM5VKIQJpAzXLCa2uwV7qq2yR2MqqpEIVSVU1Cwmv2P4ydXWZoa1RQJQqjI56eo50xQ/olhqtUpMqK6RVg5nmSkkEoNMb1HV0+TuGOAivLZLf7QlGkYg6toUE6Tj6NW0ZJjw9gSdJAMbNWYP8S7KfUDIVxF+wdOUdik4CJNCB7dQw2mHdHnYTRhM/o0pkEMrydH01wmMuXBMQ5HcRVDoAmTG+iuPunlkOPH3XRdph+jnQCqEEZRUhKxqtyvftRlh+nt+kpkwRQWrgP0GsNH/geqHPH5iTT68jQWkYUsSL3IYHYjtbZa0a9nmyaNbq1pRgs6jzdZT+SbNfVYjs6j6BP3W5aqCLGqAtL5X1IREnSxWnD+raGKmmKTRBEiLIVTD8oXFvXnK/QHOeAKhhMMow9yQNnJhFaM2cY8maaxO9IuNfVovy7CHF0GNL0NZBK/iivZ2/p6nhZQevkNe9Iqkf+d6PCqtjnzohs6P3uKC7L/mJRPHAdVcVsVb+oIdeWsgKyqNuPfcEMI/bZvX28GuIInRcCa2h6PSrfoyTQfeL+j7SPW8hsX5jJPl63REhA5kgABkc802dcDk0i6qwjkhTQOhYz0IQZGhjyYrBFMYks/ZrH2C3slv+iYxzRtyk19n5Ao/CotJyW9a0/ReDrey2cUjfp29VqKhJ4EB2sq31dj9tOhE6H2RJJ1yicTrmnOJ+RxtQQ8kDJMqAPy0zY92ZR2dsQCN8LZWlr//g59bEJOoxvxrxFBPVxPbp0RqG8m8XMD/TNIUs+HEwg3vd+SFNiDgUw9Q08conPMbgrDhFFL63/eOLUZjImhEQe7LRX9GwiV+ZpvDZmNeQCCDImnNVmzQXANaMaAH7tdcAZBtCom8Ta0rUnG7RutZuauEDa2NrGpFtoD1KQ8xlMueupgYUJNPcS3wJcGMYbhnIqe2qqURhtAjWWYxJD8HXvLEt2EYdXd1wVMROJ83qIuqe+DmNj+EcfUDtZF12ZO8f3gQTYVmtAbCHrqmJnC9ITxWk08AKbTYxNjrfAmMg4n0XWtyhu1xo0bF2lGluI30rJFm9TcziRGwbQrpEk2NQ6nPh4+EEIGysmjYdgECtFdfDilpE/BpldcojQMXQYneQurcvdci3AuFlJZQ2lfSBp+clZ5hoZV178wM0Z9w7dLJfL/tQqvqrKs2ayV2j/a2irBPtpDV2a9mMXJIJs2RfdduUrSsjpBaFyLd6k+zdq8TSJz0wqxhxv/Gcvolxzi/PnNt91TfHwktzEdnncPTwGLi9ZKphAsvoTVhF/4lStxDYx/nKte7ALf3hppBPYHWnuRel1qax6/Mm7fWOuE2MWxPP7FGsK+5OXPTIagbKviPzTPa6LuiSvrkE1DGXLIpjzdVe8sa1/lGqB++y5WZNgRV4n8b4Urb3vVNbXPdg281oCOFgbqEHFC+xTXXXfNwTPwxRezoqs8O5jsYSIPYRD4cbAmqBjf+hptivvMwRAc8drNwD0xFinPjUHxUE9uCmngc2hNYDO+caZrR9eGZ5OYQAHShcDDNGrDr4BFwBtlwPEpbF2DeXTPqtV2sr2UZm5LXf5Dvnk25NCd+HpYdH1MyiRODQdqTRX0Sg47TAu/kXZXn7+zvHs23hjtgWGVld0u2rqB1DW2BJ9Exj1cNzAFEP8qnD3/D0ytV3Bk8h/o9bu6CeuDdkPMYQ7MwQOyHIj9BOBm3qS8pBRV38XL7FiUeZxUj6HQYVinX5vw6j6LdPUNK03+ednNPSBrFjTl1L5kxc9B7a9SeXSTUOBkdLEmUnW9vos2H/duFxZswlNfwOrFuBbDIxZ46axe4DA8YTbOsH3iMbO5Ojqz8OjEZjOozT3OhvW+8J6baqCRLYbHE/CcHWvD9E7dOMP22d3M5urozMKjs8fNoLb87h76i6i81wd8AHy1PcFXVvOOYf15sL1N6MHsfFI6hCU5crNSTvnv5jUJ/xEeNDCzDkKGtTrxGA8/0BltZPvGtUQYbNPao+ovX9SNVIanXiZ5V/byRf0KaPMD/ZNOWOl87122J2nBfn354vZMax9I/ddrUiSPfRMvaZtHwt4r7httaa6PD1k765Mkakna4vbRDVLG+7iML/IyqZ4moMU7Oupk79uy9Znq+cJPZH99/PNcns4lVZkcPqXCnPLlCzP/ly8UmV/W9w0KHypQMROqAvnz+OqcpPtO7qs4LaR5tK6JS2r93wn9vfYl7ZoleXzuWnqfHZENNeZ7TU7kuKdd7p4cTtU778Wfx7v4Cxki24eCvCWP8e75pkqSzpabdI3YHSGa/eXrJH7M40PRtNHXp39SDO8P3/7n33KJ1/t7hwgA + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.Designer.cs new file mode 100644 index 0000000000..0bb342e80d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.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 ExportFramework1 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ExportFramework1)); + + string IMigrationMetadata.Id + { + get { return "201509021536425_ExportFramework1"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs new file mode 100644 index 0000000000..09f2dc6314 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -0,0 +1,369 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class ExportFramework1 : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ExportDeployment", "CreateZip", c => c.Boolean(nullable: false)); + AddColumn("dbo.ExportDeployment", "HttpTransmissionTypeId", c => c.Int(nullable: false)); + AddColumn("dbo.ExportDeployment", "HttpTransmissionType", c => c.Int(nullable: false)); + AddColumn("dbo.ExportDeployment", "PassiveMode", c => c.Boolean(nullable: false)); + AddColumn("dbo.ExportDeployment", "UseSsl", c => c.Boolean(nullable: false)); + AddColumn("dbo.ExportProfile", "FileNamePattern", c => c.String(maxLength: 400)); + AddColumn("dbo.ExportProfile", "EmailAccountId", c => c.Int(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.ExportProfile", "EmailAccountId"); + DropColumn("dbo.ExportProfile", "FileNamePattern"); + DropColumn("dbo.ExportDeployment", "UseSsl"); + DropColumn("dbo.ExportDeployment", "PassiveMode"); + DropColumn("dbo.ExportDeployment", "HttpTransmissionType"); + DropColumn("dbo.ExportDeployment", "HttpTransmissionTypeId"); + DropColumn("dbo.ExportDeployment", "CreateZip"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.Billiger.StaticFileGenerationTask, SmartStore.Billiger'"); + context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.ElmarShopinfo.StaticFileGenerationTask, SmartStore.ElmarShopinfo'"); + context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.Guenstiger.StaticFileGenerationTask, SmartStore.Guenstiger'"); + context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.Shopwahl.StaticFileGenerationTask, SmartStore.Shopwahl'"); + + context.MigrateSettings(x => + { + x.DeleteGroup("BilligerSettings"); + x.DeleteGroup("ElmarShopinfoSettings"); + x.DeleteGroup("GuenstigerSettings"); + x.DeleteGroup("ShopwahlSettings"); + }); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Common.ExportSelected", "Export selected", "Ausgewhlte exportieren"); + builder.AddOrUpdate("Admin.Common.ExportAll", "Export all", "Alle exportieren"); + builder.AddOrUpdate("Common.Public", "public", "ffentlich"); + + builder.AddOrUpdate("Admin.System.ScheduleTask", "Scheduled task", "Geplante Aufgabe"); + + builder.AddOrUpdate("Admin.DataExchange.Export.NoExportProvider", + "There were no export provider found.", + "Es wurden keine Export-Provider gefunden."); + + builder.AddOrUpdate("Admin.DataExchange.Export.ProgressInfo", + "{0} of {1} records exported", + "{0} von {1} Datenstzen exportiert"); + + builder.AddOrUpdate("Admin.DataExchange.Export.FileNamePattern", + "Pattern for file names", + "Muster fr Dateinamen", + "Specifies the pattern for creating file names.", + "Legt das Muster fest, nach dem Dateinamen erzeugt werden."); + + builder.AddOrUpdate("Admin.DataExchange.Export.EmailAccountId", + "Email notification", + "E-Mail Benachrichtigung", + "Specifies the email account used to send a notification message of the completion of the export.", + "Legt das E-Mail Konto fest, ber welches eine Benachrichtigung ber die Fertigstellung des Exports verschickt werden soll."); + + builder.AddOrUpdate("Admin.DataExchange.Export.CompletedEmailAddresses", + "Email addresses to", + "E-Mail Adressen an", + "Specifies the email addresses where to send the notification message.", + "Legt die E-Mail Adressen fest, an die die Benachrichtigung geschickt werden soll."); + + builder.AddOrUpdate("Admin.DataExchange.Export.CompletedEmail.Subject", + "Export of profile \"{0}\" has been finished", + "Export von Profil \"{0}\" ist abgeschlossen"); + + builder.AddOrUpdate("Admin.DataExchange.Export.CompletedEmail.Body", + "This is an automatic notification of store \"{0}\" about a recent data export.", + "Dies ist eine automatische Benachrichtung von Shop \"{0}\" ber einen erfolgten Datenexport."); + + builder.AddOrUpdate("Admin.DataExchange.Export.FolderName", + "Folder name", + "Ordnername", + "Specifies the name of the folder where to export the data.", + "Legt den Namen des Ordners fest, in den die Daten exportiert werden."); + + builder.AddOrUpdate("Admin.DataExchange.Export.FolderAndFileName.Validate", + "Please enter a valid folder and file name. Example for file names: %File.Index%-%Profile.Id%-gmc-%Store.Name%", + "Bitte einen gltigen Ordner- und Dateinamen eingeben. Beispiel fr Dateinamen: %File.Index%-%Profile.Id%-gmc-%Store.Name%"); + + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.CreateZip", + "Create ZIP archive", + "ZIP-Archiv erstellen", + "Specifies whether to combine the export files in a ZIP archive and only to deploy the archive.", + "Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst und nur das Archiv bereitgestellt werden soll."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.AttributeCombinationAsProduct", + "Export attribute combinations", + "Attributkombinationen exportieren", + "Specifies whether to export a standalone product for each active attribute combination.", + "Legt fest, ob fr jede aktive Attributkombination ein eigenstndiges Produkt exportiert werden soll."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.AttributeCombinationValueMerging", + "Attribute values", + "Attributwerte", + "Specifies if and how to further process the attribute values.", + "Legt fest, ob und wie die Werte der Attribute weiter verarbeitet werden sollen."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportAttributeValueMerging.None", + "Not specified", "Nicht spezifiziert"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportAttributeValueMerging.AppendAllValuesToName", + "Append all values to the product name", "Alle Werte an den Produktnamen anhngen"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.HttpTransmissionType", + "HTTP transmission type", + "HTTP bertragungsart", + "Specifies how to transmit the export files via HTTP.", + "Legt fest, aus welcher Art die Exportdateien per HTTP bertragen werden sollen."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportHttpTransmissionType.SimplePost", "Simple POST", "Einfacher POST"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportHttpTransmissionType.MultipartFormDataPost", "Multipart form data POST", "Multipart-Form-Data POST"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.PassiveMode", + "Passive mode", + "Passiver Modus", + "Specifies whether to exchange data in active or passive mode.", + "Legt fest, ob Daten im aktiven oder passiven Modus ausgetauscht werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.UseSsl", + "Use SSL", + "SSL verwenden", + "Specifies whether to use a SSL (Secure Sockets Layer) connection.", + "Legt fest, ob einen SSL (Secure Sockets Layer) Verbindung genutzt werden soll."); + + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.Note", + "Specify individual filters to limit the exported data.", + "Legen Sie individuelle Filter fest, um die zu exportierenden Daten einzugrenzen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.Note", + "The following information will be taken into account during the export and integrated in the process.", + "Die folgenden Angaben werden beim Export bercksichtigt und an entsprechenden Stellen in den Vorgang eingebunden."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Configuration.Note", + "The following specific information will be taken into account by the provider during the export.", + "Die folgenden spezifischen Angaben werden durch den Provider beim Export bercksichtigt."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Configuration.NotRequired", + "The export provider {0} requires no further configuration.", + "Der Export-Provider {0} bentigt keine weitergehende Konfiguration."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.Note", + "Click Insert to add one or multiple publishing profiles to specify how to further proceed with the export files.", + "Legen Sie ber Hinzufgen ein oder mehrere Verffentlichungsprofile an, um festzulegen wie mit den Exportdateien weiter zu verfahren ist."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportOrderStatusChange.None", "None", "Keine"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportOrderStatusChange.Processing", "Processing", "Wird bearbeitet"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportOrderStatusChange.Complete", "Complete", "Komplett"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.OrderStatusChange", + "Change order status to", + "Auftragsstatus ndern in", + "Specifies if and how to change the status of the exported orders.", + "Legt fest, ob und wie der Status der exportierten Auftrge gendert werden soll."); + + builder.AddOrUpdate("Admin.DataExchange.Export.EnableProfileForPreview", + "The export profile is disabled. It must be enabled to preview the export data.", + "Das Exportprofil ist deaktiviert. Fr eine Exportvorschau muss das Exportprofil aktiviert sein."); + + builder.AddOrUpdate("Admin.DataExchange.Export.NoProfilesForProvider", + "There was no export profile of type {0} found. Create now a new export profile.", + "Es wurde kein Exportprofil vom Typ {0} gefunden. Jetzt ein neues Exportprofil anlegen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.ProfileForProvider", + "Export profile", + "Exportprofil", + "The export profile for this export provider.", + "Das Exportprofil fr diesen Export-Provider."); + + + RemoveObsoleteResources(builder); + } + + private void RemoveObsoleteResources(LocaleResourcesBuilder builder) + { + builder.Delete( + "Plugins.Feed.FreeShippingThreshold" + ); + + builder.Delete( + "Plugins.Feed.Billiger.ProductPictureSize", + "Plugins.Feed.Billiger.ProductPictureSize.Hint", + "Plugins.Feed.Billiger.TaskEnabled", + "Plugins.Feed.Billiger.TaskEnabled.Hint", + "Plugins.Feed.Billiger.StaticFileUrl", + "Plugins.Feed.Billiger.StaticFileUrl.Hint", + "Plugins.Feed.Billiger.GenerateStaticFileEachMinutes", + "Plugins.Feed.Billiger.GenerateStaticFileEachMinutes.Hint", + "Plugins.Feed.Billiger.BuildDescription", + "Plugins.Feed.Billiger.BuildDescription.Hint", + "Plugins.Feed.Billiger.Automatic", + "Plugins.Feed.Billiger.DescShort", + "Plugins.Feed.Billiger.DescLong", + "Plugins.Feed.Billiger.DescTitleAndShort", + "Plugins.Feed.Billiger.DescTitleAndLong", + "Plugins.Feed.Billiger.DescManuAndTitleAndShort", + "Plugins.Feed.Billiger.DescManuAndTitleAndLong", + "Plugins.Feed.Billiger.DescriptionToPlainText", + "Plugins.Feed.Billiger.DescriptionToPlainText.Hint", + "Plugins.Feed.Billiger.ShippingCost", + "Plugins.Feed.Billiger.ShippingCost.Hint", + "Plugins.Feed.Billiger.ShippingTime", + "Plugins.Feed.Billiger.ShippingTime.Hint", + "Plugins.Feed.Billiger.Brand", + "Plugins.Feed.Billiger.Brand.Hint", + "Plugins.Feed.Billiger.UseOwnProductNo", + "Plugins.Feed.Billiger.UseOwnProductNo.Hint", + "Plugins.Feed.Billiger.Store", + "Plugins.Feed.Billiger.Store.Hint", + "Plugins.Feed.Billiger.ConvertNetToGrossPrices", + "Plugins.Feed.Billiger.ConvertNetToGrossPrices.Hint", + "Plugins.Feed.Billiger.LanguageId", + "Plugins.Feed.Billiger.LanguageId.Hint", + "Plugins.Feed.Billiger.ConfigSaveNote", + "Plugins.Feed.Billiger.GeneratingNow", + "Plugins.Feed.Billiger.SuccessResult" + ); + + builder.Delete( + "Plugins.Feed.ElmarShopinfo.TaskEnabled", + "Plugins.Feed.ElmarShopinfo.TaskEnabled.Hint", + "Plugins.Feed.ElmarShopinfo.StaticFileUrl", + "Plugins.Feed.ElmarShopinfo.StaticFileUrl.Hint", + "Plugins.Feed.ElmarShopinfo.GenerateStaticFileEachMinutes", + "Plugins.Feed.ElmarShopinfo.GenerateStaticFileEachMinutes.Hint", + "Plugins.Feed.ElmarShopinfo.BuildDescription", + "Plugins.Feed.ElmarShopinfo.BuildDescription.Hint", + "Plugins.Feed.ElmarShopinfo.Automatic", + "Plugins.Feed.ElmarShopinfo.DescShort", + "Plugins.Feed.ElmarShopinfo.DescLong", + "Plugins.Feed.ElmarShopinfo.DescTitleAndShort", + "Plugins.Feed.ElmarShopinfo.DescTitleAndLong", + "Plugins.Feed.ElmarShopinfo.DescManuAndTitleAndShort", + "Plugins.Feed.ElmarShopinfo.DescManuAndTitleAndLong", + "Plugins.Feed.ElmarShopinfo.DescriptionToPlainText", + "Plugins.Feed.ElmarShopinfo.DescriptionToPlainText.Hint", + "Plugins.Feed.ElmarShopinfo.ProductPictureSize", + "Plugins.Feed.ElmarShopinfo.ProductPictureSize.Hint", + "Plugins.Feed.ElmarShopinfo.Currency", + "Plugins.Feed.ElmarShopinfo.Currency.Hint", + "Plugins.Feed.ElmarShopinfo.ShippingTime", + "Plugins.Feed.ElmarShopinfo.ShippingTime.Hint", + "Plugins.Feed.ElmarShopinfo.Brand", + "Plugins.Feed.ElmarShopinfo.Brand.Hint", + "Plugins.Feed.ElmarShopinfo.Store", + "Plugins.Feed.ElmarShopinfo.Store.Hint", + "Plugins.Feed.ElmarShopinfo.ConvertNetToGrossPrices", + "Plugins.Feed.ElmarShopinfo.ConvertNetToGrossPrices.Hint", + "Plugins.Feed.ElmarShopInfo.LanguageId", + "Plugins.Feed.ElmarShopInfo.LanguageId.Hint", + "Plugins.Feed.ElmarShopInfo.General", + "Plugins.Feed.ElmarShopInfo.General.Hint", + "Plugins.Feed.ElmarShopInfo.Automation", + "Plugins.Feed.ElmarShopInfo.Automation.Hint", + "Plugins.Feed.ElmarShopInfo.Address", + "Plugins.Feed.ElmarShopInfo.Address.Hint", + "Plugins.Feed.ElmarShopInfo.Contact", + "Plugins.Feed.ElmarShopInfo.Contact.Hint", + "Plugins.Feed.ElmarShopInfo.Generate", + "Plugins.Feed.ElmarShopInfo.ConfigSaveNote" + ); + + builder.Delete( + "Plugins.Feed.Guenstiger.TaskEnabled", + "Plugins.Feed.Guenstiger.TaskEnabled.Hint", + "Plugins.Feed.Guenstiger.StaticFileUrl", + "Plugins.Feed.Guenstiger.StaticFileUrl.Hint", + "Plugins.Feed.Guenstiger.GenerateStaticFileEachMinutes", + "Plugins.Feed.Guenstiger.GenerateStaticFileEachMinutes.Hint", + "Plugins.Feed.Guenstiger.BuildDescription", + "Plugins.Feed.Guenstiger.BuildDescription.Hint", + "Plugins.Feed.Guenstiger.Automatic", + "Plugins.Feed.Guenstiger.DescShort", + "Plugins.Feed.Guenstiger.DescLong", + "Plugins.Feed.Guenstiger.DescTitleAndShort", + "Plugins.Feed.Guenstiger.DescTitleAndLong", + "Plugins.Feed.Guenstiger.DescManuAndTitleAndShort", + "Plugins.Feed.Guenstiger.DescManuAndTitleAndLong", + "Plugins.Feed.Guenstiger.ProductPictureSize", + "Plugins.Feed.Guenstiger.ProductPictureSize.Hint", + "Plugins.Feed.Guenstiger.Brand", + "Plugins.Feed.Guenstiger.Brand.Hint", + "Plugins.Feed.Guenstiger.ShippingTime", + "Plugins.Feed.Guenstiger.ShippingTime.Hint", + "Plugins.Feed.Guenstiger.Store", + "Plugins.Feed.Guenstiger.Store.Hint", + "Plugins.Feed.Guenstiger.ConvertNetToGrossPrices", + "Plugins.Feed.Guenstiger.ConvertNetToGrossPrices.Hint", + "Plugins.Feed.Guenstiger.LanguageId", + "Plugins.Feed.Guenstiger.LanguageId.Hint", + "Plugins.Feed.Guenstiger.NoSpec", + "Plugins.Feed.Guenstiger.NoSpec.Hint", + "Plugins.Feed.Guenstiger.DescriptionToPlainText", + "Plugins.Feed.Guenstiger.DescriptionToPlainText.Hint", + "Plugins.Feed.Guenstiger.Generate", + "Plugins.Feed.Guenstiger.ConfigSaveNote" + ); + + builder.Delete( + "Plugins.Feed.Shopwahl.TaskEnabled", + "Plugins.Feed.Shopwahl.TaskEnabled.Hint", + "Plugins.Feed.Shopwahl.StaticFileUrl", + "Plugins.Feed.Shopwahl.StaticFileUrl.Hint", + "Plugins.Feed.Shopwahl.GenerateStaticFileEachMinutes", + "Plugins.Feed.Shopwahl.GenerateStaticFileEachMinutes.Hint", + "Plugins.Feed.Shopwahl.ShippingCost", + "Plugins.Feed.Shopwahl.ShippingCost.Hint", + "Plugins.Feed.Shopwahl.ShippingTime", + "Plugins.Feed.Shopwahl.ShippingTime.Hint", + "Plugins.Feed.Shopwahl.Currency", + "Plugins.Feed.Shopwahl.Currency.Hint", + "Plugins.Feed.Shopwahl.ProductPictureSize", + "Plugins.Feed.Shopwahl.ProductPictureSize.Hint", + "Plugins.Feed.Shopwahl.BuildDescription", + "Plugins.Feed.Shopwahl.BuildDescription.Hint", + "Plugins.Feed.Shopwahl.Automatic", + "Plugins.Feed.Shopwahl.DescShort", + "Plugins.Feed.Shopwahl.DescLong", + "Plugins.Feed.Shopwahl.DescTitleAndShort", + "Plugins.Feed.Shopwahl.DescTitleAndLong", + "Plugins.Feed.Shopwahl.DescManuAndTitleAndShort", + "Plugins.Feed.Shopwahl.DescManuAndTitleAndLong", + "Plugins.Feed.Shopwahl.NoSpec", + "Plugins.Feed.Shopwahl.UseOwnProductNo", + "Plugins.Feed.Shopwahl.UseOwnProductNo.Hint", + "Plugins.Feed.Shopwahl.DescriptionToPlainText", + "Plugins.Feed.Shopwahl.DescriptionToPlainText.Hint", + "Plugins.Feed.Shopwahl.Brand", + "Plugins.Feed.Shopwahl.Brand.Hint", + "Plugins.Feed.Shopwahl.ExportFormat", + "Plugins.Feed.Shopwahl.ExportFormat.Hint", + "Plugins.Feed.Shopwahl.Store", + "Plugins.Feed.Shopwahl.Store.Hint", + "Plugins.Feed.Shopwahl.ConvertNetToGrossPrices", + "Plugins.Feed.Shopwahl.ConvertNetToGrossPrices.Hint", + "Plugins.Feed.Shopwahl.LanguageId", + "Plugins.Feed.Shopwahl.LanguageId.Hint", + "Plugins.Feed.Shopwahl.Generate", + "Plugins.Feed.Shopwahl.ConfigSaveNote" + ); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.resx b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.resx new file mode 100644 index 0000000000..e07999aa4a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.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/201509031112324_Merge3.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.Designer.cs new file mode 100644 index 0000000000..b8acae3dd9 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.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 Merge3 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Merge3)); + + string IMigrationMetadata.Id + { + get { return "201509031112324_Merge3"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.cs b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.cs new file mode 100644 index 0000000000..24ce9a2d5f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.cs @@ -0,0 +1,16 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Merge3 : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.resx b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.resx new file mode 100644 index 0000000000..e84ddc5393 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.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/201509150931528_ExportFramework2.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.Designer.cs new file mode 100644 index 0000000000..1ff305bd2d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.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 ExportFramework2 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ExportFramework2)); + + string IMigrationMetadata.Id + { + get { return "201509150931528_ExportFramework2"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs new file mode 100644 index 0000000000..5bab264538 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -0,0 +1,276 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class ExportFramework2 : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ExportProfile", "ResultInfo", c => c.String()); + } + + public override void Down() + { + DropColumn("dbo.ExportProfile", "ResultInfo"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.GoogleMerchantCenter.StaticFileGenerationTask, SmartStore.GoogleMerchantCenter'"); + context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.BMEcat.StaticFileGenerationTask, SmartStore.BMEcat'"); + + context.MigrateSettings(x => + { + x.DeleteGroup("FroogleSettings"); + x.DeleteGroup("BMEcatExportSettings"); + x.DeleteGroup("OpenTransSettings"); + }); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Common.Example", "Example", "Beispiel"); + builder.AddOrUpdate("Common.ShowAll", "Show all", "Alle anzeigen"); + builder.AddOrUpdate("Admin.Common.Selected", "Selected", "Ausgewhlte"); + builder.AddOrUpdate("Admin.Common.Entity", "Object", "Objekt"); + builder.AddOrUpdate("Admin.Common.Placeholder", "Placeholder", "Platzhalter"); + + + builder.AddOrUpdate("Admin.Common.FilesDeleted", + "{0} files were deleted", + "{0} Dateien wurden gelscht"); + + builder.AddOrUpdate("Admin.Common.FoldersDeleted", + "{0} folders were deleted", + "{0} Verzeichnisse wurden gelscht"); + + builder.AddOrUpdate("Admin.Common.ProviderNotLoaded", + "Cannot load the provider {0}.", + "Der Provider {0} konnte nicht geladen werden."); + + builder.AddOrUpdate("Admin.Common.NoEntriesSelected", + "No entries have been selected.", + "Es wurden keine Eintrge ausgewhlt."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Blog.ShowHeaderRSSUrl.Hint", + "Check to enable the blog RSS feed link in customers browser address bar.", + "Legt fest, ob der RSS-Feed-Link in der Adressleiste des Browsers angezeigt werden soll."); + + + builder.AddOrUpdate("Admin.System.SeNames", "SEO Names", "SEO Namen"); + builder.Delete("Admin.System.SeNames.DeleteSelected"); + + builder.AddOrUpdate("Admin.System.SeNames.Name", + "SEO Name", + "SEO Name", + "Specifies the SEO name.", + "Legt den SEO Namen fest."); + + builder.AddOrUpdate("Admin.System.SeNames.EntityId", + "Object ID", + "Objekt-ID", + "Specifies the ID of the associated object.", + "Legt die ID des zugehrigen Objektes fest."); + + builder.AddOrUpdate("Admin.System.SeNames.EntityName", + "Object", + "Objekt", + "Specifies the name of the associated object.", + "Legt den Namen der zugehrigen Objektes fest."); + + builder.AddOrUpdate("Admin.System.SeNames.IsActive", + "Is active", + "Ist aktiv", + "Specifies whether the SEO name is active or inactive.", + "Legt fest, ob der SEO Name aktiv oder inaktiv ist."); + + builder.AddOrUpdate("Admin.System.SeNames.Language", + "Language", + "Sprache", + "Specifies the language of the SEO name.", + "Legt die Sprache des SEO Namens fest."); + + builder.AddOrUpdate("Admin.System.SeNames.SlugsPerEntity", + "Names per object", + "Namen pro Objekt", + "The number of SEO names per object.", + "Die Anzahl der SEO Namen pro Objekt."); + + builder.AddOrUpdate("Admin.System.SeNames.ActiveSlugAlreadyExist", + "Only one active SEO name should be set per language.", + "Pro Sprache darf nur ein aktiver SEO Name festgelegt werden."); + + + builder.AddOrUpdate("Admin.DataExchange.Export.FileNamePatternDescriptions", + "ID of export profil;Folder name of export profil;SEO name of export profil;Store ID;SEO name of store;One based file index;Random number;UTC timestamp", + "ID des Exportprofils;Ordername des Exportprofils;SEO Name des Exportprofils;Shop ID;SEO Name des Shops;Mit 1 beginnender Dateiindex;Zufallszahl;UTC Zeitstempel"); + + builder.AddOrUpdate("Admin.DataExchange.Export.NotPreviewCompatible", + "This option is not taken into account in the preview.", + "Diese Option wird in der Vorschau nicht bercksichtigt."); + + builder.AddOrUpdate("Admin.DataExchange.Export.CloneProfile", + "Apply settings from", + "Einstellungen bernehmen von", + "Specifies an export profile from which to apply the settings.", + "Legt das Exportprofil fest, von welchem die Einstellungen bernommen werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.NonFileBasedExport.Note", + "The export provider does not explicit support any file type. Therefore, the export provider is responsible for futher deployment of export data.", + "Der Export-Provider untersttzt keinen expliziten Dateityp. Fr eine weitere Bereitstellung der Exportdaten ist daher der Export-Provider verantwortlich."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.NoGroupedProducts", + "Do not export grouped products", + "Keine Gruppenprodukte exportieren", + "Specifies whether to export grouped products. If this option is activated, then the associated products will be exported.", + "Legt fest, ob Gruppenprodukte exportiert werden sollen. Ist diese Option aktiviert, so werden die zur Gruppe gehrenden Produkte exportiert."); + + builder.AddOrUpdate("Admin.DataExchange.Export.NoFiltering", + "There is no filtering available.", + "Mglichkeiten der Filterung stehen nicht zur Verfgung."); + + builder.AddOrUpdate("Admin.DataExchange.Export.NoProjection", + "There is no projection available.", + "Mglichkeiten der Projektion stehen nicht zur Verfgung."); + + builder.AddOrUpdate("Admin.DataExchange.Export.NoPreview", + "There is no preview available for this entity type.", + "Eine Vorschau steht fr diesen Entittstyp nicht zur Verfgung."); + + + builder.Delete( + "Plugins.Feed.Froogle.TaskEnabled", + "Plugins.Feed.Froogle.TaskEnabled.Hint", + "Plugins.Feed.Froogle.StaticFileUrl", + "Plugins.Feed.Froogle.StaticFileUrl.Hint", + "Plugins.Feed.Froogle.GenerateStaticFileEachMinutes", + "Plugins.Feed.Froogle.GenerateStaticFileEachMinutes.Hint", + "Plugins.Feed.Froogle.Currency", + "Plugins.Feed.Froogle.Currency.Hint", + "Plugins.Feed.Froogle.ProductPictureSize", + "Plugins.Feed.Froogle.ProductPictureSize.Hint", + "Plugins.Feed.Froogle.AppendDescriptionText", + "Plugins.Feed.Froogle.AppendDescriptionText.Hint", + "Plugins.Feed.Froogle.BuildDescription", + "Plugins.Feed.Froogle.BuildDescription.Hint", + "Plugins.Feed.Froogle.Automatic", + "Plugins.Feed.Froogle.DescShort", + "Plugins.Feed.Froogle.DescLong", + "Plugins.Feed.Froogle.DescTitleAndShort", + "Plugins.Feed.Froogle.DescTitleAndLong", + "Plugins.Feed.Froogle.DescManuAndTitleAndShort", + "Plugins.Feed.Froogle.DescManuAndTitleAndLong", + "Plugins.Feed.Froogle.UseOwnProductNo", + "Plugins.Feed.Froogle.UseOwnProductNo.Hint", + "Plugins.Feed.Froogle.DescriptionToPlainText", + "Plugins.Feed.Froogle.DescriptionToPlainText.Hint", + "Plugins.Feed.Froogle.Brand", + "Plugins.Feed.Froogle.Brand.Hint", + "Plugins.Feed.Froogle.Store", + "Plugins.Feed.Froogle.Store.Hint", + "Plugins.Feed.Froogle.ConvertNetToGrossPrices", + "Plugins.Feed.Froogle.ConvertNetToGrossPrices.Hint", + "Plugins.Feed.Froogle.LanguageId", + "Plugins.Feed.Froogle.LanguageId.Hint", + "Plugins.Feed.Froogle.Generate", + "Plugins.Feed.Froogle.ConfigSaveNote", + "Plugins.Feed.Froogle.AvailabilityAvailableForOrder", + "Plugins.Feed.Froogle.GridEditNote", + "Plugins.Feed.Froogle.General", + "Plugins.Feed.Froogle.ProductData" + ); + + builder.Delete( + "Plugins.Feed.BMEcat.TaskEnabled", + "Plugins.Feed.BMEcat.TaskEnabled.Hint", + "Plugins.Feed.BMEcat.StaticFileUrl", + "Plugins.Feed.BMEcat.StaticFileUrl.Hint", + "Plugins.Feed.BMEcat.GenerateStaticFileEachMinutes", + "Plugins.Feed.BMEcat.GenerateStaticFileEachMinutes.Hint", + "Plugins.Feed.BMEcat.UseOwnProductNo", + "Plugins.Feed.BMEcat.UseOwnProductNo.Hint", + "Plugins.Feed.BMEcat.ShippingCostAustria", + "Plugins.Feed.BMEcat.ShippingCostAustria.Hint", + "Plugins.Feed.BMEcat.Currency", + "Plugins.Feed.BMEcat.Currency.Hint", + "Plugins.Feed.BMEcat.ProductPictureSize", + "Plugins.Feed.BMEcat.ProductPictureSize.Hint", + "Plugins.Feed.BMEcat.AppendDescriptionText", + "Plugins.Feed.BMEcat.AppendDescriptionText.Hint", + "Plugins.Feed.BMEcat.BuildDescription", + "Plugins.Feed.BMEcat.BuildDescription.Hint", + "Plugins.Feed.BMEcat.Automatic", + "Plugins.Feed.BMEcat.DescShort", + "Plugins.Feed.BMEcat.DescLong", + "Plugins.Feed.BMEcat.DescTitleAndShort", + "Plugins.Feed.BMEcat.DescTitleAndLong", + "Plugins.Feed.BMEcat.DescManuAndTitleAndShort", + "Plugins.Feed.BMEcat.DescManuAndTitleAndLong", + "Plugins.Feed.BMEcat.UseOwnProductNo", + "Plugins.Feed.BMEcat.UseOwnProductNo.Hint", + "Plugins.Feed.BMEcat.DescriptionToPlainText", + "Plugins.Feed.BMEcat.DescriptionToPlainText.Hint", + "Plugins.Feed.BMEcat.ShippingCost", + "Plugins.Feed.BMEcat.ShippingCost.Hint", + "Plugins.Feed.BMEcat.ShippingTime", + "Plugins.Feed.BMEcat.ShippingTime.Hint", + "Plugins.Feed.BMEcat.Brand", + "Plugins.Feed.BMEcat.Brand.Hint", + "Plugins.Feed.BMEcat.Store", + "Plugins.Feed.BMEcat.Store.Hint", + "Plugins.Feed.BMEcat.ConvertNetToGrossPrices", + "Plugins.Feed.BMEcat.ConvertNetToGrossPrices.Hint", + "Plugins.Feed.BMEcat.LanguageId", + "Plugins.Feed.BMEcat.LanguageId.Hint", + "Plugins.Feed.BMEcat.Generate", + "Plugins.Feed.BMEcat.ConfigSaveNote" + ); + + builder.Delete("Plugins.Widgets.OpenTrans.IsLexwareCompatibe"); + builder.Delete("Admin.System.Maintenance.DeleteExportedFolders.TotalDeleted"); + + // Common + builder.AddOrUpdate("StoreClosed", + "We'll be back.", + "Wir sind bald wieder da."); + builder.AddOrUpdate("StoreClosed.Hint", + "We're busy updating our online store for you and will be back soon.", + "Wir aktualisieren gerade das Angebot in unserem Online-Shop. Die Seite ist demnchst wieder verfgbar."); + + builder.AddOrUpdate("Admin.System.SystemInfo.UsedMemorySize", + "Used memory (RAM)", + "Benutzter Speicher (RAM)"); + builder.AddOrUpdate("Admin.System.SystemInfo.GarbageCollect", + "Collect", + "Aufrumen"); + builder.AddOrUpdate("Admin.System.SystemInfo.GarbageCollectSuccessful", + "The memory has been successfully cleaned up.", + "Der Arbeitsspeicher wurde erfolgreich aufgerumt."); + + builder.AddOrUpdate("Admin.Configuration.Themes.NoConfigurationRequired", + "Theme requires no configuration", + "Theme bentigt keine Konfiguration"); + + + builder.AddOrUpdate("Tax.LegalInfoFooter2", + "* All prices {0}, plus shipping", + "* Alle Preise {0}, zzgl. Versandkosten"); + + builder.AddOrUpdate("Tax.LegalInfoProductDetail2", + "{0} {1} {2}plus shipping", + "{0} {1} {2} zzgl. Versandkosten"); + + builder.AddOrUpdate("ShoppingCart.ShippingInfoLink", + "For a complete listing of all shipping costs please click here.", + "Eine vollstndige Liste aller Versandkosten finden Sie hier."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.resx b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.resx new file mode 100644 index 0000000000..f24f629459 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.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+w8yPc2s9UglVZdZn7KqHSMpUkprXdgkJZ3TL7RgJMiMVmREVlx46bX9sn3YT5pfGABxQwCOOyIyk5UPVUoGHA7A4e5wOByO//X//f+//c/HdfriHhVlkme/v3zz6qeXL1AW58sku/v9ZV3d/o+/vfyf//f/+X/8drpcP7741sH9TOBwzaz8/eWqqja/vn5dxiu0jspX6yQu8jK/rV7F+fp1tMxfv/3pp/94/ebNa4RRvMS4Xrz47aLOqmSN6B/4z5M8i9GmqqP0U75Eadl+xyWXFOuLz9EalZsoRr+/vFxHRXVZ5QV69S6qopcvjtIkwt24ROntyxdRluVVVOFO/vq1RJdVkWd3lxv8IUqvnjYIw91GaYnazv86gJuO46e3ZByvh4odqrguq3xtifDNzy1hXvPVncj7siccJt0pJnH1REZNyYcp9JTFn6LNBk/tyxd8e7+epAWBHRH4hFIZgyUZJfbpY7yKsjv0ikH1lxdchb/0bPL21dtXP7366S8vTuq0qgv0e4bqqojSv7w4r2/SJP47errKf6Ds96xOU7bvuPe4bPQBfzov8g0qqqcLdNuOaLF8+eL1uN5rvmJfjanTDHSRVT+/ffniM248uklRzxoMUeio3qMMFVGFludRVaEiIzgQJa7QOtdWMwX6FtVYLvO6iBEeV4cGszWdw0/R40eU3VUrLLi/YIE8Sx7RsvvSov6aJViacaWqqJF1080AyG9V2z9N0jZWChV6rLbTeEPzD1G5UrT9V7OmNcOkgog5g2MSk1pdn6Q9xD+DdfE4z9OuKfIbRZmWilhNoOWX7GsVdzWxGkFXyVo3I7+9HtSXUqmdPm7yonqHNmn+tEaEitaajUdx0GjytvCft0mKfFXadoT6NCPAS4GJLdEsymayffGcFAjPwT+TjS+igXUJHt+5wXZTkann56+G86PhpagsH/JiOXlDX4tU3UaIRj5gO+2qiLJynZREV4SYCghnPxJs77aqCwazbOsMi/XlU1mhNVYK6lUvBLlOsepNj5bLApUlKqefHtreZX3zLxRXMw0ujnO8zfHlASIlyT0iGyNfPUF2RKW4huuwfI7ukzu6esCLwcsXFyil5eUq2Yw4c9BM1z3wWZGvL/IUWLs7mOvG+sIdzTWAV1Fxhyov06Hvl6Pd0NY/GA3ytraz2p/l6RIVW2obcwT51ZJq+qW0yO8TPNpGgWvGbKxRt2NeEZfPsk5xr6+i8oev+sQzgWdgjl0S/pMsLtR3M31TdLrxFvk2uWvcYBM3eYFKrL0W2W0+eVNfbm9LxG+HLaf9Y7JOPFEcR1W8ukz+jTzXbyyVRH36ikVIm+IkX29ShFW5jRUWwpHQ7XiOiniFjRrvHRSpVdvvn6QWzWBilOGtms5Y0Vo1nflj2utWYyKiLxXd7tGPwPmOQ1ASgwwE9TLJxl2ztshItfIVi+Rglsnb0pgJv0xjJdDjmgDN6jRNnmEmJXpNvSC/CbO9HO3MxVb+ttsWV5VvvmSnRZEXvqg+o8fqooYdvpoVOyqxmGJZdax7mi1dW63jGPOJa7ss2aZjsEX5IVliwfadIPznHZEKbJbEyPLQoav7Cf8X3Wm2VSEGfZE/tNq6H3aSRQXWbOTEuC4KlMVPnyg62tZY5uAOOChUYi7VFVo6Lmr/qCP6CQ/d5XjiXVLgbUVePL1iER0WNoUS0i1sU+jid6iMi2Sj2f8Ztq1pKik3afT0MY+jNMBAjRr7UuD9pt+uY1G+Q7cR5kk7HWYhZ6jutjRVFcUrx/PAVr2Vr0CEB8GTt8UQzDvoAfcETwLh8ZFI0e3MeEYEUGsPu/K8I9BadgYdm2pEj3HsdAufibfFSzu+ncZU/YTNKY2RHKpl6T75THZYAQr69Rm3sZdDCftjBSi0P1Z1mkFl0fdRLd0QmK+GI2FreG343+UPWZpHSyc1vUyiVx2Cg1pW6JGWRu/rpG+1+W1/gNjhYs7UncMWRFST+ZS7tjo1Glqp0mC1Jv5i8rEQNaIJ0AjT0OkjHpPGfxKmpUX5GT34x+XQIIiE2dY6n5RvlkTSJo1cG6noIEbqQQfK2zovkrygcF6niXglnd9lSloN5KzVeDFVh3sTDe4qn2VoF2iTPgUZn1E7s4zp5GTyJo7jePI29FFZgbZgx/lSFUMfKFiannE6LR0AaYhNUSTD0azjZhqjkXRovkNn3InlpyirozR9sl6fpdujYVMCn9gG3h7xZ7jmGyrTAbEk143oegwMDmQEo9rejQH9IutG3XK3bVg8B+NGI6Yqz8ovv0zi8m5cwzqHkmHjmlDovFStEBON8DwvPKOIDKLpp+q7Prx+opabY2mXUGPA39AcGeBVlcoCrjXR6cF5RINwPqFqlbu4o9r65asRooPWUjEoQ6jtRq4+xmm9RMvmohsqyNq4WE4fpdK3Sxa44knd5NtAFyHaNi+xRUHu7zbkn2O07ShpB4gEtBc8fe3KT0mWrOs1PaA8WjcmR2vlojhZR1gLnRf4V3vD/W8vX1ySc1PMTVrM0eNEmBtsF6jEtKYhw4FocYbBzQ6ina7VmCvTIl/WcXVcZ0ssSViwsXFcJDd1hZp4bAf9ehJVUZrfvdKhPmhceVsD0XwZrSd6METforT2xrYosUDiXRKKK4ewOOmebKAbuCPTseQ1W3/YpFlUE/ZtNnW9tnJCQyFF9yCr8rZaYvlKREPsQMi6ADQ/LHjbGIdcTDt8bUgjE5noHMWqNUNDuD1XNPrfZLEOdm6IWbBXFJYbKWBLnizR1ape32SM98EV2Tc88Tep98xR5VCu/GOiw4W9BXUIexxMaryo7OoBu1JnWOZ496rLEmm3qLctmA34mqujGOQYVL9+c/C2UUJ2wzAagHnXVZ22NTT8zYuDUaFQjw2JSHP+eQkK3Ob7Iq83uCNh7It2EVhky+Q+WbqdFE2zlNuedc2+thtv/MO0d7RcJ9lJvl4zwTZTXnemXIvWeEn233LiyXn4kn3IyfV8f1uxwxPOYPiEqggrDeK7n/5KLmlsTsYh7V0llfLeQpiRHaVp/tB7dNF9gh68Td6jzabI79HyAmvj7O6yXvvN9Oe8CoyxQ3eFF4yUG7R3H8MhbUM+rvKj2HvnQK/6k87RtdF7ii9/1NMLXZTVt1FMTJACr6PVZ7yJGrTGZM2+xyw2eSOL8n1yW51EhfcWrMMTwlq5QH/USYG+VCtM8WY58eaUFudg/Ki0daAQpqOa2MZVEhPb6Gi55PrgPaZFOUTkh4nu9k5Hl6WNgHcIvceIZ6URuC+3Ak5Hj1aL5vRxkxTUUH8XPfE4zVAcxVVyT1GEYPsPUXkZkVwfoWZ1jM3yPhPuDQmHOLorEGINR4/wgAHZFXqc3hJdlBeI3Dtm8gm5a48W0clTnKKmU746jsV4jook95a+Hidd+yliT1lZlOTkOVgqzjM8/d1Rtrd1t1wmRPKitMN4siIuDXNHtf2Nhejx9BHva7xlYVGelt7kxL05wRusu7zwTlqNTRy8KVpk91g0MbouysDTbKzy+EewQwiyXaMYj+6jBNdNUgarp98Y7KnzgpVkAUf+MX9oRk1WmgDpyfEGIbl9og6Bs7zo+niM8O7L8+gqin/kZDtNMjx4nzOT3SDBuGhoiXcg/abX26Kgm/42DiXMJDUYm/iTMBgpAdCyRZYos30FMlaxLJAOHtdPx3VVMUk83GWLQH9PylWalFUYpK3wpwgzL9brI/+Nc14ybJ1TdEnsf6LFIgm+An1Jl9M20G5MTpjw3eBtXG4wnii1HIg5TpqWqDtlc7hFweI6zZYemDp31mlGTumC8FertkaY0cRM0Sq2WdvEm4+rBDUy6a3w8RqKyqo/jDzJ1zdJ1p7LBWRC3OcuuKE82mzSxN9i/o6Su9V0ojjexwRH/z1ZToj9w7S06VcaX33SI/JTJuFOLMJFXSCaltR7/d2dkAtxgMk9Kp5IZUvvCZv9y7LqcVSidvUJsu3u8X1CUVkXqMlsJjUng+Sh6tsMG4ndoyU/xqjNSEvDIOiJlsYDFca93oaJoIJEXoRyfIyQEmqExnm5yht/SlTYezrkUUvNktgvkWD0S1d63UJf5YOvfAh/kUMJMUkKUNsQJFYbqEJ3rseAQtTOqFwWsDMGcgwwGqJ+DGPE3EOmtLFgkhAryxG1Di+6IZYPqHOLKUKneBDZAAQ4x46zh4jKvrOAiv5DYLIxgLCO4zhPKB7lEFoYRe85CFnHeTDHPveH4PIuNyCKHo8BZB3moBz7S3egt0mTII+NAJb3H66iGI+6gmx8mlqO472K7pSDux7BifqUKZaqUxbGUZt+i4oEG3XQPlLZfUU9xQQZ1JLNkklVx6niUVsN3GK0tkN0Htc43a+CB8eAIhOy5VIuHAHZsiF/NqzoLA8qdncMIe0wB2bbZdaHA3S3L4Y4QygUeEGEgGbfPLthf9PFIetza9T2vw5xzfK2NFG+E+U07WYmRKTG15LslQLdX5LfiAru1Oqast2VW5/8ti4zlyRGjZfMoWYbXVWe5PWG3NH2f4JPxOTxKJnZzND4SCplvjw6YCLEs40D0W3jr3LNVkzceLMVVHv5AU5YlpTAtosT4GKYwiOhH4PgvDD2SLSoWs6X59Xq24RrAOOAAOVDAaG9Lhh1+26PG0YdisNSLG9LsxRPdOGG3H8J1LLOgT/fXYnjvMJ8Ouu1Hs3jTGFWpk6MQl3oOVyYCfQeN7nRFyoAsPW0WZ5UkZtU/m8ujq7+lFd5kxGEx+0ckdPi+TIOHvPYdxhEAF1E2d0MLzQGvhsXPn5hh28O7dop+LNMYSCQiqYH/Jagh0/qTZWjNg59+OiwZdEfQAL7G2MvbKOmwd4PJ1Ud0NBjvkyw5AUAv+wAXQ8cnwlp6x8Md4X6akg01QsZBg8A/XWau/goN3gx4+dAlx+fzTsWoU9uRRUCQ2hPbjl946ZORjj9c44c1Mtc+cxk24r5bCOnpdRZGiTHaDKhsZTgsDEXur6GSBME+AWDnK4x+A5SrJSbII9BAGS/qFNklAU6jF/lcoMmP7U6TtKURD126Z09bz92l1LDoOuyD9MNxCjxtZXHZkDTa3iVmyLM5DGNsknE52y4mw29Fyh0y62/wnKeTh+xomJfFJ1hjzzEP0x3giTdHSvPm9zWnlEUrsdD3iyiw2ojb0uzEEzzkPdJnubFB/RIE1LP3vpevO1tu88KcPTKYTpIjbytQDutUAdBi/IMRWQiOgNhi35t6Uo1cKjBHYABWH4JoP8h2RSJgBNs4VxuLej6G2QXdy5eI/DXDSy2g36YXD+w5P5T6IgxtxretxlXUl+6Gf0hkUN5hQn0h8/NIZP+B9ElgZTIQXvscnzXrBE3DCccQpR2K0TpEFS0h0FFhzib/YuzcTrlGi/u4hkXVC4YCiBQiN1Gc5XWf5/R4AlmI7wgmSfa9mnUTati6JOAWfVyDltep71DubAJ4WZJ1NqkU/cjygeUbm7r9L9QSZOfBkH2OXfB5WPD295Dl9jtktvqlh1tsLS0yFBZYlnVpV4Q6lxz8iwZkbSG+qq9vJrX2UEn1S7qp7M5+l+H/Ym8rY5G7+ukb7X57ZBdWhM0FugVYM3r7WFaMXgOPGxDZ3mxjvzjfVpsl1GqTjEVJLv+nK/aBEz+fHR7m6RJgG0qzQnsvZcJZFcvyiYc5CgePRfpfHPAJLYkDB99jMpqsTlaLvFuZ4brVCF3DaTrH/O7JHO8Mk3qd5mlpSjcrwo3JJUYDJ3qv2bAmIh1oVSMWRdBbA2dNuSnn3tVN3lYoK9jEHmHOTjbXvN7DlW3W5jrwaIR+83DCCaXFND2/gIbuaQhNwcKUHsEISf2GMyW1qePxKCJ0qO6WhGTpkl9dIHixjcIjEBVA5wGowrCnJjVsp0gbAbU6/O8lNyQ6YvBgYilQq8BEKcuXuWbJFb0kZbLOzkuhnvJwdh2kzqY4B7SIrBz4xKhX1yxbZcuEN5jZCTCCskmeAQCdhGGELoqAbPv8kNULM/zJKvKD0lZyQ79AThJ7+VwwBgUwLYj6SIQjdYaARhQfxyMXAHygNZ5p5h0oPJckjwUSH4pkEB7OaTXxr4nqP2+Hm808uxVi+Cwp5e3dZYUZTVLVDgxX2dpSLfpD9MKZrFNlD3N0A4ckq7ZkWHuIOlb75Mstj3TO2EeRJnwlVsqmm/maujt5A39M9kQWylKNSmoAj3cu8ozpH3cMtALyN2rftMzu9d+W76TaWQIXAq7hbaHGRZArkhYtPly67WaFVJl5zhIsYsjAGlHx1B+KZ86cnnE6rc4DsuzvC3Ngmnq5XKJxyhbx4e3B5UiC5VP/+oh/4gIERdlrlG0bwMopKtVgZBpgz8HaBArWlQkMdeY51PQ3yJvF2+4yJBgIR2+sS+KfSx/E62/AwfupViY66Fyo964dCR6aGB3pa1ivb9lFwLJsEZrBbA6ggBi50Eor23hmB4OK1CHgJu6wzqkUEvPPjQ1nF5a3GWY1s37tt4ReaePcVovsayPrhGrTr7CnLO1kkqbJyJATxMe7c+cDZQsoyfnUK+8cWyhkd3U1dh297CXR5gO2krLvN7P727H+L65KdB9Emm0XpgD7l0w65y27Y7miSD8SiPGKxytP/ANEJXW4jrIvELmW1J535czCC4Jc09gQRJ3YtZ7Vo8yaqMJwgdAyM7SpJESTmJ9nOZ3ffyatUiT2uUrBsduxLi3nZklbpwMnjjIw5mQHUaQpRhaXw+AAztB5QIrgUDebNR0xYOHCILDWiBv62OU3dXRnXfMZqi7G7a5rfJliAM4p0t4DbN7b1u7la3FdwI8/mprjOdVeKTNI3UTK77tPLBzuBa7ixcsJzrgZNYp2IvitBryvlzlkmna1U43S7tJsQ5Q4z6OCsHlegzhtVYPnbBeq2mSqeTfFPhVh+ewZMvb2o6vpZuZlurKLd8U7ePaf9ToEs1xlHmWRneLNR4sSdmtvasR4qJo9XyfkZjA29XkpWsm4wKVVLXByhyCBDWmElBQ72poryM7CLW3VgVwHjSsXtcFSG5Laa1RIRM9DNq1rkshOdEmaVES6TpP67skE5SI9lJiXseQ8gliOgVSCrxJZaZCnJTCueRueKg0EgzK3fC2jXrnK4bfo7IdYLjAl/Px7f85EwcocyPo8w04MeBn9FC6u3RJ7VcMit3gsc6jO0vWkTndx4TS5G5JOPdxhxFkdGZirwfAga2hcoGJQSBvlm264s6vpP7BUJK3tefe48tVXoQQR8tmz6jqmrvVYDu67Thp/4w+953yqB6c5kFG5rZl6ZdEaJsiFIKr6xjC9gIOsz7DnhYnK4D3qyhNBbedU56m3/KKbslimnjI2hogKMpXPKLdsGFJr46y8sEhmEe+telxwvsajhDXLDyzmZGDiTsYBazfvpnpmse8NygOhqCaDyeOIZ3ILGqua365xfw3rLA79JxfTlSERAxbEWlgxqLHFIHixpZbp1HkxFWSONFfTwgpEg10irOe8NIQB90gbyvUJnE7+qFJ2NZautPfVQ+2SVvlD1+yD9iQOKfWoud+iWy+3tP0Olc50ZS7c7VxO7vRyS5UKncBVFtDO4BRAajv3S3/QcfK9bzhQgTpc2ih8rpMQFAHuUlAEB30urwt3anmL79M4jwrEAp2Qz5U6lOMqNVoBA83BRrVGiTZaZeltGF+z6XDJEWp6ewaaxlUrJOy1OQg5KHk2RTVkKImUoP7GZccbpdr2CiuC4xVIMBBPznrp0ntxe3oxuFJwMlb9kiFGkyIBTvHTOadhFiVltTL2lAhPki3gtcDXWKcJwVeN8vN6G6TGRKEdU22hsmMLX8hvEy5b56mjuIYleU8DZ6TC9B402qk5kO/ZK68GDpRfmZeydpldXbStX0yZQfFSuuWQ0LngwqVt0UzQXs/0BxIDweKjLLdN53v5fsI27h4bp0jndccijTqph0cEphrusgCQp1kMqHLu8kA+euztjNeCo3iOGg0eVuUSjuj0chsEQS+iNrkhKpYFMNbSPZno+2bCT7d/5agB08UJCG2U0IECZ5QU9zha/Su3RHC81gQ7B6kAHWt5NEKqzVB18EWCOwd/anqWgPgvwR4av+D4pe3RQn0vsjrzcSnzBPd1ZozuPHz8MyMF6GCLA5EgwbZhzzHJeLP9abzIMNybX7NAnHanCmDtTkL4K/N2054qXSK46DXFUrm2WvjZynjc0XLNLLkry7Ah9IgfeKkLtq5tdYUzVNrzT8HJSFvixJopmdXaFverzrTAe+Kn2L8aqe3s2L8Kpvlg0qUvCTTbB2sI2GwnUdP5FZKUGRNGu0pT5IkHHNSFwXK4qdZnl/qGruIhrDRdyhO1lH68sV5gX9RLfbyzd9evrgkGRR+f/k3Z1kYB2L5ztO3qJpTrVzWNxVWiekii9MrgtaIWH+1HtaoMZK2fo7GrkhjeG7oK9pzjXDU6DwjbbXOPCNsG5t1ZLghC1G2b2ykHPEqkhATIUrPEJqapvKWpyawvOWpqd3inz61J+WhyZm0k/VpW6FqZbImsOFdZ0u0PFpPOhL2FeDvqECY7QKkV1+h+EdeV0cV5qObukJzbqWFxsv/XM/wumlreoS65nR0e5ukCZbJYPuDzeQ0oPeDyKaGvGxcIKy/TjBvjW0nZ5bCmNio+ukmknR5FjNcoM10iQyi8gdayqZk0hGe3N9P/5Isaej0cZMU1JHwKc+q1cxt/heKpqcnK1/NI0fv0E3ifW2FQXUU03XzQ54uZ+APseGZGJNp+DjKfsyyAebanEXFsG0uTuZsrn9faL4mFzfRDMZFu5pSA7BPhTe13Nd4Q1C0aVyviigro5j8HEyD2ZueRWRkjV+gsk6nJ/tJtCGO9HkJLjY602gv65veRp93yOd1Ea+iEs3pwD+PEtdL3/xrqlPPS9sc2Z9jhbOpm9OS2b3G71CKqmf18pZiJ3yByNEbc7fY6NjiQ1R+Rg+tF+dzTu7ZxKNXCf2POsenNOCRJxWjax5wOPaEyoVICRDINiZPGTfYtACFDI5LJF1zDhTsfENfS7xL/5CUVS55MBECvG4PcYfOyqGE42QFqO2bIO+TW7qR0w4CArz+WqLl96RaCYPRQwuDMqhiOzhai+R8U/A3TQkn9J8rEjrLlzv17HOTIkrWM1Is6dlQBPeMKbft2QVaIrRGS1aJnUrfAmWhtEyhBRYGo69h/fw67rs86WBXKpJ9XAI+FM8Uu/RqY6SNBUhe23EAEqXHQ3mFr8GK0OG15QZNCSrMQ8CKvK3+GNTTw9toNV838TTvbA0nMaYr7FBDs8h2HwVZUUPb2gutTpnCQDDq+FhzOUk6bC24BqJB2A5iLm+ro5evhI5W0BDIRi/CBD9YnEihdNQ0t3aHGhpDt/soyKUa2lahcIbWHEa70YBgm85L4fgrmYNikbfV+c0YrQA992GmnUIEyk0cqrAou87SbGlRAG9Uh/AkrzczObUvMDE2CXEQzeG161ubJ8HNJcrIPnaOkTVNzTOsT3izxeRPnaydRTlwB3UfbtvfOpEDyn5JNnI7CQu46WhgTaocz7WsjjgiCah0VZbBey3JTA+9bqAc3kIy2YxjInnfDgli5583j7/5ovlHHbWj99p0ZEl1XiTx5CG8fUOTR+zOMZpZRjJ1hHHn22hsxKlJNm5tatptJcx13vDWd/lDlubRMsBzVouyQxbMjP+IBSQrUYfXcgtEtPV3lNytLLYumrt0dbZMETazoukDGRoFf0Lz5IXib6mhdFSWeUyikpedsQKfegS0kmSWn86q8vfvWhw0gsc1wEGksUHazKqmWz0U1LG2UNG1DsLfrvxM34LwsisJioNdKW8riDX4mXmzY77sit1VzHwI9tjJPa1GE5gd7IPiBhz8OwkbcLDulXUawHeQQQXfBbpq3xA8BI7jKI2yGHluy8rJb5nN5UCbSDMoA9mgaBcorE0FJ6gNJXD4E6fAAUEmowl43NSF8bg8qdFG1PQxRAf9J28riA1yVUTxD0zxmQK96SXdsLs7yjPINXz8HUqTe1Q8OVaf3fYxDq7jhV4Se2cb8icPBGUhrgcdIHZwBCANAhxDeaV6YlGGUEoHp7teIimdgkTSQYfo8/jLtcIQUg5k8gpLi+N+BbNjdoH+IG9GujsIRmgOcqCQgxAJxYIJQajtUpjDpwsUlXl2lhcNN83vBmn5F1G3d5ADAr8OqC9PmU1tewN2+rwxl1V0e/s5nyNFzdFynWT6q71//SnIgyAj3RYmr9wOXbFz3VEzNJHspSEIYN8JgnluNnNqnJ3gRcgvnoLHdFjZVPIfYGU7j4rWvrE8IWzO8xwqslMcIsIymAcyTFjIdlIsYZFEBeYhEhYxmccyjNGxD6p4YG7J7mKspa5ZeHaPIQUDdhpy2KB3jIWGoLVECqTvt7NHVnW+K7QCHPPKYPRdDnLo2yK53GBZ6+6497rA5UQqqqI0v3ulxHtYHifX5zDhv2zGiUAc1wqSvOosSfHAArzZjvn64UvWDvucOVpyT4LlnIDfScqVrA6JvFEF8TFko1rWvlkFnziMVo3OlAQqLJZ0UaLy2z0oh+qsOFVoD3rTVuFN/HjUtGE2U2gwmEySd923Ler8+U4AreGnF2ErT9muBA9jANpXF21DBxwTKL/gau+g8P4MqknFvZLz4vlFTjhr9hBbn53aVXTnvy3DSA6i5Sxafw30SJlurVauytfDVJbAcssWy9bREUwI3vwWFUmUDenaT/L1TZJFnhaxHvmBl+VtXf6oA7Cy5u59lagOQcM08inK6tsoJpNYnOPZ1kadhWnW0jetud1EOxIK2/dkGQ7Zh7DXpqISUcrZRiMboiU/xqiNDnGOyjK5y7AUJZSPFkvVGfSbMMfCXZQg8e1bnjl1JxfkEqztTbiSXsnzdqbt5fESVpnxjzDHPtTX+aWuvtxSpM15rzVR5UnVGOZQrfaKxe96jEOwBEyryhxrxvVDnpwYtC13rBrUchmsq4uVlWLXwY5xWI2Yreoy7FH9EGdNfGPhTcODPTi5Qm/RBHO0XqHHCn9ab6ZPj09S8/xRJ4X/ZXVGXrKqyNMQcSFbOr3iRchCvdrqVGdFyvOcy3hAf5BpHdsRqp2tBkPlEdM8j0q3AFjjWqp5tTRQ1pc5FuyQhPA8BFxMDquIwSpylCaRZu8UQFNvycU9WeoXX0upTfMa2lyiaA/cLm9LQjf/OJo9EiJNdGSe5sXlH3VUoPLi7mbyQTW+peW/sCSwwf3BY0eb66YzNLQoMSa8kKM4QBIlDxuSw0RVQwiz9mOS/SCBvtvMYWdrbe2OnWVod5oZa04r0lWCitYV7rz89DgOa41Ks4WJ/wxxy6ILiiZM5+i69vUyBL0YoA09JwMFpb5n3esx6CDXMIQguRKwkL7ToQlgDy8UKnqo2qebRwoRRnS5OU++lM0/B30hb2umWAWu1a+F6hRnokYvy/Q0I/DeBtIlijGvhBmE5lg1L5V3agMdMn7M7/L+UHNrDkh+7NU6Pc6XT3O8tZpnFZaX7jzqM6oe8uLHHMEBWDE9URE+qYsCZfGTv2ed4jx9jFdRdofoY6GuqBXrh7QROOCOjPBaWYsJr9MBi/Gr2hr2C6Q4M/qRceCSIY2g1GMZg3otpEO3HB52I09r58XTqw7JYUWVt6VZUX+ZZG3rJkbzcMkvk2STMM8W/jfX5eRjThD4k9Vkw3KWF2s86czdtKna+5isE8xiV3ljrXoH2xABK1e75ADaoWvP/BipbjvNlnhmg1tYdtubTxG9meu5y2mxHFSzvK2G/L4WVoNlOxsnB7+MuY9uhdaIuv1uUpctN61fvhqhOXCjvC1KqMn3GIH41MDPr2jlbZht6pTcfxzFPxYZjdK8rG+YI1xnf7UE40Ekgk7wZC7wUBl2tpGJWsJ6YNYTHaywQ9VWCOmNljUG+KY1oMbjCOK3Zq+bXKE1Nqe9gosgdAdFIm9rO9bZtwQ94K6u5m/Zdcdm7j7C03CXF08BeJlHdeDjAx/Pxsetcg/AxhymAxcfuHg2LqZTkcReYcv5ep1nr3hMBzaWtxXGfYRH9L7I6838bIlb3oIUalwDE0VhT+kqOIrTCxTn5NVqe48tCR3A6F71SA4SN7XEbdNhq4yBsw7yXSyHQzD/28PasLWeR6VhazCEsNWWgHntsL8WPlKYv+rrHwTwOQvgZVqrznEnajVU/oSPUXZXR3eTrWS3t0mauO2D+rrl8PMgSvK2jpbLApXebzq8QykKcMfDkT3l70E3o4OXkY49rnsoZgXhC8XFQ4Dw88yiqMT8170E5xENNcJ0YH15WxrNb3qny1bzP5UVWmM6PFAjYe7mLwjppo6Lms7F0DL3u2SNstLtIFSQlB7ZQVgOwvKchOUsL+q1Z9gAxVG+ElAdZEXBtQyd3tdJ33Lze0sH/nQGvRPa5Jsk3sfQA4F/waADOZRg/ilAPdNJJfeYFv2D5K4CO8ZzkFaFtIaI7iE8FEpSr/JQmDB//gvFylcCpwn8J4mv5veukxxYkfcedFG2m9njp6O6WuVFQIQX2HLYJEzKBVecE+lQlo0liQJYtXI9hmfzAUjBgGv/cljbmK1BdEx6z0JL+z4A6XrOQHotAdQJgj99zF2C73GtO/I2N4PloP3lbTFkIk3uTHim9n3XiXToRIqFI7PkXKeHuRbg2cMdKRhwwiOHDfqSH9sQpFSgcmVvg6uShowh1AmBP6gUeVtbdlpoPDVvp2nVKYeBRZznehMldy4OjHZ1Ll91OA6s68w7U0XFaLcpEzVM8jjs2xIb8rKuhdudCpFHfGovhhyqgzQ6S+NEmvw4jk/xlKXtsSZSXUM27YKv/AdKKBNI3ucJsmgmIY7JeyD+iQqDXum/RNmS3P2J0vTJF9dRVUXximx23pwl1hnZhtpvvWr/rK1trCs/o4fyIyKi7nn00qtMGONBc8rbginmfRZDRVKlDn+ZJJVJGH0S1OyYMpD5ZIXiH3ntlf68edLmlYDqIDQKodlKJORevpFxuUo2GwLfXrcKhXdRXkWPp4+IoYYrKoyou9IY7J2r3XgRJJS9Jff08VpD8UIFDHsN6LDBGWhaR3iJwrii1+sTcCsBlfAhNb9uqeYpNvGL7xOp9kM++4k0oLnmml9n8Yca1srOSWddkAtshK/PoyfCAh+SkoR4uistCcKD1pK3xZPMV2nRmdjRYDN+rKCcSXjoWqw8yJlpHUHOjCsGlbNwAnaQLAUTP8UpGj+z7CgNBNE5KpJ86R+lVkUpxVf6Ibqs8LQTkQzhlwjlfA10n2mRJVUSpbusydguGmmx63ENueoaAWr11RjaNjhDuv7PrZb5LZu1PndSyzQRcfJv+hYRJYtTpAfFQYFfCQgP6lneVpjryG6XagE8NCn19vJ59O1vq+m5cntIlVE3k6D2ESTregAf9I0cSlCkClBPUy+l0tClNHROSTVGdFAk8rbOuxyhb/xUQI/n7XactOauxiIvSywWqT+X8agOfLarfGZh1fgEwB8C3zVr5d1HdI9S7ztGq7yo+utqc8fSnWHwcI1rNnObPlHExAFR0lsEGnHEVFA//xWGEJjJUVGgYo62ZnyqQ3fDD/1RN08V+V3soG96fYhKdQZIF9K5XV2QXVlQXlUId0XhErWP2jjkisxuk7u6aHasLZ6Dype3tZ140meYapGJrXB5kSR6fMVgOHCsM8fua6JcmkbBhXNIvbL558A2CtmnN7ACXWbRefjjtF6iRXaZ4Caj3t/mHgF2HpUluTmGC6oggQYdwslpcZVUytfwwrQySxD/J1RF7Q0+lbkfrrF3iAkVn6G9eeYq8L2DC5RhlXxUfk+WzSGLF7YGyz/zbHo6NE19L6JNa/8LfTepj7faD6N58xv5cU4WuAD6pUjygmplr/NkMrCrSJUV1WUu5GtyvWZW5NPHTV5UH6pqc1VEWblOSrKSEoCXLxblWRrdlX1r9hnnoirq3v99JW8p2IqO6YEnNn3C9GNnZEz+T2h9g4puwUzWmxSd5yVmTGqv//7yJ2HCRjU+4Y4kG9xd8i4pGeCo8htxEhpyq6aA3nfqb+QQMkR3iJxuNErRexr6+zTqlrY4DcdpfmM6AeTCEnXEoIxszGHC85WahydaWOCCkzhJR2WZxwklzEhUsPTeJiSndrxCy5pIbvmDG9pptnzReAvkVQbHQjNzxCExgsYKgHIapi6W4t9f/l/CAI2a6Y/lh2ZGPedaEcnYkppEKGAVXuJZT7JKtJSTLMZSkRp3iMNgaG2Tyerb4kveoQ1ZJrPKmPgmnWjrEV7GNeEu9S1zWwMd7X57zXCZCfPhhtK8CZvo2ETNE2IFOeMNsPa8B7QDcJ6Swd/w0/Hbl6yJRnpBQptIDPNJVMbRUlxRsQAvg3GtfCiz8ax81ky60FbZGqv+o0Y1WnJrzfWZilnlVSB2BaFteFbRHMC17/KHLM1JfrVxCz+9erV9ntUPZQau1U+fSSfOdoRpr9kL6CYsO6qgYVhHNh03AalWts87p1l1I5mXR8H5MukCn5lg25zKCBvLYnZqjyXMfMqWbRVgZ4XE7BQ3q0e0NdULzKlJX5hqW2PvNrTmPKGv6163/0p5GgaHGHkMacPBkjYAtpUg3z7LqocwA5+q58nIrG2q7AxjtoF7pkzDv3o9BWNyL2WLbew+Y46HsAXGHM+T4X6re+B+K4zZnadrdSUPCDHjcLxvzoYCXnPNSPdQQVxOsk7MwEIyuu6DVnuXlI0ZfLTBk0JOrNrRJKiUMpKqEsRUHbwNUynbABjMkHG9SNNKug1huirTkaVvwVztB6BJ96NNAEOsTgOyQLVsKBN0vTLtH+QSEuH0k+ik1oxIN4OOMyKRST+6+tu2464x3yT3qHiiMbAaC2sErLDhHIy3MWqI19h+TrZ8qnoznxUG0tmIq5h62+as3hzolyQNCwgVFBzmYp9Jm7FYRndmpyAdxXxcKp0vky6wucl2gksNN7I8/MQ8usd7WdkYtsCge7ibJel1byO6KSq0O1oIGGJMFs6GK0H829jdqjoyA1+p6LwPu9yWqUfDGPGERi1JKyq0oCvPaZsE+E/V1s7oRe2I5lOQ2vk06Qpbb6c423BFh+rMxM97vLqrxrElBt7DVZ7mEU6yu0+oWuXL6wuEsSXkzscJ8Q0onY/6qhATj2vZ8K9Be9A+ihYG9EZeVtiow9N2jxkFXXfopZ2GoEG6sIBWZAFbMKfE9gVaOYIZRFk5R0ZbyabK1oS4zQqgZUYODmLDPsGAOQPyWM1ZL5xZLunDDMwjoek+sc1Y9eimeSwtYVlojBsKmVdpyfDsBPZnRqYCaW3S/qji9g7Eu/wMw0tOMg4QQcFD8T5LhMWhuIgZYCwzpnUZ+3GSkksLXQPabo7hg1OBQ29OClG6HKjRXj4c0n3o+stXUNGjv9loTxahGWgRk5B9+waUbhQzaCzdfBmth0zSoa3oqyZ7Y3PvE28vaM+H5IgyHlLWgvgVqmDDtOoGAc4d8jvuGucaDWUG9jWaQ5N+sDlUt8LDx2l+R27C6vlWgIR4tQOy4U8R8V7xpLT7M/ChdE72hvfad+Ove8ZRcQkPLOPA/jF6OyYU0AN8KGPw3eBD2QhmYkXZ/Jg039XZtkf+At0n6OEDSje3dZoRm3dUoHPOa6or/PRCTQdnva51ud++Hd3OcbXdwOZz5BvOs4VPv6m4NfYnL5AuKrTWGwICJMTUHZANE4uI98oQkHZ/Bq6UzsleGAKk993a0TOOikt4YBkHOhgCIHqAD2UMvht8KBvBTKwomx+T5rs62zME8jTVa8ERFLis56nVrfExwr3SfGDX51iLoTnYC41Hen6UlQ8kCiAXclqOmYKBk/FZA2LLbSxiyDIEGHg3eA3o+EzcBsyFkYmH4bfKa9/yirqq4rxYXjMco2IPSR0ZD7Lgtpwoa0rClTC77wZvaoYyE59q5s6UZ5ta2+NcVLTpBtuRdC5/MlD52Zi6Gsi/XA0r/lW3pjgZIgATnCCaEQkGD35+uCVinD6SBMxRelRXK8KLTaZEboIUOdgMasMJ2eQV7ZKzmXRgrw4drYY0g5a0mmOT/mz9OPIsL+o1dbtqGVwEhbi5h7JhXQC1FZ8GCciRd2IGzpITd3/YiGbsN+SjMayUkdo3BCw5iUO+JVaCezEXL8EE3h9muqb/b173VE42AyhlI2sOYpEC7MP0befWTFn/52I8YD5Mmh5q7YISa7jGQMk0Q55CfTWYZcy3o3wHdH1ehTeaD2O+2wHzi+EXvZXEDDi8CcYgl3EfyNc7woKSMcxqw4nzY9I8rbA1VmyepDeMJIaAIVZsX5cyZ0MQsXkAcSD7TdWLGRhJRV2T5sc1t8xR2v3AGCwgF+2nzwPu+2xct5c7hi7P1Ffy9MmHBPemeOqTV2kzlYG1VJnK2Aou+dzgBhWpx3aPS42GMgPTGs2hST+2nqoMHEmj+az4qRHjubi3aQ1gXVBn7yjfjgaxLaYdzZtJJ2iF7S7uNExHzaMcnHR5t4374/HuDwtKej7XCi/OxT4xmy7diQA5AcPtY0ITae9nZbs9TF7yPrmtTiISEIG7vIpKtPyeVKuBg2TsoqkHsWVXxYYrdc3I1CLE/eHuthv2agbeM5wGI04EMWydMUdGRM9COn4Ba6mY0tVqVDcIsKdMCravQ42GMiNPK+fQpB9dnd3i4a+siNkx8qjqbNw8bnV/DFHzwWyLqcH5NOnMqOJ2zdbPeYVM9kgDnNRkJSDWJiuDd39YU9LzuYxVcS52f490gR6w+JznGEHZyY/W966qBLEhAG/DkMrm9spLbzKSGbjVZP72woMPDcTMENDWNFZ74P7HpiE3gfFKekQSVtJbc2oijcFkOTNtrz1yWPdneYE7PoO8wvOw+4tL1+9mz9yxio4vRtAqprP1xcENQLn8JFy9OywIDmFGTgTnyKT9HsF2wwu6jL1mESscdMBgAx6zT9I7L6tZ1pO5bGcZjU15arMDkSsXqKqLjLx6hUxuRsDgsDnAQNpZzmATe2Yzq8Ywi7Wsmqe9sJOHThvqPVmF4Jf2QipAK8skp82eREWzlh3X2TJFyrMZRR3YShmD21kq8qbk54fMGCZbLAx6NosZop0Kk14MtbZoInMj0S4b0hqTM+F+rh/aYWyDX/dyFRFGoQurkFWYnFP3MdZCN4htsOkeRl6Mc+PpOBSE1mZOdE6XuJeMqRzBDFypnKM9YsnLDYqT2/b2/1FVFclNXSFTFlXXVrAsXNGBhTU92EOWNhvRfCxuNsf7wPLwSL5syJ/XEo6Uru72uEADQ4HGythw6A7kYzYSy+2Lisdw57BY3HnDyLMIYtjRhUTJ4G66XU3erS05ym4Zyxos+tuXOP+xb33FMuEbd/lr8GxbCrtl+Sq6U+SHE2EVcuMgISPMchMMF4fLeNbi/BYVSZRV/bSc5OubJKOA11hikntUPF0la0VeWzs8CsIpUDjQ1LhD0N1LduCTeWEdOzqfXrCdU5OesfW2Lfyq8Rlu6AxQ7ATH7/H+zmJYuyEae7jTMxjVP2pcgjnja5Z4SQWLZydEY9QhQD5GA9/mYgB1dDc4HppTk56x9XaN911XAAe1H4Chn5OC3x2t/oxUeTcUvafOFMF2GFzpjhPGuG8svw3vm+18WwjBzjjb+MF9i9K6H6HArZasqURmISUUTwBRUfdHLjY6qd1Z6TEa8PYkyYg/LKSKR7Ft4bLaGpja/y5etJ2y5Ldsrj8Lm/z6MlpvUvQuf8jSPJJnNoDBA3MXhxxyG3b9nJy34L7MyF0wpY1OA0Y1t8ZhVwkq8KhjNHpxRMphMDjEYT2kDY9J0Fs+f0L5bNtrtHooM7CoeqpMOsDW2wEG1TkiBMgJ2HIf3QvS3s/KhPvrQmDC8Zufhh4xWT3Feiy/9GCwMkvb2ydmNRzLfEu8bhZNejKquEPsbM/I87GwC/OGtC3lXdkK7z0H9dnv1M+StCLZ3fXX1CxwGPEkV92LRRVdcbnWtoO6Vz/CbciCngXMdfJ2r8tVeUEMo2QdFU+nj/Eqyu7QRVShk7rATcRP8ihiXU0wZphUsgoO1rYCbszavk+jnY37NANnGs+CmbaWotkNBqV/2HHmqEp4lhyj3zIvgp2ZmwlBgltw36j+1tjuOIp/LDLcl/jHZX1TxkXSxNpqbxPrKkIsKKljw5TaZvfqirHpaGbgbdP5tHFm7RxX6zZhmnoz8/Q++hQMx7JFht7D3d1RnALvtku5GAaHmLeHtGFXCXrLg4Pts6p6HDNwqHqe9ubU4Oj2NknxF3StSw4kQII82QFZ8aSAefZH/KRdmIOTZIQ1aXvb2c/o65V2xqe8ivSBUtfFWdHUlh763ralqCf+XtiIeLz3WGI+YdaP7tD1WZGvtXynqAO7RFlwOweovKH52c6gN7M4J7XEN+kFW29XmO8qt2W9ocakjMc0s3W2E/syP9OJZDfpw1Bri1uIKrnHk/Mxv7tmfpPJVOwjpHXgzUQPYredkLcCGXFc53dwW6Edzyx7C+3cGdmG46o7wb5aVQkBT8Sw++l2VI1gZt7cS9PxZIXiH3nNh9sLn+VZcg0RgFlzwbpWOXRNW4eYWhjjznG37fBm4Hjb+TaSAr7yFtOXx3WBSXF3Hj3RtP6LLCF4dQ+pKGrByczHFezymasaM39JJIhda9SZGbjSaAaM+sHU2xku7N7MEdjGlEdkCEx40+mxKsPmAW7Vicb2lbLt6LbA/rr5NukSX3dr0vAxj6M0+Tdadn28/hhldzXxBsgYUF4F4ngB2obXFU0B3N33fOe4Wj+OGfhYP28mnejqbJFjDbZ2ui2d5VbOdQu3E7eetrR725Nd2ym9UYrrVLgGKroou3VUVF9u/oXiihShRzzfMe1YlGV5RbH8+rVEJ2lB2KD8/WVV1KLKJKgvUdUhfcriTxFNgF6+fNGUsoF3QzHAimNUp4+bvKgwMdKcrh8QPh7GECmekdskRXKMLYAW3WW8Qss6RVdR+QMcL1OuRcZe/YWQje9Pa5GhGi1P11GS4l1RFK9kJAQBtei7C6QQxuEernkfNT3TzyvtfxzntYRRmHItstZg+YSqVQ6OcASgR6eJJAdb0N4gsG3UqBVTtApketZJSuk0dWVaJCdRhe7yIgFluC180o8miau6AHG0RaYEUWEaQRiTh7yVlRRIJrYAmB41k/wRxDnKu2k2cOVEjGEM5qOB/xRl9W1EyaUSDhZMi1qH0wpZt36DzNfbSRokbawJSLg+PEfXEcICMimgZXqid6+HydXdGEKPENsO5NbhPbaiwJ6NAIyJTY0UWBg4EC3K4zS/O89LEFdXpkXS7RIgJMM2TYeEbFjQZUX2yReopCYkiBCA0yL/jB5KmervyvQymafpUVY+SESxLzVCJENhzATE7lZxQBNeqesJKtZJWWL7tok5BHvFwRhYlnhZzqL0qK5WxEhv8m3LG1DBaxujcUcyDu4LzdBc5ZskluKhpSaIIhkGs168L/J6I+0FLdUiok5XCEfrwTZceb+S8IbGAwZrVgBQr2bfJ7d4ISyWWuwAoDl2FUIz+skURl9ohuZzXoGj6wu1aMT322F6ge+8G6x5MstqeLjaEImMYuMntrWjZR5IhYc5er9W27fxK2Rw//jX4szMM/hBAYWhJnvKQjcGxcMF4HiUL1Q4NWbejCnx6EsHUkrRJwbMECkSAisaUOZ1dmvYojXTJkxwWyMF8wJadL6NLdA01meegTAzWYC0NnQO7ybbq6tac4nepZRsDPrrqSadUHnzmHI9YVZ4k0ppegObcCMAvQkPX2kCLXrZtTSLLeMVWm/SCGZICM7UgfGkQszDGOsYBU4ORG9YoAxhjlVKJA+j3/p2t33Ave9wEUuD5muhQNMX6nvT3RgBezNcwdGxC4pKzALfUXK3AlfwEYApuncJNiFKCWfzMGamtk5egDsiWr5jI4NhthtHYWsZpA+Lg1mEiVY0R0WPMdTomjhareyuN1FyBxKvKzOYX0oKpWIZgxjt9z+iCu8rdVMMQ+oHzsdZgRQQw+Js0UqXZlnAn9a4hsIbZNsJSfiKZRsmuM3cQ8x5NtxlIP5A21nCUEuFN38MoZ/CIi9LXDFVoORhDEYPSr+J1ONflcxqaYr0Bkv0qHRuD+V6ekt9LLB7hTnHlRxdXo9PFpkK0DnmGJo/cB5iAOSV+gCDfmTiWapwkm2EuAs1YBBzp6p8yMCYOsaUG06Jr/vjXRndAFjd4MQqcpqNDrW1ZAMwA0RTz4YD1cCD4eszCd0U0PLxyStBtJMdaSsIqGgAICFzqB2Setfjw2cl7cawRgMbVdHQzZhaY6QQu40P3CfitlEIgCnTsZVsWYMlwZQsyLYDUFc1ZQ7EHR9BX/dH0SJFJZDy4cEVINoJJ+UKokmwApSSoQtApc5K0VMJSqWjHA+XQycQlbgcOSLWEFTqbB4VFwkw8jHwoBBNWDNMQQ4B1bTs0p0CXR9tNmmCllc520+RKEp4+ahU1SBiMeE1ClopsQJ0k09BEMoN/GlCtx7aZnxdpZA063FOK3J9w3BskIJkYAWDEUL1AhAORAsZYuA4Qyn48cPKcvWueIAZUsPwO8sGHKFHBpFoHMsVija9Dh22tlL6iLDaYQlVFHQy1PpSzFNrMb5hvb0ggJqPSm8xOJJrHqOBPSJRGQ4gnHwwEDhEIi74T0EeEOMsZueo5XE0opSh5HW0DCCtqmAxczJqGwFIqsQeiK56GQXB7Qaql1U/Qs4jsuOgz+sLVFYFZnq0ZGJORSoa1JIPVl8ZoqkQv6ogp0EL0LIxjNifrGzs63UfowtQEgRUDA2CB+nFheeqyAXinJhCXdo+BW14EPkIOEiIHkwctoIUPKKZiMCFSstJMQbUj2M8td5kGaODfPpKrnPxQ3T37piOAp4IEUrhQBCAQW/EEPyvckaIuACqSInsQ5DjJE2xjuvvECiowoEaDGdcIwB9OIQzEam9KTDcslVQSYDVj4qvoqLTcK/BgFwCYkgPySbAgV7QvYPh+jxANHUF+QCV9SDySW5OKGiobgIgJHPDw5uQ3bUSJfFEIPloBFiISMxFFwVhRFQzEOMkX9OTkOG+DUwPAU49Dh7cW/JApAB9pKR237pcoPsEPXxA6ea2TjOiHUcFil2MrqZ2w6FB4E1Vw2YU2k3WjAO5u7tQStkUgeSjE2AhejG3sxSEElFNLJukwY7fh0tiMD0EOPU4eHBvLgKRAvSRktpFNvM0VTLKGEAhAiwc6CxoLsyppGiEYmLGGK76XTdX9uChsyDqnjOQsuH3Fw81RGBRQW4RiJKOJPiWV9R+oI8VsNcfYXLIwNXjkdTyV7sK3BK6SabAhXrclcpr7lInQEB1DcU4lRVBbhPvhKroqMavWLbasYbb7WmJKIE02JppiWa51ZudSKo7tsqtn1lF+ZCN6sPxgcpbxMpYQZMmJ94u9lePldQFoOTjEoEhurEXohVEApDNQREa5asnCQemGcYYWkqULvRYRxUO3RxkuWbvdktowsJoRsCASqgR6cnAIgFoMLqrHpI52qvySs5oYIzmsRlLGJ5ocMHEECjqpTTYjAMqtcHAmcg6M5YQqoNBJ+MQmMAOtGnyz+o9uiCcfDAQOESbLqOCgi4gqom9uE2bKn3KQei6r9KixjSYS3dC6Seuh8xZ8nAwuIJ8WMp6qnAwPseFQWwY3IYiNmwySrbJQgzJCCT21o9vnNd7AgKOc3lrmdlVBKm/RUovHkQjPwOkVAr17jIe0zwkUISViEAmnVeEkFgTYp5wkS7JzPU57vEqKtHye1KtmKQxIml0VeSD09SEyMYkyFFQTYdYxk6hnItQ7p/rIX+PnIZwBf1AwXoq+lloJnUbACmlcxSKkl/ZCTUm57iW5XhHlack7LihyfUeSeGkUf0MiEZPDZBSjdfmk9JpPAbTlCQAck4pzVElvHxIqmoQpSQpsxREU7YwsR0Lta2VUH0lu8E6io0lXte5cozbpAdiUhJyEPJxjQFlsZf6K5AcnikFk825dj1kcpMTYQyoH8MIXkUSvYkGo4Ri52RUdt46d3GwWo8CD6jbA3PwXvtpHtfEToVRgj2NNgchVQoCqgBrhXEaQKXWAZFOrLSHzutZSAprcFZkwEiWp08zsxOfXfGazbIO6SM5uEqFSGvBuknICqnUT3Lk8s3kKON8eCqq5FIObDFIlXR60m8uGRUaVvgnpLAW41J4KzwpNo/vYhxzpr8jxAEqwgQg+LCxc/OSCM59akAyTUXtYNX1FVetpHld9QTWtDnx9StFMttrWQpaQLwd0Cjk0x4bqBTUiX1VCsKhA5BBbcYUweVEnaHYVniU2Fy5W03hGcVM2RHjOZUwlUfygC6BKs3jLJ2yEZiWBCy0d+YAFpdcRzX9D0UVRVpp02wLxii0FDDFpKC0Orm2fhKMuzBfZgdVl/Trt0ltL6roV/Ip52RWI0rVkfEbb07TMULhRRUW05YmZtQFMFHa6M28qabIQULcxcJBFpzovF2u71pXmq/GdZ0HrzRUp6C20jDlYSakP82cfC0ptJgLNR5rMinRWcxRl0LafqLUPZBPmpZLPIxOw2XBUvebKngzu3NLqvr6MlpvUjQ8OiqnDgepH9K4gjeFOHQTZ6DtHxsZ3buA6COBlA8IrgDRh30ORUEhCcIZrogMLSuWeBHIZCiKZdyaLLMu1Yyfv/mpt36kVbRSIaupkDbFEYNVC3MT04qMLgScknRbIhr37rHmFM2musXg5ViMSC2+92xDeUXjs53CkYeeMAck66h4On2MV1F2hy4ivFXrn5ICXM/aSgo/r64unNgq12VM1uMFl5zhMa2wpKR/GNNwDG04yFGlEFQbI5yaXJK3u5THwNo68pHqqoI5VqQvkamyiugamviIWNa+YonSVbEfrGK5CkrUeZau/t0yrb0tgZQPDK4AZm1jXlZTpW2DEc5gb/fvqV0rAodEIMVQeFiQLMwTbyqyCLgmDhISnlrTXxK2VWnyStLLkOYSp0A+sQIbPyh3fVbkaxXpVOAqW0xaCzb8uGfwlGaeHPW8pLvKLQjHABuPbagTmGgM4olJxrwQeC28FggpeDm4SilLa8Gqnn0jUans5Xgh/SY8mBiUfCpmA+HMBqZiMSdKzRZQCz5peA28oggE2JrWlQ/ZFAUYtSZ7t1EVjmvaHkR24PnJAGHf4zcZrxdZUiVRqriBoqogH7myHhwLLrwoqQwHV6Gf9jIU+GLmtfjapZ6Y0rrmA5ehMCGx6aUpwxYBqmsn1TVJK/v+pz5DKwwtH7K8kjQ3K/9sqYKgCuwTJ7XTrUiGK5FuBdKuPFOsOL+9bhCQoOIoyVDRl/32mjyxuY7aD7+9xiAx2lR1lH7Klygtu4Lu8fehZvvlxeUmiok6/x+XL188rtOs/P3lqqo2v75+XVLU5at1Ehd5md9Wr+J8/Tpa5q/f/vTTf7x+8+b1usHxOh7t637jetu3hGULzzZXStKtL9FZUpTVu6iKbqISU/xkuRbALtdRUX25+ReKKxpd/cjpkt96KncNto66pyzuhg/MPKlDzKOuEvndViQNUi/ZK9KzVwwiHs9AzzM8RKIO6GgR655T1cb1L7HkREUnN23fF0tMizyt19nwN8988tpN6zyO4as5pkYS/o6exqiYz7a9Ir+hfjXfzbG1rCCiGxXYjvRDVK6goTbfLXpHpX1BlsxR34bPtriarNcQuq7EFuNxnqcQvua7BeUwg6Pll+xrFXOkYwtEfL+95kSHl9HXgpByipMXfCO1ID7g66MbeGwOCkKPYhotcd68NMwjYT6b4xLF0Fb+TrPoJkWCymo/muNZlOf1TZpwrDh8tZCSAkUV+mey4YRk+GyOa5hdwko8ycVSc8xfS5I0kqf+8NWCH6KyfMgLnh36rxZ9KjjFQj+Y1/+A7ZCrIsrKNkUrRDMZjF8r+jZsWiDPVF8+lRVan0cVt67wZRaSQl8iHnzYI4HhyiyxXtbUzAJw9iW2/WwemBZsEa7MjkuTe0SsXJFR+wIr+bkseXZtv+3YmnXePQ3vv2C1qJxXK2n9aZYq/+XlLE9JchkBD/vdTrDJLyy6JGevKNmjQqsl+T7B/WkUg9hbqHz+JZZsPZc1yRZ5FZU/+JkVS63oikkm2LnMZytaEo2V5JlAw/67/czg3cVtckckCZ4Zttwc+wUq67RaZLf5GCv73Rzbl9vbEnFKvPtmjuVjsk44JO0ncxzHURWvLpN/c2zMfLaYAcz2RBdxdO+/bnthOslJbHCFlqr1WQrkYJYeFfEKL3gS67QvtcCcoiireXO3+7gzi2GrYBBRL56OHQaTi2dHWX1XV8KjNIk4rmw/2fBgnmFDgDCvoF/5MnOsogFua3AHW+GqfPMlOy2KvOAWN7bAYtbQY3VRix4S9ruFZo5KzMWYlQV84xI7jKfZEsTXfbfsXx3HmAPgHjJlln0UZ4T5bOMk+JAsl4jj3OGrlV1wR1gdr0Mx4t19QqE93jZSAsbbF1pYGvnDN1SIUst+3xlNP75w5KPpVdeqDDS9uvquavp3qI/q4v1NTIEFvqTcpNFT8wojh3FcZI2zPasHULYlNrL9Dt1G2Gzmhbv/vEP8jerOFKyqKF75+6NBlE4cb4RnGtZnGucRcUVWSzo5hCQcKgqEUGjjj+I9e7b+vDPACX9m7YEXt6S2m1B/hfMpWSPRiBu+7ozgDbc0fWRNdg/VQLzkVaeRqK6993XCnz2MSqz8pl1V0d3Pldn3U0Dpie84yaLiCUbZlVmePTeHNcDZc1dgpwEy0TXZf7XYAD3i9kXrjvlss5B+Jk/UjhdR+skGBz0/SQTTeFRgwXWbZVSBh83jkp3RNMx6FWxh91vO5zteTvICt89vXbqvFuJR5GtONOgXOwzi8jZ8tfBRcB7aKyvP7FUu9qL7ZuMt3qRPfEf6j9Z4xC6NCizU4gmnDU+sfMUxJ9H0g4WBB51dOhxbHudLjmebL7YeWkhJjUssxkYWlSaqdTS64bMdLihYZ/i8bU867sryU5TVUZrycWejkp1R8uxQPU9nGUwuh7PK6tPo+WZREafewQsgaqJRgUVoR15yiqD5YrF65QXvzKNf9jswp3GFC/EOzGe7DUDjUcFabYm5MYnSUhgsBLIzcttG8H9C1Sr33AuOUDlIrqb+RCYa26g05kAGZLMridN6iZbsVeTFkg9dkgE5tEP0X/Ekb4Ipt8fePUPQUETaCABms8OkPaS4yL6tDWrmp1sBZuO1yZJ1vaaO1qN18wTl2H8jlltgjx7V2IFyi8NMWuMClVWR0JgOCaFUcBbbiDpNpU51oXB31Jw2nY2X5tNgd1GG1iin0Y9DB3g84xILfu0GIjAoW+CAj17ClCLtS63iqAt0iVK8e+IP1Lmi3eXzwIwdgpNnDO4nDQPB/d1nWzGQYBQKbY55mlNV/oSn+2q1f4jFpWX4ao+pPbIXjt6h8jmPWC5X9K6IZA0SS21jL9mb2WIIpiqBrhzzh2SJrlb1+iYTdopckTnOb0mZ3PCn0P1HCzkh10LKFa/imM/bPMsO70naOwf6eZffKYAqd1fgM6tt6MILV2SzyyvwyN4Xeb2h97PBdUECYy2Qi2yZ3CdLwHcHAjwT1em5L5Bad8t1gncq67VwfDYusecvtMb6SLRCgWIr+j58yT7k5OYDv2zyZTauvKaOXLnCEBY7VFRFf0dPD03OtdHedFRih1HKD0KhHd6rpOKXPeazTRhwmj/0vhb6EpkQFQxBWLSw2RT5PVpeRBXWsJc1d4YHFFtog7zSoIch7Ht/lVdRCtMHhHAag7wRKZD1wdhVfhRz1te4xPJqCOlQl3pUuCTCFlr09EfNdZB8sPE1ZfVtFFd1gQq8olWfsWHJawsZjHkr7zE3jXE2X2z21t0D8PzGevhu0Z+2DmQ08GU2h8N/1EmBvlQrksG8y7E5PiWGIKxbGMwNGP+o3EJ+a6y3sODHxNA4Wi45bLwsa6GtAlD7pwy4CNT+u30Ek3BnnPluYf5naSOezDsFo00AUG7l623E6cutBD8MYU+N08dNUtBYzXfRUwlThoexb4Vmk6MYwFv7UigL6yYq+dcvRqaNWGyhTUc1hWuaQqlVr8k55tFdgZBom4qldoemfcUrkupHOD3lim3ksk/dxQsmU2Cjv9pKJ09xij6i7I6PBIYhXFs4R0WSC/Mog3FohRoYFI2giSEIG8qTwzDwchZXZIPzDPNBd8jGIx2X2ey4lgmR5Cjtap+sSC4vfvclg7KKjIweTx/xTkuMjBwKbPCdlgJt6SeL8LTo8QRv++7yQkgexRVZ2WR4b7bI7rHE4srdaahglMFAVvH98Q/Yo8wVWTv0aP2j+yhJo5skFdDLodxaggcBQ1id8iqwi6UWu4H8oRl7m3uVn1yo3GqXlNw+UX/HWV50/TtGeG8q7JTkgBaHHVH8IycuBJJIRDjs4Ast99vypPjCxts8f768TeoKaY/v4XmHIWxbaI7wVS3wEJZ0Q8u2qhACCRRbSR1Zd47rp+O6qoRbckKpNebvSblKk7JSoOdBLCjT6JoUYXY/x/sw0TMGQ1gcP+DdEK2axPxFh1GJVVA4j8oax5d0CaAZvlp7Q0+EaMFRgcUaRF6wjlKgd+MSN4z0Yvc7vAiTd4XF+F0lpFuLp9nSqD0ezoLDWvfiaUbiUCBGgwCsY5tGWBDEPkpA62gng/ZUgFY7MfYlOm4bpnqkTo4Tr9eoBB/zBcahBbYaS3e6Xh5tNmnC7xVAAHP831Fyt+IEvftmQR1gn2e/s/ueLHkk7ScLegHj+WA9nn6NUKsXBZhDWyrFIgXa5kF80DABRDMAcR3rPu5ncIBirMk9Kp7IJIpZL8dl9oFDJB2DeEF8XGZj+ZeoXc4AR4VY6oD5E4rKukDNI8Ag9hGEQwtQiK1Q6ICX/FDiZgFsQ8vo4aXo7RMKrUPWUEFi7mAnkQTEsQ1CA3UTPYTzKPLG1RTxN1MUYDsTxtMv3H437ruIPvtAHnnVaSJ5yP/94lW6HoNnD1yZlYddFtvIFe1K0GRXG1I/fJmNv7C1Y8SdzajEHGNrrwj42O/Wp6PlSV5vyHUFPt0tVG5hO0iwumHrZoEe/VPjX8avYwjLiIPmjA1bC1DEAVu4M1qvdZknnjl7O8+7g9qTV91VtUfi5UQsw1cb4zNs/qvjvMKbdSlWoNjGzRkgK2M717LoPqj8ECanxtUExspOxcRSC8wJDUISUA6fbXp5h8Q0u8NXx7DA8ipvrvnA+HWw9v3/Ah2HCIWW7uYL8kQ4j5Mt2HqI68SOr/0JANxFN8vzvtUhpx69t/8tQQ/iUwtC4c4YXK329Lwt0iBxuS0iqzmNsdU2ByU344psTkxsc/hJ1Q7K4cRmowKbCJdDPjKx3O06VRg5GeFyv1w1t9QEvBob0ohzX2i27NNrXRH+yWsBhB6ePiWWaVira1rm/nDDxgzkou5ejRK3yAbgVmEAsMdtVGCxd05S8iRMn3mF2zoLpTY2eRuCCaMGim2ccE3CEGLNjvPS8M44OZxLa+fSKxEQhFML49RHslY4KJeWugkA93YKMLdZospANT09gIVj97EqIjG/MvN5d1Qyc6bqqYsZTC5KWFl9V32PuG5efECPNF8K7xkfFVkv6zuSNX+7Zmcgf/gYm4tbXIth901PmU/SzRu5KM9QRGzWbmURblTwxc+fW9mrokEYlkXozrRqLLvPuGz/gYsgo7IDAzsxcDjO9WTZbfCqvxkQ+uCQpYLscE4GczigU+M6HKPt3zHa4fjouRwfbSuuppWW9r2VIMZZk1nF3SyT1d99gwxQxdZquBm9GMPLfrfARjP2cJjabzYXJNLNbZ3+FyrpvXX+pgRXaI33cy5H25eFFxkNdy9K8vvL7X8TgsjGMvPfXYLJxijm4vWuWWGny3y32Chs2meXuS4Nn202HV1yJn63MXz/cyp2Sx18nOZ3XTo7e75U1p6IJ5vmRIU3KrA4H8FDOM9LMb0u833rszRa6VpNlxGR8Vw0lagmXT+b9iWr6FBojvd7VLajGaNkv299Hj+jh9Jd2pS1p5U20VAZl2xPfglRoETx7Petz/t5nqbfcmJGXaCYPB3jILdaFBPJK273KCsfRCtgXLJzWxTPyP8Wi4exNreVJj4xOi6xuqoU6AWlEG9UhXuFqatzlhfrCEjFzJfaY76M+OevxyUWjqzgKYhDJ746ur1N0gTwqI4KLPCRNEW8i679Nr/7aFE28Tvdo24cxbhCi6gZSRCRW7TQxwhbqPC+iiva5m6IdOVjfpdk4DU9sdQOc5feSopcANiZtaqdHc9T+W6O7Zcqac1pVqqzpCgrkfWZz3YzL6Iavs67RmF1vImyJ8GibT7a4AFj6dzi8yrM7/jbfZLFgo4WCi36KKQaO7FNLdaw3Rt+Xeu+WmN6C2J6a4Ppn8mGbPejVLybyxVZWAWrPENQLuxRgYX8dEl8OfkZPs+h5bdl11MZ8L7R20iSi1kvq7mrh+n0eLNsI3yBk8+hyBYnnNeDL7M4B3nIP6KqwhvJMhfFTyy1wLwqEFLhBsot5qjGm6skBjHzZQ63Br9F8KPabcmWjmeDH6v6HkpvSR+Nnxn100pjXA7KSYdgV3VU6ICf8Ny5uMswqZs00/zJ1bjIJq59nmd5p3jMdlvSxtqrnsLGonKRNXX9qRzx4TYGAUyLm5sC3SeRKLfjkn1borbE3N1Zox9fd1gcz3HhqtNw88cou6tJ4nkOC/t93jCa43zJbWqbL7aRh41bFnzQqy+ywNkGM7R1T0RPJAxhoQuGZ67kjUiBLOYouuOI0nyxcmfsdN6wP2cU8FQRpXvnnuhUl58O77A46HB51V01/rsen9QpTUMALgV9ocXxZZb8UaNLBOzKuSIL11sa3S3WuD8kgYY4dKDYIuiy4tzP9MP2ZfBPbZM1d0Mvq4JGXJR5XfjuOyCMLnJuhGYf7Lau96I4jUvsMQLXiLkim4PgsyJfn6f1Hf/65LjE6iA+r2NBrpjPOyMFXRSVH+d3WBxj3uCq+8DhIXYm9H1szuBtPlmsXnXKLTHNl+2sBbtuyR92cqp2ds/i/3PuyLZ1Rb+P+vS8/NXjcQyGlVWeZl0gLUIBsHPvmbpHfL/lFS97XNEzts0J4f25z5Hv9tMS8ee8JkCx1ZpQ7GJftCWrYpIUwXTBf1+jssLWOZEswB4YF2/zsHLXLas9Pepnj2DDXCsgmDyuFsDVd9XPJ38v2vW1aEkgu1MYO67UipuYfZcvmzuYvYszp/MNRqA3JVOHn29rnUfFOilJSEJz58lzzeewuaz/WhS7KoNhrx306dvG+7T+685w0OljRS4tpUd1tcKNJjENTAjBTSrMDpxlh24aLgt73T9EWH1HlcWS0OQ24e0kqNwee6vidY0AYOZtfSETe5X/QJzTgP1uie0ojlFZynCOSi0schLXhK1Omb6AyndG2s/yol77B9L0aBzkWFF3GqG9yjdJzKPoP25L+MXr3bb3uhfncLaQ85240bZ3iZsoX1K2CCAbFI+rcEgqTyMdtEUeRf9xa9JBSAC9LjkqsL4xAF4WsHQuNtqT9yu2X80xkRdqODTtJxsHWFlBmVnY7/bYZDMJldtjp1m3QbxNyUFn2emsyF9ZueqpWVXU+yKvN6Ce6kv2+VrG537t4TVL93kbSoqIJmg+jQoOCsuQZw75Nicy26gKCGC2UTyu6lBSeVf9Xbt/sWyXuPuZndfQufATmGY67WVFUm8aMaGNQRkBRgWW+MQ0Usxnm6NI4D0ghzeAwu682ivvrQ9ByPkolNocPzfHWBLUQLHlvJBrh7WAlyuy7y+MViy1cCE2b1fBiIVCa7zNFWepf1IGZM9xJ3VRoCx+Em8RwBA2LTT1LiI+nmBcYt/n8YGlTGZEKIudfVRBGof5bMvX9U1FUjYvsjjFHYPYm4dwbIFccFa30EPYt0DTTvfPDarGAkN6tqgcGwxp22KrEhRj4yEcW1CMhYdwbAHXFWUPhnDUT1jPJ8TGjNIzhECSGYCHaBskpgF4iLZBMhuAWwVvkCrC1dLuqyV/wFznwmmdtAHohiLb3olZ79nvNtdkbutsiZbQM6N8mQ3Wh6hYnudJVpXfUYHw3PKBfRIQi3VuheIfeV0dVVWR3NQVku7z1JAeLZb/uU41TTUg9qu3LOQTKrcIRQqcp7O3vDcSi3xjHWBJ9gVY6eKNL1YHJ5hFIBNDDWkTvFIsxVCv4asdJtEEHb5aYgLG7DbCT1H5Ay3V1JTB2PX55P7+rdjj5qsdptPHTVLQoJdPeVatRKQCgCv+/0IRQGW+3I2D3yUFiqt36Cbhb/fIgGxcUH21Nu/shzwF3FEyKJ+WIA6SQzm1dBxlP8StFgjgjF8UVhDADf/iRI6alDlh7bIOyjD35U7YFzcR7x7lC+3XBWqTgJmrYQgLSauxzVgk/6ZielVEWRnF5KewsCng/FsTmVQN6d/iBSprPq+4DtZGO25IhgUFPWEInxagEcmhrCIheitPMSAFmM3llSJeRSWSemFBAJt9VQLfvRgV2Hv82hyBoL+vK7PHSjZcJOFxXTVrtNJLZ1zJ5sAnTN73fTiWYbdRF2gdJZlws0ICYt7Gh6j8jB7azfrnnES8xkC+NwXYzhwHdRvuryXeO31Iysr/IXQApUtOYTM00xwe9R5IXmaY75YOCvBwwnKp2Le8T++TW7p/CshcAEoX5jJDMw1zdW3zWNjvFhq0RMvvSbUCmUwotMMLpIphPv8JGDcMr3rw52xX+FtzjOEW4N0wOZQ990NncHyZxe4A8Nbae2kXZdcDeh0xEmwmEMB+7HhjuoH2SlC5jeUTJ5uEmBqCTckVOeAEbizxZRZ2McqI1S+avsx3W2xAB0cFFh5CVJbCLfn+ow03DWSnxp/ITQDAM9aqvcYIEH7kmDJLUXfCMCTSoCR0aCia3548D/hc9z/qiFJ5jGr4amF4ZEl1XiQxfDIsljpgBs99xVIbSsr669pXeT9d+wgeODscK3d7oGZ5BQctAXFtAySDBMTCZNAek/oejyqORZ2PQ9/lD1maR0sgfxlXZLNQdVWlZg8AYBMpG6OsRB0OIVhfLLboO9af31Fyt+JfIWS+W4RA1tkyRWSV4YIfme/W+vWE3HiFNGxTsFvL82eaIMt7eSZoXJdnuO6Uy7P/UvpZyH702S3h0VXeP5ELRdKzxc/YUmQ9tGH8RiJGF7eREZZpODVsrHczBE4rtd9ssRzjEvLaC4CsL7LzO0G7efb7/Hu5vZMgcorUJGL1kZoOi+OrW3DV3dbkV0UU/8ADgw4t+TILrCQMEbJTRgWWJ4sIPgLly6zOCZN7VMCvBQuFfwLp8XdWsJg8pGhOl0XfJnAO3n13cIDAsmntP7Z3NGzNhqnqIrtAf9AMmJ7GC4PKyWxR1p+IjQJdcgvNQGENqXB+rwsUlXl2lhfNbPG+dq7QBi+ddkT37ryLQyh0xiuP5FAC2s8bnHxeLLXh1Oj29rOYNJr9buH1Wa6TDAzxG5fYUJoRX/jqoARkP0N7trbo5/TPE6x8Qyz8Y2xOi78OxW5r7vOoaE0X8aYrW2Lrn4MwjktsjJ2BxtCRNFS+tU17wPOaKXzT3agwC6MCLelBBTzuMcQ2VtuDvuv45nKD4j4ycZh7v3TCKtQuuYXt8E2jFkOKHzySLxswIFsDa3kb5yxJsfAJQbF8mZUKffiStWSQvS8wKrb2iu/P8xuqyfI0KBSYXYwLK3QTGRpgH8z4f/akSc+DE6fgwWDct0cptPaNG1r9Sx+WDrCyYzzuyzhYedszvd15+RYVCTZie0k4ydc3SRb5rxp6/O7zaIV0ohXkR80tFuSDRURuxT9c2nyxOGGMsvo2isntvAJva8HbwDIYy3AwIA7MKgwHZXf85fHumzmW78mSR9J+srg/BRyEfbAP1olKRIkAHRcLhQ54yQ8lbhbAwgovy+Quw7vfhHLEgn+AESq3P8MjKUqF60tcmf3GmwRJ8lj5MpvAM+gpoOHr83GNYA0a/4C9F1yR5W7uS119uaUo2sSO4qZOBNn11W+SJS/cOrePDoe2knSTBZVbhDegxwp/Eh8dY77bXZz4o04K6L5E991BnE/yrCryFPLyymCe/4YgrMwFELbZpewoTSJBbdJPs24oHXMy75bipjdDJ9HezZ3TYCpcgm5SPc73QaKCIbB942ZcNy8u/6ijApUXdzfcAQtfaLkNOlr+qy4r8XhdKLTY1NDNhwyxWGqzlp0X6BKlKAZuVYyK5lhrZBipOEAL46jA5sZH9oMc74H2i1D4DIO/rhK80yf86KcMezQOmk9Rd/fN1dAZ0slTsLJD+K5sG8fQNn6dbZ0jNC8YeB0bkJ8upwRwvW27iqURAAXnLqAfLJi+TE+z6CblFwr2uwU2FNcFEvrEfLZw2ImPCn2wflEov8t7lxa3IoyLtrkSfqjW6XG+fOK7yH63sYOyCjN253f7jKqHvPjBm0MwjJUSwQL3RKWlS5IvamEYxrqV08d4FWV3iKYnUzcmA90Z1dZ2yvduWTc2p0frZVV3VcmpXn5we/FBvJXucCWdCPvHHBcKOUxHRbaWw1lerKOqEqJlxFIb49jv4SCpgNY3aVKu+NWD+bxNxbpLAXfSUeckGeBptsQzys0LV7QzKoyySfspgJHWgrnaatLq02izpnkex/DVFpOoHdnv0+1etrVJXaE1ou4mbFt6blRZVC6bVXX9adiHNsodWDSf5lxQgSR71gn29oThjqP4xyKjR5FslmE/1pMgdWBCY0zTsGOwaxcB/TBhLzDs3T1gNnDnCq2x4eN7UAZhdGBVMzS7uocgz52fR3wY0fD1GR+7nuCJusuLpzDcxGNz2YFqURy4aOe4qAukDcJEHDKPuN4DC+0PC71HGSqSOFD8B4/NJeWxFsWubyL/jp6al8BHmIavVpgEJIc9BcjHR3F6geLcN1d3j8aBcxV1d51lw/o9pjltXZSLJe/W7b7tDBt+LYKwYY/GgQ0Vdf9cbHiZ1py7vvmyjcB02bOV6ucqt6VLu0cvPXVph8ZFl8rrTsPEkjfpnd6iD/XOEsR8ctbblm8ERWVdoCY4ztMpwqJy8Yao6+/q7qN5zAubdg9EaY8V1rjI6jA1yYXTVPLpGe9l2vl/l+AmS2+HMo/NnSEVKA48+cx58iwv6nW4Uw4BnQNXGuCY6GSDaVN8B0Ms3db5BCUQj6r/aHGwmG+SmMfTf3zG5yXnBX0doE2w7OuWZHE5eSXVCHb7FO+syNcy7ubLbDhThnNcYiXb/0JxJYh089HuCqF4edD22mAEXBmMLL0NrcF+/NQ8I8wjFIqdcPcvP0nRMxDPWGPQbQ0u+Jh7hi8xiJxceIraE208hybBK6li8daO7cH3yu3zmO4zcxLMwRiUAPkxKYxhohVtgi2B/0YFvCmhuCaxtfP+9SZK7jyt/w6L0/m+rOrObkEDWRTkusYYSfNlmzHLvrHfW3OwUBs6UAjUGJmTe0WDYVdZ+ziO6XOkrXeZZwCgeB/FJtzpSUONGHyLnS/b/gUM8t4sCcyL0pTP8TMqsbARqyqKV0QG3pwl4pkqUOyC+60a91sf3D+rcf8sx70lVfcZPZQfUVWhIpzzDsbpoPhMEU2k/8DWRYeeCs5S9gGRn+4gbT4zYE9ia05WKP6R16GSBAnoXMxZPY5dXfx3OUkWfTOM7J3aUE4YtxzKpt9X0ePpIxLIMCqwoGr02IUwCy7NcZHV8rXTKcPCGV67olgCJI6CcYZQMbMmixJaF3x4EMCceuyQhMlPerf2Hl1cF+Rth/PoibQa6l1dCKvb27qGmKaRO755HhtUbo491Ouke+dC58kWltsCsNlsev0pThGUgntUYInvHBVJvhRWiHGRzelwFaW0NucUGRXYbHPw9MFv1I5KtuFaChWruciSKolSUMD5smcs5zTBSvJvmtePDMR3XeHxuawoBjimkfXTYIHxbuHjUmw0Cw58TYkvc8AqQWiPC7i+NCrYGa6/QOT8octl6bu2sbicVjY1gml4/bxLK/CG3wgM3x2wvZVge/usbfSTIi9LvBtJg3AUj81lL6xF8Xy5amsrqWcklFsE1IyRT7ipj+gepcKixny3cV3mRdUFeXLuSrbEIvSyTlMQ4ajAwkTctAe6HNGGz9uK7CLPMQoZQvuPNpvnW1QUqBBwjQq2eeISPlncWUFe2ya5G7nY3P6zTYQdzQH6ISr5zSFbsDMqCn8gSQ898801SFxSzclq7urJz5/oOv5w5uK7A2ROb1wSyqlq7yqf7J29Su+0eE4zQeEywXC9aaa2iXMVJ5j9buMyitN6iRbZZYJrR9xGXCy1OuiIypIE4+KCCj7uEAFs7IWmMm8wdF8tXI1Jxed0aD/NGan2CVVRG73MGWfjEjuM0rd8hEI7vADNmM/bj3+7QBlWT0fl92R5hyreEByXWRwL0hr/zDNu6Ox3W2zfi2jTmlYQ0lGxLW7y3jkwU0KhLd7jnCh/XqD5MqtjXLxI849YDF8tZZk86iuKM/06/3J1VJZ5nNCXaIU1Cy+lNGb0+mizSRPC6YzBolio1PX45amDlhkheI1ciqu/tIHry7wuwGdgjFa7HjW07BFi9n3x7OZVVBDxduxmRyzLbv72GpxvH5bo/W9WDDHUkrGDxI1mROYO+07zQt9JP05o0czNCDTADf+Jl9VVvry+QGVVJMRIOiGj1GkIk9rCXnZUpwE0URb6tjzZhGsgALMYdNlTfTTUm5tpOtfbNXNtQ/VYiAgtvgrSwHT+QT1tRaye098jDDDxQOf8JrrFs7WJPk7SFH/u3bcGs81XkU25zVyPcXrSlEMWcto51H6Mad0xbMgvEzKfLxbl5zpNf395G6Ulshy9N/Oco2KdlCQZUJOk75pNiKjkIV1NIe8FB8+CG7CXujnP2ROQB+AzTYc91xQG19b0jTGryGrItI0la8Dod3GleS4scIEeomJ5nidZVbYBsddfS7T8nlSr1hmsiijRVhZjSIQqBnyhbchzBsa4AvCJvsO7uEjpyBBO4XSmuo2FI9QJYeJwSD35iMcWUuPwuHeRgfTj9zdz2o17++9VdKe2bSBwMZEXLRxgTKwYEbGv6eLkkjDuXBB/CcYUfIXqfK3EIR4lGSp4kN6Z237p/y67D2RCozv0KV+itBzqXcYrtI7oiMpNFNMd/BKdJUVJQsOjm6hEDcjLF7jv9wnWet2RW+tw+SM9SZsUWR3ApyhLblFZXeU/UPb7y7c/vXn78sVRmkQliRtIb1++eFynWflrTOU2yrK8okP//eWqqja/vn5d0hbLV+skLvIyv61exfn6dbTMX2NcP79+8+Y1Wq5f89VbtEZYfvqPDktZLlN2xpmD0/7gMYtbao5Z47e/I4HRugm+QLcvZGrkt9d8xd8A1iI9+P1lQghLhZG+zUCDSyNyrTsjUIj29eULom3IndBe47xWoh+ioJlGeHL+usiW6PH3l/8PrfTri8V/XjOUuD5+arD85QVdCn998dNfsNr7miV/1Bj4qqjRi//XumONdqDxyk3PsvuoiFdR4dC9Bpe6e5+ix+bWx+8v3/zykyMdm6Nn7/5y5Hwj9td0qEDV8VB/sh8qPYp7rKYZ61v3sQJVvcfaNNHEe/FDZVD/dYS5KsRYIoGIlE4LIm2M4LnguKzI9Sm+e/9tHT3+dzeEx3meduhuEutekTnq4/caLCSir0oIuyjJzx4zKtXw6eMmL6p3aJPmwKWxvdXF+M/bNv+KnTLuKw4qzkHjwgLtKUB9CjiQn4xQLEr6ZnLsg6OJU/1nsvFBMjBcl10AFF0jXHjzWGQ6gv/1J2u9MgT/hMVLI4bVKK1xfsDG2VURZWXrL/SnKoTRBx9Jh9RYuM0DZGFpyicXC0xeir7PODZF14dsX+4kJgyb3NPNiI9sYnm6LGXLlv9a02rYZ7LQTKLpz/IULz3ToMakJ7/akQfXme1mlQ0ftZDFudZBsvle1uRc7Coqfzhs2bj6XsYCnhI8FaEMT/wn0VJ05x4GHZ1QvFO5Te6IbyII2gtU1mm1yG7zIOi+3N6WSGb/G00CDQ71QXAcVfHqMvm31yJ5jiWHKCgf3g63npzk6w3NGmC3ulpvlTqD8gijo7kOPIzTFEVZbWGaGi9ercgjIvB/lrXrFwftTD2SNljNWCTPsPVA+A9QbPyiaI2etW2t/C+UP8aukb8FWtEMWv+MHquLOrtuqw+eKhcfYZVvvmSnRZEXPvLXdgl0WHiMiFtWTeb0Y1RWNBeLa196BPTXaebdG4wifF/eOFKmjmMsTBrHkvHIWLYJJpWL8kOyxLrNhx/xn3dEa+CFNUZeHsIOU39LOvBoL/KHb6hg1VuRP9x3XySqn6zQNb3pFGaR+0cd0U9fs+S5eAINFjkH7xVz08kYtQkXtLchm5ww0+BmgmvczELyks9thM33Caysf9So7uzNPpX2M+FEZmz2O85RZa/tZntkTViMZWA3h5PGk+eiCM+c/PZnSqe9kXgwe1s8lpski4onp92oVuW8dbARP+G1GbZTrRAbS+K7/CFL82j5TISvG06Tq71pqKYHnAlFeZsgpclvL2dfS9Q1ynj8nQ5LRDRe3pIO3zFl8TA8314qhTjUCR+RZ+hQxwnZ6WPVvlYbAtui/Iwe/E7h6MFKwhikplu+PqXLNYPEa+M3ThLjsDdRd0m/QrnYBs9EKQ1Xkj3O1Yp8HdyJQ5DaeoeMHC2Cu9e7q1f5FB29QJv0ya63Fmin6PHJSWiMx3EcGqXJEaqLzdhkxwgRtjNKwmUVcgOMliyIw9VaN/kmSLTd8TxnNtDy49p+G5DRk1HhT5mZnj6TdaJ9HUhl+P/yi71Z2fgD9LuVMW6jcJS8VAu5S3/P88LrhM4oTsipYyaBQi6IGze4VTAGvA9p/DVYuVEOxLUmkLs263xzB/6ZCN5oTJPGNDzS1FijG62LZfBztL6ZJp+AroW3LqFSbRPjnAgTjKUdA22P7O3aWGq/I+9PSZas6zVd3Y7WdAnpll0UJ+soJfcx8K+SXqx48zfMcsRTi2fdtvt48DO11GDvklKEohTJGGvqB9eH3JlrmeY60HGdLbGEYHnsHwtqwmieieIZxmdvqrF1vQy10TtM7pwyfm3KDxf3qFH4RYxnr2fCT+ddPnCHePimohcnNfR07gRX3asr3RGnDxd2yaGm0th9Tq3m1Jo5bnaKyTCwWKw38it6a0Sq/l19vpjNmSc5PQb9IVmiq1W9vsmYrZMLom94Pm9Srwmgdy7KlV/IaqjT26AeDrXzOKhKfl6KmLTgG+xf4C7QJ236l1H8MLacjtVvcp8sLd0z/mrHzHc0id5Rm7JOOI+W6wQb2us1c8jjG4hNGQetN+QxHL+pJslav2QfcnIPwG9x6XCEUk/j7MRhFy0hSXGAaWHSFIft7FGa5g+9OwDdJ+jBa0E82myK/B4tLyLywsBlvfaZpc95FRRfh4y+xscN1rN/oVC2RxdX+VHsZU8IWajdUV3+qIOLSJTVt1Fc1QUq8ApTfcYW1CDQoVp5j1kmNM5F+T65rU6iwsvK6nD4r8/tC+JfqhUqzkc5d9161r1I3i/1avXocop2VJONWJXEZOk/Wi65Jr26vyj7gKIAkTCeF4aztBHCDp3XyDDRGyn5civgc9pctkhOHzdJQXfE76InGUabcB/6kihF6M/dH6LyMiK3g0LM6hiTw208rr5PFCAeGDknOrorEFrzAToOB049oiv0GMYQXJT9Q79++qRFMnql10ffsfjO+zd6A2AcvdHr4zskZxJBsiic4UntDji8jLJlk9osSjtsJyuSLcvFsWQaehY9nj7iTYQXZy/K09KLhMwjRp4HNlFGHovN7rGIYWTdaZOXpVfl8Y9AbkKyJ6L4ju6jBNdNUganhxsI7KPT6pVkwcb7MX9oxkoWGjEVlr0Nn9w+0X30WV50/TtGeFvkg/Y4in/QN0VImgTPgw2yRSP4Fg0N8Rah31x6mRR0+WqPIkNMTIOvOXAMgY8OHC1bVInmSrCLKYrZnAAf10/HdVXlXnfAWlzfk3KVJmXlj7CV5RRhvsSKeeTzcLq0jO1tiiqJ/fzMLILgS8eXdDltA+0244SJHQrexuUG44lS54EYnY0wbdBLm+/wSneVrFGAID4W92m2DIi5czWdZhUqSm9ebLXXCCuamIFaDTdrm3i/cJWgRna9dD5ePFFZ9YdfJ/ma3MYgS8mkzIr73502lu1TNz6j+I6Su9V04jvepgRH/z1ZToj9w7S06Vel0DqnRxxW4YQ6MQhzuopoopMRipCXwXbl1BUceXKPiicyq/aul3FtH8cLe/Xc5XosW9unH8dRidrl1dtR0OP6hKKyLhC9Vq8Og3Tu7rQxhH0z5Me4KQePZBPP0zyjCLjFfI8C2nAhVJAArhBumhFCQoaQ+C5XeeP5iYbw9oARbX2s0vOIn9BGFbjcre5o5O8X/1qiMEFb8hCw4IZD15S7BjF0q7VmSYi7TI0lEgBTe8ZVnuT1hkRG+6XOFLGYJG60CQmkx8d0FfRj1AEPWbot/OrGigd+H/b5Kh6XcCYSeWSL2IhbAke5HOcVtsCCx0hpU8U5CErHdmECpQ7hSKNwwzBnNucJDTBxCEbvKvoY+CRazTdP5yg4q7zKmxsJPGYnH26L48v4FMHE1DGLIsQ2/EWU3YVP4xkwpjCsT2oHI7f2wmfx3CLOIQrSe7HfEvTwCbDZDDSweRR7o7qeiTHUjiZkAiGj/FZ/dYgYR7ksl9AI9c8uIYeHREDbSATUHhQ+M5Ha7g296WyyCdcaaydYu9t/Rkklu5E5uO77mr4zx5P2ou6eeoH2tk6rw+UGTe6gal9K73MjeMWQdeF8IZB1V/eJ7TlOEOHhgx+Qnksjyl2fdugQs9kzQiPvKCzZKHlib418LwqfPlZF5PhihrlmYw7gnolK0zrEHDI2n+RpXnxAjzQFQWjku5e02daUema+2i3bUhIvmUH7Q02vDizKM7zlroerOwEijifmQPbi2YELQ3AhS1H7Poxr/2m48Rmy4SQHV8HPLRi6H05uwp7cHE5btL3bv9OWw2HEfh9GmIdRtHzfJth/JotSNyoHE7mv6WeUbNr3DEPL/KLsMk94BRLtK5s+M/5UPxdiHxyoT0DsktVVmx7aAalRUmMPvGd5sY4CPHhNcF1Gw2NUofoZPIFToKvXR7e3SZp4m8f0jq6X7TT1ir8omzOELpW7TwSIInOzsst+LESeRjRbZ1zi3EKuEKSnH/O7JAsUVUvwdbfADVDaHM+05Lx2yCLKI/DZ13THD+7dETGY98d4We6Y73msymdJUVbBzhQJkwZDBq7Bji9prTdRFug5E/D40cTK7ip6yQhGT7L23idZ7OB/4Kr79OSESXjgmdaRytObkMjeBkH2z2RznpckUY5bJBtgXa3yDMEJ4NzSbHaJssKwtvv6Z76paaTgmWhPrSP4jYMjmPrmynZp9bJrKaIQt/WuHvKPiBBrUebaqzhvbfnualUgZI7/Z1v8WERQkcQcbq+Mld8iL+t5p9JXO/s9jYV+/HDJn0X2d+EQKNgD1XcZpl6TSc3LMz7TqzwBXrIx527WqHomzD2dlbvFdfXmpkD3SQQJl6/rYIsK3ZhPj9P8jtizz4RFP0bZXU0SFVrz6FDTi0nBo2VBS5s5Ykwe27Q42G18rEGSm7e4ThT5KcxEesgfHgrlVXQXJkh1N+/RH8Itwh/Mz7HF7dTLM9Gzk6zFHY1O6pReOFKfGNsfC9JzxUs0wWb1LI3uFmvcdXIFTksb++jki+p53aid1qZpAsQvK5LT+gKVeV08GxN86/ZNR08tk7vkB+qQg7cH3EwfkkM8X5+n9V0iy0VreKic17Gc7T2Ojj6jh/IZvYC4dQ4NaIHTV7DCoCIZaMJgCqKBd9O4/TPtU3bDfj1sMDq0xvdo8jQ9ysqHZxOCRwbkELROawX357mpxO5doG955fd0ydR3sDDRngnXbH2ZD8c9TdRaq/PCnOgHWaKD5Xmiy+r7GpUVtmKJjOzCAeNuWiDTH3uyZ1zPRBfo94K//GJvMAd6+ilIMDBG0vI9wSFheyML1zscuAvXpfwzUayufOp8DCdUrJOSnLteoJho2ufL/D7r0CTC1Ocd8ENsPNenj4SqUXpUVytC15gaA89q3rd+sypcJGw3Wwvmyk1AtK3mDIz9C+Gtq/wHCrONpOiO4hiVZTikNP4DT7BXkiZjoTvLi3r9jM7yr/JNEtuLV1vNL7PItoXb6BVTM3vhfB8uxsxxdZLKB+WOZyIgdED2LNpW23MBIfNIWvbMH9fE7qp9hw6pqj43mtjLGUXS5XohIBdvSC88XzBosUim3BEbzaPmuVnfTw30nJTP+yKvN44aqK07bUio0ys1gU8oPrerjpcsh1AoRPYgo+qgVibxcO6neqJi+Ux01F6oh+fGbttwqjfEex5MS8cS8AInxRc0v4o6Ze9+7B/GSQp8kxx4doZLUeCd48DvEjetSe4v1XxPHIcVAleb4zogqubeYeD86X0WsbooUBY/Bbsk3iG8iIbjW01m9r85S+X4uMuP2t+iKrQyw5v2CstCusjiFHfVkBx/ddPEXWPk1uAcjV2Rxvo3A2Ya4ajReUba6oZ5Rtg2NuvIcEMWwmrf2EiNYcWfkLUhSs8Qmpqm8panJrC85amp3eIPc7WM8snkjNi/SDxpK1R1TNYEttRrbO0sJ36S5AI9RMXyPMcLXPkdFQizlmdeyRWKf+R1dVRVRXJTVyj0rlFooPzPdaC8SK0RIInws409C5NGr7f6N0HGSCPiyDaGPB1TIKxHTvD8j+0Up2nHWNgIIb+JIF0KZpgK4/PMI17+QEsZ6bx7enJ/HyZbE0F2+rhJCrpz+pRn1WoCvP+FojBjZ/nyXYKVW/UOUT70idfs0bQZHj/k6TLQXInIAzICg/w4yn4E20pxeIOJGIt3cRIaZZcjKzDaxU0UaEFqNTQ1CkLmcyWBOVgg/t08ok7eO4zi8bvsk6APxm6yBi5QWYvpdB210YZcmA5PHBFxwF7jfWdvE4Xv+nlN6pYotDv1PEpCBZtzGbCC0LRFSfYqJMFl3bgMJ/F1TZ2YeHcPNNjtwgVaR0nGBKk7RIV/iMgLt+3m9nNO3PLxKCNRwKOSblf4tcS2/YcET9ezSby49Tc6aVX71ttqfgFZM+QweZ/cUmP/GbJONzT7yRtqes3f1xItvyfVypGFuOreXRllYAjubpmTWZ8Jg3bmDDPLeC13uLcrweNzYNiR2v+waGI336LsukrvYUW89eKoNPDWbBNww3CBB7shb7oHs9R6jOHujFz+7/aurDluHEn/lYl+3If2umf3zbMRsmR1K8KHRpLbsfvCoKugEscssppk2db8+gXBC0fiJMDLfum2CgkgM/EhiSORibJ6Xe2Lw6Y5f+y9Q2VJvZodmVBkGBGyNpst5ZCdP8yGgsr08nj1Y5lxuXY7b87Sf57jdiDcbfXHLKlI4r3Q1419R8FvF6eQZhJJQt+Gdvul5nsdWmVsb6F1F+zKzv9V3VX+LUvzeD86GNFN2TXlZWH0FkM8K1HXpnRNaPT1xVb/E0oOT07LQpMeXp+zfYqu4ir2c0DYpnqvH2L6wajdZ/99HeBjS5/9WT7Q76kwKeMeYHaedHmfunHhi0f6dHNbpzyzex03Sh3lvEpaeB2ncZ1XYMzyrQzuVeNzE+b3MoA+/3BIKcfXD5BQrr7IOW4nD/GMlvyhiHdfkuzg8Q6Q+LyFXZSQizzk66bxCqXJV1SY5IhcSk6wbgJs6BSjF8npfUdT08P9lHhuHPz0wGLtUZ2L7A79VUer28qwj38u5WngZl//+Dh3ukNxmWfXedFgxc8ivUUcIvtfg/2+S6OmLghW74y4YMljM548Pr7PfXm5qzJ788F57TMi0IbCx6uwpXibWHwgc+LqcxkXm/pIjreWt3HRLhxGnTw150Ju16x03XHpr4dBHn+9Orv9n/naI9xjCTw7UYHhXx/uB9tP+/h6rs7MtSN/f8I67Nzi+nHciM2beVrAuv1wYr1xjfcqytbGzd/6ecR1kmJ1j4wK3ATXbtU3Nr524KjxKoVuZALAIvrC3mIizc8ClI1AZDWj0BqVh/iwYc1b5rS2Vd6fcZHgtU6PYbyL/Jxk8ZYs3pezjU6N3DCrRJ3C2CVjT5ydH+Nd/QiowNsqyfO+sb04L5uNUjoQvkK1/inZh2v8j7B+D3GJiObdrwGtuqn/wXblkkChLJNDhmdgQkCpyxL/0uFoqbssqWMJOjwlYWqP2ft3273a/82eD7b2GD5uyvFZKza38cffg90XHxtysqf5cK4+PJImiZAhQuRJvqwb+ZyOxVf3/14v0buYHLwNAIzodWOPxpfup2Aj9jliC6OjzeOfqCQ5ox3m/zonxTgnQmrxl1VFno4//pQu/CfAyW/aYbGdyVubwhdpEntPUuBx1zjKG3ismW7e421joCUiOltBUFeyQp92fD7EGl1N5Gle3P91jgtU3h0+++az2Tns/4WHib7g9X7p0bhZTdDRTYlbukcp2o30fvfwmfECaf0nR+SddDT+S/s2yb7Ut2HQomw5zkYPCSqao4dNGdbZrpH8hYWukxu6X1I3tX1svUd5IXi8Cza/AqjHYCNw9nDuDFxZF8L+f3Sb92X6JqvpR3027tHuXCBL/oxOEem8Id5Oyd7mh7w/hAuyK7Ns54/qmL7O98++Ql/hXScGbnec9x5V3/LiS4CT72NcPJOp04X4drHfUCsjPXVIk2++YymzAyLxpMbyBzdmwaZFkuWm9R/FEjpkKFNFqGfbtvd7DRmjvjUXb/O6AQulmHtzXefFEQ8Z5WHiqXnnPCjA9PSRYX77mWeu8jo425tsjwdz7CfQbg3WHgtuxAA1Mo77zjdtBFnUWe88zLeIT+iIyIYbN7WRwSQy+V7I2I6rSZtMEDJZ/i7rVsOB5XW8+3KTkWs8OrbpRmDjwzF/3gOK2d3fp3g3SXvsPKAj/sBv5oYoyLejzrKLWRPCwo9uOLCb4SXW6SEvnn+O8XbHuDV7P4d4u0NM9JrstnaZ72PDgEVoktH6HlLcsH/8QWtVN0+DcAvUi116h3b5ZoLiLnxbOsVtluHV9s1+OCzz6FT4sfgJqCnPOdKz+kzSpVEf7r0uucLMrVaXQmwjIHPO8usn2TCUtsNWAkvAmG9eUVzijWsbWmkbw621BC9dLAFJ5YIl+1YbX9+t39UIDH2DE2pF3WLoKjmirNzO8dtPGE0Lo+u8OB+3eIxLieQ1mvrsp6tkxMat3R7yU7KbLY6QxbkQidHcxdfcCDDH3y9cF/nRHYZs7XEPanJ3Nui64/wxz5//hXaa0FsOniP1ayE/Jx31Q6F41Er0pmyXs6+fmzyLnhrrM36MaW8KU0CW4vint/lWfA0oiepeHLZJfAMrvzQ0zKW6REjWv24ElmGXytrV/W8OjVo7JVtcPh5PcXLYypoY0r7j2T38zXVrrPZmnn3aA4d9rm6MFlt4srD90e49Xeb4692OZE1rzwbF0K2qHgyzNGtXkS7vB4zAPVV4ikaFOyh77FSTA9A8yva1N0+cps9j2rmoqnj3VK8hXl4nwk2UnbKHtn7z2NbfLdsyNiTv0bfyLaon7gZPcmDhvJ7pgAkZjRa/NXOpwFxEGozarT77+Jo2U/9t/+hgvBHw+pGETjN8q81jTInLJ7T7kp83F1QiyDXsIsOlkIQhNX3rNOajzZvyIf7+5juiJHVpBjfSeSuO+7hOGBIm+CW6+9TcUiwQQTiHYxaxCe/hXkebjJ/RMvzOQIuEMrtzUUeLvo2fa4VsK6EdL5397BFb8JBhaPEXaLzUG4HD5fMuRWy8U6cRqJu5RUWSy1O3Gl6DVXFKWhuV8/C+iovKIHXZZO5uHlyobrKkSuLUMS8eW3vxb5LIY+3k3yQiEOl7I/MNdBc1GD5BH9El3k3k52w/Kh6ZxAvTD0P/5cIQeaQv9+P3w9nfudMDl/VZz2koJn/zxqTzSwOLr2NK5kezkdzIXL3tnpC+HPMp6lv5bfpdp/mOscjLEq/N058j6GcELT50W/HCwJK8RV9ROm4BeP+UF5VpGmgzFy9M7jOv9M2pvTnzfW8W0m/EKLIU1hAQTMxJSRipqChQ4au9WQLGmDB2XdQJJevAUWO2T03ksD/iUvca1dMt1z1qohVtw/IEuSZfxfNM6pz8RxnLBb7IJj7gG9F/47Rm5N5k9LHMdul5j26y+wQ3G/c7Ore7pdu4LGtnOlxQjT5G7hrzIudDUomx5Zxa8ubl8g5Vcet86Cc7c93guJj2cJv+dOfRleYO4VVWcVF+SvYHNOous2nh//LMj4xNc5+K+NSuGiS8mbdVZ7ZkxsBdytd5bWJHzssiyYuRkZSJOHU+PXuNG1t9/FEh3l9jPsBdG5H4JQDpu74Aev2Xg+nMboNBVR13dEzzb7nHGaqGiP/aSTjiAMR2MNuu1jqWNPt2HAw1Q4wkcWvBmxv8aXnK95e1qG6Tk23IeIo2HTqNKtCjnWaFBka+IxlEsZysfc0wsZqb447uHMZhbLsmTEe17cppVJm+3A52xo4kzb4dB0PNECN5i4pjUtbOJU1QGDqCjcuw8u0ZT1qqX6cxBju2tIpiE16eofUyuUGvrR5yHo8dddvJPHq0FzCjlz+67Vf+dlRya/kaSUWOO3ObxdMuaaQMdPw78dBUDjiid+hrgr79gdLT4znNHD/AHo6+TLxevAxHI7DzgHTVR7HyKS5bjQdwGH6d5ofu0fBWx7KVzygUgdE5HdbZbV46OFAONUNM0vo9yI8ylt4ODn2Dox6EmwrxsWUMn/M0NYNY8DxN/8zrq79ZgzBOYLmxoBdZ+c3lNp2uG/wzut0RmDdyvz/b0IyTN9NQhzgb8gQ5pS1r1gH/i0riLe2hqfe5ZUsyrF+UZb5LyMi2Pbz5fsqLehPwmOCdxv3uCe3P9dF8+YVD/pts/7d6N/KPXxiilqV7lD7+yha8O6dVckqTHebjH7+8FEQdGmR4YFrkStgm/0Nokni11BercXqZZ2VVxFhV4nxNsl1yilNIHI7YcHrXCu+b5Uuu0Km+q8oqUFqTDlsOMSxrHuHu+144u6NTyasXFCZMoII7SnPyxCICZRg/ri95pbz6kDX++X+rHf3rh06XcbmL9+L0wbDfq7kZ2AcYoguDYM1+7L2AjZLLpM+Wv9mA9s8zOqN9E8GhjygQXaugdpV/y9J8iDxGBnX4kR3M//z110AYAzlneJJQBEFbL/8EQIPlMun4eiFQi+iQIXKbRhMxFoQpmMSiUczLMBbKjklVFRxdRt3x0V/mxhZlxqSijBvYH8Kg2cJgNptG1ZwNft35fpPROWr/L8VcV06Pbf/bJFhjGWYZ4YqCoAvUUBhkcfIYLcqGzNyLgBPohyMMJjSKPwqcIA0tB07D8c8scOrdxTzaJbKulx8z9I6AdFPDj2u3KbCfo2z4Z7YmvZPbxQmrvHYJbtlPUBnd5+dip9jrtXXZvV7/4yTGRXAuhXgJjK1e5AnApXamlfSpdricHWYPcVG7jnsxFxuGmdWQzwEzhdP1bDBrP6+rsmXQkk0o24wls1meLdGQ9QjTmLH5F+Lz42vCpbgLvJROdJOiq/tHG2qyPu/gvceWabsohkF2mPLN2DBaKhs7NvchQoRHPvmKiueH+kW/dGxpImZQmQKrPaCFNfSFC6moYbBh9V2jeJsbE/0qTvJeb1kLc45pCE8bWpbzEtmsyheDq9WcWM4IqunPLO1OrWY+tKxzNzzG5OSsmPDgku6WaY4tWPsBJiPNGg4xWzgyqJALMXZAwxodKUtgeRCoWY+/Hwtk3S1dYVHYW80XbgFgm/5LZ2/eZv7acY/V7xBuLamj+jSvxw1uatgWWF9hrmgS+MGhB+Rs9RRhHI9ZFUyARIPQC5KOTWItLASTumsdYNQl4/xDoNAGBbPBTx60YyrcVXifgvn6ikVFEcj6sjBG88tiiy1ZPaYYcSywNBuUuogdHkGk2T9y8XBJS/1vawcAGGRm4UOvAO34Obw4MNhP0QkhwTA3nzdcF3WkT/qqW1p3NVjz0P84yUeGj/MEsRLYznQCT4AqZVQrSZfqyDczIUuzQLYwEJvFlc0YzwArebSzqVH1Oknrd6lwpDcnTOmceG3s3nrRYNQXq/z50dAGvI1gIRb68eKi9DKs9GWb+Xh1Etl8vGbDVZMd6b4qSDyUkiyIoi4llhRcPQE9osOPk4AL4pzlByQIAjNYYWFgBopl0i+d6GwWrHVBn9aBr45bhofhx9XjqBdlFdjpYkKtAzsdtwwPw4+rx04vyiqwUwezWgduak7Zy1ryw+rxQsRYDVaawGeRyLTtSAVEScOjwEH3c5j7fNNR9ICWVhCT3mry+fDCR6ymowprTxj52ux4CoXToEsVj1zFH0sYBoG8SqZAo3F8dhk+jQKyLwCsult9GRBUA/8DgtQaIHMDVBMLfurzTisLuqhjLt0EmRB8kx54uQJucWBbkQVcIuimsnijgTevpXvzvU5QGacX5+qpbnFHCrlP8qItnkoCLuaninD1FlApng0gZ8PidV6cj+RQ1jfw5PeLfZ9MS9Svq8fFIMt6QEAy+E6NgiZtsACD9udt4ABIjbxoIETkv78X+fkkRQFFIgxe+/MkHyLSochCIOjIFBMQPEYdDXwtwYQAfNuPWEi8zGB0zMdyWotDyBew+JCxPWboQkJo8tWL5bhOun4hbM0GIpIgJYCTnGrtQvpkGml/WbtvXCOGSUeswmce/VVsmqcFzZTLXHPUzL7C7cIzfSzjA/ojwdwUzxEcV2qhYbhozkF+WILNBOJixDLpd/ZIXCDWgLlibyN+AIiZ25S58EU4nPfDRzziloyonkuRg4CucJNhZxBkTYBZfgyTeWAzYbwSO+DMHaXk9+SxuoyLfXR7rpM+lmj/KameJDK4D6PmWVLHBdPY8GM4S2I8TiNB0ctihAlwKGaHCLPWgQVyHdJAtgbiHORngkWPFQI8oc163dNVXBbWPtJTYaGroSVBbbI1kjPOmBGdd9n0Pq/Q8tfZNZciB82v68bQIMjy19l36BtG+22OGyg747SK80mAcYYdsHz1Z5eQVKs4yYRw5vUjqFmOLwcuk5khV6wwwzJvYD+SH3XJX7KOSSFYX8C8EpMBqJdj+Z+xjlVyYgTz7TpqgZEjHDiwBcHijBoPrCcQGR9L9BXmvaTtQlNOF8jm5zX9L5zSZ1ytVOciq5PpoAC+xoHWwxTL3NKGKdnAGpiWZxWr3/4Bz/RW5Wd4LKl5mXNtmxNGLuOiWbK8Pmf7FCmvJ9prFYoSuPGiS61gwnPELUn4wpA3WjJdhAGQIJtJnwOLy4HQKr5SS4DZlN8qJ3TN/rkSsLX8O/glAGvCG3knXM19Md/2f39Cu+SxfX14UVVF8vlcoRVgTMk/xJeMcu3oU4u3BijCrH841X9GJnLRpkePBzMghDKNClkNuOwIw5xEuUNorAVVCWu0mQAbWKhxNZc2NHB+SNs8GmyzG2yVBHNjvls6PMQHfQyzpawiBp4hXujSjawWKJEsVggzhh8DwKWJuKMeU2AwfwSImQ74nCjD5LMD7c+4SOKs6m3rZX78nGSEMJKnjKffw9BEzDsYpsDqWFDPGwQtJXmYJ15SBQVFm0pQEy5otpeMv9XszRcP1Om/u2MxOvdW3QCe/zzjEjxKH7NEjlGGiMYCW7BJ8yhX0KKhR7O9NPyt1yaaQHKz1m/NJq+DnP5IUiAEhnzqY8jFonHSY0cPsFzMWSMvxJ9xeu5BqpYwDC6mRS4R14TNljAkhp3wFAbKjbQWeOYbmBvWsy8nTZYM614Yrm71F93Hx1OKrvJvWZrH8ifCPQFz/tL/uHA0wOLNjwRW+bNh4SFBBZZvh5g47Fp/K0JkGn+dYCLIV6znnuGF+nUTwdYHeUw6o7lbAKyWv6WcB0QTbhvt8LOQjeLgjto6Ei8fSALndt7M6wOWKJJJp8xwLghmXgGmXQL9RIm608Xho9/LXSdpVce7Df68YSozxUmmZlIg3s6zCa2s5tZt3mcUVV7UC6/kGBfPb77vnuLsgO7iCl2eC9zF7lmxvG8J2KV996O5kSMssP50zS+BFuSQXGFA0shhZsGkA7AMaJA/fmJiBkwwmp8vG3y8+3KTYV52X+7Pn8tdkTS+4qt4dSVhns0VL6NZ/RssmWQ2BwSLw93yd3jLA92E6/gxmJt7NX+xS4GEu36POQNhrmedfTg//LqJM85BntWccV48PiYp/gVFE+U86TtkGxp+XXvgg0EUk87mDqhCUvQEXTjJoSD0LaZC2tiKR5RpFWsdLN9XDOh3GKjxAUXXRX6cDiVs59y6iS1aPT44gUx6pAdjKQB5yH/CYyHwGIZixmVrlXzFKn+bHyLq3/VAyRcbHB27auTKJlrB9r3KuAm1dlHpLAzQaKGMVjEsi4uA2ioOgOZD1ZR2yxZOs5usyye0+5KfeQ9a4Wc5tgRKBmRi6SRog8VSsxbSR1ajzzBglEhohEu+6oxxMHfnAkt8uI2fSSzmmyyp2/UUklm+3OI75gJa8oXrjrMsyGPUJzUSi8FHF9ldLZGvcQ4WSRUUSsla2JDxbgDxC0qbCPJ83dnw+TbfxWnyb7TveIzextnhLOxqqNHvCejhHn6cBIEC2ywzYmkQ1MGqCoM2USaTTjsOZ8SX7w1AOP9vfu2/jTX/atb67a3eHfqaoG9/oPT0eE6zOghxPSRD4+TUs8cU/r2ql10PeSSrr4Vd2waIvr7M2iVT5AO4aQaIDF7AjMJcJ9Ekx2QmI2LV95w4ZAp0fgUtlXTMZ0OUX1cBSBkLBBNTeT5XlTQ/XOZHsk80NGpUlantGN0145JC/74hayVVtVV3E8Oo/vdtzmd54EaREPBD2Pw4jaOTPZI8WSlYPcvAT8fbbDh6j76VluaIqjK1OaK7pptkft+QOZKq2qq7iWFU/1v5iKUn4IdwwicrDkjyZI5g9SwDPx1v86278zT9M6+S7NA6V5ru+7h6k+/3+P6ZVblQuCETpda8VZ9z4Kv+4SIrvylOpigSflS7n6d5ZTcGY752fBJ1LQhbA4vLOD1wOrqa9bzK/MBi3ZbL0+nF5IBa/qsWRzit7wWLPYamfbfyhsSGqicVroGK7pg/36PrpCirq7iKP8eleM9X17pHVffG8znbvYtJKqVf/tYU0m8u6dL73RM6xv/4Zf85x+Mef0756oIJYrt68/2UFxXWQ5q3t9lCfyIJ1KlIZdQzHp7HpH6JI+m2L5f32ZNoOqxb2J9T9BCXXyC1MsWgXhkKTW9swDWhN7YY6o2l0PaGzmj/5hgn6UVVxbsnyVhK6OD+QVINI0NUMaHvoQjqbig1F1UtoFYsPUSJ8LtdfoYnBlMMApSh0PTWuiW8Q9VTDumPK4f640h0HWojQ4g8aKuAbGlr2XJqwpohL6ady7tUdqSfMUkpA9hQBM6YvlTTw2VcoQPxAxJ6GIqgHoZSnYqSXXUuICvel4Aq6grNRkDRC0egGI+oJYpMv5CdmuvUzkmBJIYVpFKNGkOoY4FJlSH2zRSDnTIUZtpWoEagUOm7ozJWeFvxXZydH2MyVgpLxFKp+KApjXnRMKHvnaXQTVR+QynOV54CnLY8kWG3iv7UHWl76J/KCh30JVD7faFOgHpCwfatKwHZ7wp1C8c2Qbf0y8wTgItHjkbXZ4WnDYbuV7wxguY8Vw72yJJoOhyuJ4W+hiKom6FU08Pgrij0MBRBPQyluh5qfz10XxXkBKtscq4BvYFkYM8gpYaL4WZF6HkognqrS/XWkToGFY0iVQjaQqrcoB9JD/K2jU1NE31Bam6aYpXJaSh0MqDimJQl3qN3h7SiPAIJKJtApd3c4mVtFqcX5+qpPkdoMhRKuVCTw1tfVQ0Nd+QltmSyU2VQv6S4jIwmPKF9yE/JTtZPW6joqKUw6UnWibL9lsKk/d+L/HySddIWKnpqKTQ9tW9ShE7a36H22yLDZezH+vVs/zBAuo5lyVQLWZZSw8XvySNeNhZ7DRcwGcQFTGnIhaJndW9mwyj5CFBl0uE02pgSyvd5BVlSqkzaSVOs6eQOfcMC3+ZJVpXyAQOpoI5BQoP1l2TbNRTJ1lxGG6yOUDJibLGqJ6Nxu0N4O5DVG0AEmmCuHFYjQ6IVLydrTwxduYg8CSwmT2W2l5OlNZbt6mT0qv2dJMe06U5PnS1a1JeSHNSdsoYTd8Z82XBkOqYkRatsAEmhYrRIuVk/yrx0sv6VlRR8Keu58WvOpObcisuzZHueYsCQGSfWSJFkHjJVS0tuMWptDQ13VH4AgRWqDOqXKtbu43PwxLL9Hd635wZnokP8VmAX1RXBO6iu1IR1xTUkUywVxBSlD7g+IgP5GdwacuXgqLAkugMPWZRH8fxDRgkeh8iILU4aH9DxlMbgTIXJdCePA6XhVYGCA5FEdXVg3HP3YZB3LFCovi+m3f6OMoQnsso4iiTg1kCg0p2JDvEfxVPRoQw8Fx2KNZ18LOSdUGVQJ1SxTpIhfqEoyVAGSjIU6+YHiksM5U8oOTxBi2auHJwRLIlZh1cJXteXsGEQSRTdUlQmpw0amwTQKM4erCwRHzkMmIgsgeqciCPVzgkq8g8wK6hSeF5QBOZdNYG0VN01FJouGyKtgT2e4uQADelQBBvUrlSLW6Jr1TeEp4BRyxEZnHq/RVWFCg1yZYSyE3GIVqdkMcSQqG2RBlS7SGbbuWy5KyM0YsNsiSsNCAKcOUgo4dMHCbElOwZ8mDFgdjPERqeAr4VYGumdEEumFbueQXu5HwlPAIvM0uhQWORliRtP5b2KJCDyBCqtqiHbLbXZJrYa/6uSbEK6EnD/0RXqth7xd4WzA1MKbjtoAl1XktsQxUWI2Q1IdxqvEEQkUR3rRxenU5qg/UPe0idIfznZVZaDTqAw46El13PAXrPLHQMkdPqb+ztUYhNMfLUNHBPa20q5/4NAobr0bIkMBoK/sNTcu6rJTW5Eo5rU+Eiq91Axuw42vxZmkgPYnpCZHGwanm9GA50tF0AwCRkzAKmCJ4DawCekf5wpHovQpTLPkJ7AYPko74oplS0UTbsSn26J+hVIZB4PLJXNCOuG1WAshQ6pVwkSR/uIdWKnKkBu9yw1/3yCSgwFuNZjBtQu80wL4GsA0oTayf8FK7KxOoaHClHftEwZAK13QYA2xEcZVDOKhxb2KgFd/aNriVIU1HKR+HcBRBSZzz9TU/m0gTRj9lJhlFoi1pNfqRSWVjHKwOuCZoQVrwZkqpEpJBg6mD5MQUJX8irWQhDD+qFHvT+6qB8JpVwozvmdiCHxn2frgb7zTXWuyLv43RpfLz78ghB6Yynyvxjxe093xbgLNP5HnN8CkorSHaq9mKodYnTf+siKkptUU3w/uFcyzfdD8gIGrAmqRSwMq56HuDggcEaYVPM05gtTT7e3t8IOXykkciDbI5SFVIwNavhK/k3qTGoBHk6VSpWAFULiBHghxjSievLl/LGN2Ldd0k8tS6cQBnhr1kiheEPmiit3sfvv6GCapKKLtCEMqeTZHC2+RzPKi6ZfawmkwRZbE6mAedCnWHKBdP6XXdBTQVJZ+QTQeeS554x0F1IIyOt4FgtChbQhsDyImvSTBCQPNlEmVgl7kRF1Nxmovf5QL9/NKyuOCcGHlM1BofJ5pKIV7ooHaEz2zDOIAuVLNfPKiq8TIKxEvKWojH6EGvXtAuoBCQOoAnpY26hA+V7WXvT2tk4lNE/iX1zu5pHUkz2rdheRU51cUJbQ+yhNKHp/49hfyiqMp5xYMeLcq/xmyCUv7sGakAKEshCKkBtBObGXcVyCGl4naYoNad+yQgkcaTgVGGFohNBdWMWhabnUAm3IKcBF0mAakIXHsFcDFCZgyOkE6EJdQS4WH0KByCMLj8DWVIRGaFoxCXVgr5ouOoRSHSJRCBXwES5ITWn0CntRu+AOSlFFohCi8iEoSM3hx/GbqzxNlWKyBCFEpONTNHsmKP6Em2hNiIyoaREWjiYZyaRQgw3v0deTBO5wEJH33aKdplTLGLOKCuEk8TgaETUxNpQtQY5kYKvK+CHeVSlfCJlV1H/gBIFVAi5ShaDrntFi2hJ9HlYTOqVPoxqD5fXkaJpLRao4OMrlqFnFEGgyiQ1033h6WcT4sVddH+lHqSeAKoRShJBEpCr1qx9xiTO9Xl6OLJjAzHOAQWLY5d9R5IiOTySRl6bRsMxEQRpYBqMbibXFin5Htm1SOawNzWhG5xlNMhPpZlUzlqLzyPrE85aEKjI4VQHp/B+pMAG6SC04/paroCrbxFGEMEvhxIPihUWDf4XckQOuoPBgGO3IAUUnY1pRRhvzpJpW74Z6aahHj+si1NFHQJPrgCfxK7gQvW2o5+kAZeBfcSctEvm/iQ4vahczL7rF+7OnuET7T0n1RPUgCq6r4k0cpi4fFZBUlUb8c1cEM2+H9uVqgCt4EgSsKZ3xRuEWPanmIz3uxvpha/m1C3Opp4/WqDGIFEkAg0hHmhzqgUEk7UUE4kIql0JK+hALI0UcTNKISWBLP2rRzgt9Jb/omEc1XchN+ZzgKPwKzQclve+8aDy599IRRaOhXbmULKEnxsGawvdVGf3UdSPUeSRpt3w84Zr2fEwcV43BAynDmDogPm07k1VhZ0cccBsMtpTW/3iHdpvgw+hGdDYiaIbLybU7AjFnEr03kKdB4mY+HEC4nf2aoMAeFKSaGXLiEJNjdlUoNoxSWv/7xqnVoAwMbeDYranoX0FGka/p1gyjMTsgSBF4WhI1GwSXQzMK/Oj1YqYQg1bZIN6KtiXBuH2jVd25LYSVrU2sqoXOADEoj9LLRU4dzEyIoYfoFujSIMpQ+KnIqbVCSaQBxFiGShTB301fWRo3oTh19/UA0yBwPq1Rm9D3QVSs/4ib1A42RdemTjZ/sJNOmSbkCoJSHRNVqFIYr1XFDjCdHpsm2gqvIuVy0riuVnil1GbrxkWqkYT4jaTdGqtU3c4kSjFplwmTrGocDn3svhAyNJSTW8OwARSi+/h4StEQgk0uOEepWLo4B3kLK3KfroXxi4VEllDqD5LcPWeFNDSkujzDzBjxFd8ukcj/1yq8qMKxZntWqv9oS6sE+2i7nsx6UYuVQjativ67cp2kVe1BqDyLt6k+zdm8jiN10wKxhxf/OYnolxzj4vnN991TnB3QXYyX533iKeBwUVtJZYLZTFit+YWzXLFnYHRyruawC8y9NVIJ5A9j6VnqdYktSX6lvL7R1glxi6NJ/kUaMs3k5U9NCqOsq+LfNM+roj7FlXbJJqEMuWQTUnc1N8vSrFwO4nd5sSLFjbhI5P8qXMjt1dSUpu1yfNZgbC0U1CHshDQV133/zMEz8NmMWdF1kR9V+lCRh1AInBysNSrKXF+jVfGQWyiCIl67GqgUY5GQbgyyh3JylUkD06G1hk2Z40zWjqwNzypRgQKkC4GHacSGs4BFQI4ywH3KtK5CPbK0ao2edJnS1G2Jx3+GOc9cnO7Y7GHRTZZUSZwqHGpVFeRCujnTwjnS7hv/O03es/HK6ByGxa70epHWDSSusiXYE9kscZ1jCCA6K5w+/g9MLRdwZPAfKPtd04Q2oZ2LOtSGObhB5g2xHwPc7puETEpR/V28zLOyKuKkToaCl2G9fF3Aq4c8ktVXnDT570uvboeoWdCWU5rJit6D6rNSeRwmpsBK6WxNQ9Hl8i5afVTeLlOwMam+gNOLcS2GRyyQ6aw54FCkMBun2CHwmFpdPZ2aeePAZjOITSVnMx19Jp+bqKCRLYbHE5DOjrShylM3TrFDdDe1uno6NfPG0eNmEJvPu2f8RRTy9QEfAF9tT/CVleQxbD4PutyEHtROB6Uz0CRFrhbKKv7dvCqhP8JOCzPtIsSt1YnXeOYLndFK1l9cc4TBLq09iv7qRdNIrXg8yqjoy169aLKAtj/gP/GGFe/33uV7lJbk11cv7s649hE1f12hMjkMTbzCbWaI5CseGu1obrLHvNv1cRx1JF1xl3QDVfE+ruKLokrq1AS4eIdXnSS/LTmfqdMXfkb7m+zDuTqdKywyOn5OmT3lqxfq/l+9EHh+1bw3KH2IgNlMsAjoQ/b6nKT7nu/rOC25fbSsiUus/d8R/r0ZSzw1K3R47lt6n2eGDbXqu0InlO3xlHtAx1Od5738kN3HX5ELbx9L9BYd4t3zbR0knRw3yRrRDwSr9ldXSXwo4mPZtjHUx39iDO+P3//n/wENr7KMtqEIAA== + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.Designer.cs new file mode 100644 index 0000000000..766e90a409 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.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 ExportFramework3 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ExportFramework3)); + + string IMigrationMetadata.Id + { + get { return "201511271019577_ExportFramework3"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.cs b/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.cs new file mode 100644 index 0000000000..ffbf880d57 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.cs @@ -0,0 +1,142 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using Core.Domain.Customers; + using Core.Domain.Security; + using Setup; + + public partial class ExportFramework3 : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ExportProfile", "SystemName", c => c.String(maxLength: 400)); + AddColumn("dbo.ExportProfile", "IsSystemProfile", c => c.Boolean(nullable: false)); + DropColumn("dbo.PaymentMethod", "ExcludedCustomerRoleIds"); + DropColumn("dbo.PaymentMethod", "ExcludedCountryIds"); + DropColumn("dbo.PaymentMethod", "ExcludedShippingMethodIds"); + DropColumn("dbo.PaymentMethod", "CountryExclusionContextId"); + DropColumn("dbo.PaymentMethod", "MinimumOrderAmount"); + DropColumn("dbo.PaymentMethod", "MaximumOrderAmount"); + DropColumn("dbo.PaymentMethod", "AmountRestrictionContextId"); + DropColumn("dbo.ShippingMethod", "ExcludedCustomerRoleIds"); + DropColumn("dbo.ShippingMethod", "CountryExclusionContextId"); + } + + public override void Down() + { + AddColumn("dbo.ShippingMethod", "CountryExclusionContextId", c => c.Int(nullable: false)); + AddColumn("dbo.ShippingMethod", "ExcludedCustomerRoleIds", c => c.String(maxLength: 500)); + AddColumn("dbo.PaymentMethod", "AmountRestrictionContextId", c => c.Int(nullable: false)); + AddColumn("dbo.PaymentMethod", "MaximumOrderAmount", c => c.Decimal(precision: 18, scale: 4)); + AddColumn("dbo.PaymentMethod", "MinimumOrderAmount", c => c.Decimal(precision: 18, scale: 4)); + AddColumn("dbo.PaymentMethod", "CountryExclusionContextId", c => c.Int(nullable: false)); + AddColumn("dbo.PaymentMethod", "ExcludedShippingMethodIds", c => c.String(maxLength: 500)); + AddColumn("dbo.PaymentMethod", "ExcludedCountryIds", c => c.String(maxLength: 2000)); + AddColumn("dbo.PaymentMethod", "ExcludedCustomerRoleIds", c => c.String(maxLength: 500)); + DropColumn("dbo.ExportProfile", "IsSystemProfile"); + DropColumn("dbo.ExportProfile", "SystemName"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + var permissionMigrator = new PermissionMigrator(context); + + permissionMigrator.AddPermission(new PermissionRecord + { + Name = "Admin area. Manage Url Records", + SystemName = "ManageUrlRecords", + Category = "Configuration" + }, new string[] { SystemCustomerRoleNames.Administrators }); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Common.Export.PDF", "PDF Export", "PDF Export"); + builder.AddOrUpdate("Admin.Common.TemporaryFiles", "Temporary files", "Temporre Dateien"); + builder.AddOrUpdate("Admin.Common.PublicFiles", "Public files", "ffentliche Dateien"); + builder.AddOrUpdate("Admin.Common.Of", "of", "von"); + + builder.AddOrUpdate("Admin.Common.NoTempFilesFound", + "There are no temporary files.", + "Es sind keine temporren Dateien vorhanden."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.NewsLetterSubscription", + "Newsletter Subscribers", + "Newsletter Abonnenten"); + + builder.AddOrUpdate("Admin.DataExchange.Export.SystemName", + "System name of profile", + "Systemname des Profils", + "The system name of the export profile.", + "Der Systemname des Exportprofils."); + + builder.AddOrUpdate("Admin.DataExchange.Export.IsSystemProfile", + "System profile", + "Systemprofil", + "Indicates whether the export profile is a system profile. System profiles cannot be removed.", + "Gibt an, ob es sich bei dem Exportprofil um eine Systemprofil handelt. Systemprofile knnen nicht entfernt werden."); + + builder.AddOrUpdate("Admin.DataExchange.Export.CannotDeleteSystemProfile", + "Cannot delete a system export profile.", + "Ein System-Exportprofil kann nicht gelscht werden."); + + builder.AddOrUpdate("Admin.DataExchange.Export.MissingSystemProfile", + "The system export profile {0} was not found.", + "Das System-Exportprofil {0} wurde nicht gefunden."); + + builder.AddOrUpdate("Admin.DataExchange.Export.ExportFiles", + "Export files", + "Exportdateien"); + + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.RestrictionNote", + "There were no possibilities found to restrict payment methods.", + "Es wurden keine Mglichkeiten zur Einschrnkung von Zahlungsarten gefunden."); + + builder.AddOrUpdate("Admin.Configuration.Shipping.Methods.RestrictionNote", + "There were no possibilities found to restrict shipping methods.", + "Es wurden keine Mglichkeiten zur Einschrnkung von Versandarten gefunden."); + + + + builder.Delete( + "Admin.Configuration.Payment.Methods.ExcludedCustomerRole", + "Admin.Configuration.Payment.Methods.ExcludedShippingMethod", + "Admin.Configuration.Payment.Methods.ExcludedCountry", + "Admin.Configuration.Payment.Methods.MinimumOrderAmount", + "Admin.Configuration.Payment.Methods.MaximumOrderAmount", + "Admin.Configuration.Restrictions.AmountRestrictionContext", + "Enums.SmartStore.Core.Domain.Common.AmountRestrictionContextType.SubtotalAmount", + "Enums.SmartStore.Core.Domain.Common.AmountRestrictionContextType.TotalAmount", + "Enums.SmartStore.Core.Domain.Common.CountryRestrictionContextType.BillingAddress", + "Enums.SmartStore.Core.Domain.Common.CountryRestrictionContextType.ShippingAddress", + "Admin.Configuration.Shipping.Methods.ExcludedCustomerRole", + "Admin.Configuration.Shipping.Methods.ExcludedCountry", + "Admin.Configuration.Restrictions.CountryExclusionContext", + "Admin.Common.ExportToXml.All", + "Admin.Common.ExportToXml.Selected", + "Admin.Common.ExportToCsv.All", + "Admin.Common.ExportToCsv.Selected", + "Admin.Common.ExportToCsv", + "Admin.Common.ExportToPdf.TocTitle", + "PDFProductCatalog.SKU", + "PDFProductCatalog.Price", + "PDFProductCatalog.Manufacturer", + "PDFProductCatalog.Weight", + "PDFProductCatalog.Length", + "PDFProductCatalog.Width", + "PDFProductCatalog.Height", + "PDFProductCatalog.SpecificationAttributes", + "PDFProductCatalog.BundledItems", + "PDFProductCatalog.AssociatedProducts" + ); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.resx b/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.resx new file mode 100644 index 0000000000..9c408d2cf0 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.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/201512151526290_ImportFramework.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.Designer.cs new file mode 100644 index 0000000000..a112acac53 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.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 ImportFramework : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ImportFramework)); + + string IMigrationMetadata.Id + { + get { return "201512151526290_ImportFramework"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs new file mode 100644 index 0000000000..a7d561921c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs @@ -0,0 +1,421 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using Core.Domain.Customers; + using Core.Domain.Security; + using Core.Domain.Seo; + using Setup; + + public partial class ImportFramework : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + CreateTable( + "dbo.ImportProfile", + c => new + { + Id = c.Int(nullable: false, identity: true), + Name = c.String(nullable: false, maxLength: 100), + FolderName = c.String(nullable: false, maxLength: 100), + FileTypeId = c.Int(nullable: false), + EntityTypeId = c.Int(nullable: false), + Enabled = c.Boolean(nullable: false), + Skip = c.Int(nullable: false), + Take = c.Int(nullable: false), + FileTypeConfiguration = c.String(), + ColumnMapping = c.String(), + SchedulingTaskId = c.Int(nullable: false), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.ScheduleTask", t => t.SchedulingTaskId) + .Index(t => t.SchedulingTaskId); + + } + + public override void Down() + { + DropForeignKey("dbo.ImportProfile", "SchedulingTaskId", "dbo.ScheduleTask"); + DropIndex("dbo.ImportProfile", new[] { "SchedulingTaskId" }); + DropTable("dbo.ImportProfile"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + var permissionMigrator = new PermissionMigrator(context); + var activityLogMigrator = new ActivityLogTypeMigrator(context); + + permissionMigrator.AddPermission(new PermissionRecord + { + Name = "Admin area. Manage Imports", + SystemName = "ManageImports", + Category = "Configuration" + }, new string[] { SystemCustomerRoleNames.Administrators }); + + activityLogMigrator.AddActivityLogType("DeleteOrder", "Delete order", "Auftrag gelscht"); + + context.MigrateSettings(x => + { + var seoSettings = new SeoSettings(); + x.Add("seosettings.seonamecharconversion", seoSettings.SeoNameCharConversion); + }); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Common.RecordsSkip", + "Skip", + "berspringen", + "Specifies the number of records to be skipped.", + "Legt die Anzahl der zu berspringenden Datenstze fest."); + + builder.AddOrUpdate("Common.Unknown", "Unknown", "Unbekannt"); + builder.AddOrUpdate("Common.Unavailable", "Unavailable", "Nicht verfgbar"); + builder.AddOrUpdate("Common.Language", "Language", "Sprache"); + builder.AddOrUpdate("Admin.Common.ImportFile", "Import file", "Importdatei"); + builder.AddOrUpdate("Admin.Common.ImportFiles", "Import files", "Importdateien"); + builder.AddOrUpdate("Admin.Common.CsvConfiguration", "CSV Configuration", "CSV Konfiguration"); + + builder.AddOrUpdate("Admin.Common.RecordsTake", + "Limit", + "Begrenzen", + "Specifies the maximum number of records to be processed.", + "Legt die maximale Anzahl der zu verarbeitenden Datenstze fest."); + + builder.AddOrUpdate("Admin.Common.FileTypeMustEqual", + "The file must be of the type {0}.", + "Die Datei muss vom Typ {0} sein."); + + builder.AddOrUpdate("Admin.DataExchange.Import.NoProfiles", + "There were no import profiles found.", + "Es wurden keine Importprofile gefunden."); + + builder.AddOrUpdate("Admin.DataExchange.Import.Name", + "Name of profile", + "Name des Profils", + "Specifies the name of the import profile.", + "Legt den Namen des Importprofils fest."); + + builder.AddOrUpdate("Admin.DataExchange.Import.ProgressInfo", + "{0} of {1} records processed", + "{0} von {1} Datenstzen verarbeitet"); + + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ImportEntityType.Product", "Product", "Produkt"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ImportEntityType.Customer", "Customer", "Kunde"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ImportEntityType.NewsLetterSubscription", "Newsletter Subscriber", "Newsletter Abonnent"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ImportEntityType.Category", "Category", "Warengruppe"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ImportFileType.CSV", "Delimiter separated values (.csv, .txt, .tab)", "Trennzeichen getrennte Werte (.csv, .txt, .tab)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ImportFileType.XLSX", "Excel (.xlsx)", "Excel (.xlsx)"); + + builder.AddOrUpdate("Admin.DataExchange.Import.FileUpload", + "Upload import file...", + "Importdatei hochladen..."); + + builder.AddOrUpdate("Admin.DataExchange.Import.MissingImportFile", + "Please upload an import file.", + "Bitte laden Sie eine Importdatei hoch."); + + builder.AddOrUpdate("Admin.DataExchange.Csv.QuoteAllFields", + "Quote all fields", + "Alle Felder in Anfhrungszeichen", + "Specifies whether to set quotation marks around all field values.", + "Legt fest, ob die Werte aller Felder in Anfhrungszeichen gestellt werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Csv.TrimValues", + "Trim values", + "berflssige Leerzeichen entfernen", + "Specifies whether to remove space characters at start and end of a field value.", + "Legt fest, ob Leerzeichen am Anfang und am Ende eines Feldwertes entfernt werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Csv.SupportsMultiline", + "Supports multilines", + "Mehrzeilen erlaubt", + "Specifies whether field values with multilines are supported.", + "Legt fest, ob mehrzeilige Feldwerte untersttzt werden."); + + builder.AddOrUpdate("Admin.DataExchange.Csv.Delimiter", + "Delimiter", + "Trennzeichen", + "Specifies the field separator.", + "Legt das zu verwendende Trennzeichen fr die Felder fest."); + + builder.AddOrUpdate("Admin.DataExchange.Csv.Quote", + "Quote character", + "Anfhrungszeichen", + "Specifies the quotation character.", + "Legt das zu verwendende Anfhrungszeichen fest."); + + builder.AddOrUpdate("Admin.DataExchange.Csv.Escape", + "Inner quote character", + "Inneres Anfhrungszeichen", + "Specifies the inner quote character used for escaping.", + "Legt das innere Anfhrungszeichen (Escaping) fest."); + + builder.AddOrUpdate("Admin.DataExchange.Csv.Delimiter.Validation", + "Please enter a valid delimiter.", + "Geben Sie bitte ein gltiges Trennzeichen ein."); + + builder.AddOrUpdate("Admin.DataExchange.Csv.Quote.Validation", + "Please enter a valid quote character.", + "Geben Sie bitte ein gltiges Anfhrungszeichen ein."); + + builder.AddOrUpdate("Admin.DataExchange.Csv.Escape.Validation", + "Please enter a valid inner quote character (escaping).", + "Geben Sie bitte ein gltiges, inneres Anfhrungszeichen (Escaping) ein."); + + builder.AddOrUpdate("Admin.DataExchange.Csv.EscapeDelimiter.Validation", + "Delimiter and inner quote character cannot be equal in CSV files.", + "Trennzeichen und inneres Anfhrungszeichen knnen in CSV Dateien nicht gleich sein."); + + builder.AddOrUpdate("Admin.DataExchange.Csv.QuoteDelimiter.Validation", + "Delimiter and quote character cannot be equal in CSV files.", + "Trennzeichen und Anfhrungszeichen knnen in CSV Dateien nicht gleich sein."); + + + builder.AddOrUpdate("Admin.Catalog.Products.Fields.BasePriceMeasureUnit", "Base price measure unit", "Grundpreis Maeinheit"); + builder.AddOrUpdate("Admin.Catalog.Products.Fields.ApprovedRatingSum", "Approved rating sum", "Summe genehmigter Bewertungen"); + builder.AddOrUpdate("Admin.Catalog.Products.Fields.NotApprovedRatingSum", "Not approved rating sum", "Summe nicht genehmigter Bewertungen"); + builder.AddOrUpdate("Admin.Catalog.Products.Fields.ApprovedTotalReviews", "Approved total reviews", "Summe genehmigter Rezensionen"); + builder.AddOrUpdate("Admin.Catalog.Products.Fields.NotApprovedTotalReviews", "Not approved total reviews", "Summe nicht genehmigter Rezensionen"); + builder.AddOrUpdate("Admin.Catalog.Products.Fields.HasTierPrices", "Has tier prices", "Hat Staffelpreise"); + builder.AddOrUpdate("Admin.Catalog.Products.Fields.LowestAttributeCombinationPrice", "Lowest attribute combination price", "Niedrigster Attributkombinationspreis"); + builder.AddOrUpdate("Admin.Catalog.Products.Fields.HasDiscountsApplied", "Has discounts applied", "Hat angewendete Rabatte"); + + builder.AddOrUpdate("Admin.Catalog.Categories.Fields.ParentCategory", "Parent category", "bergeordnete Warengruppe"); + + builder.AddOrUpdate("Admin.Customers.Customers.Fields.CustomerGuid", "Customer GUID", "Kunden GUID"); + builder.AddOrUpdate("Admin.Customers.Customers.Fields.PasswordSalt", "Password salt", "Passwort Salt"); + builder.AddOrUpdate("Admin.Customers.Customers.Fields.IsSystemAccount", "Is system account", "Ist Systemkonto"); + builder.AddOrUpdate("Admin.Customers.Customers.Fields.LastLoginDateUtc", "Last login date", "Letztes Login-Datum"); + + builder.AddOrUpdate("Admin.Promotions.NewsLetterSubscriptions.Fields.NewsLetterSubscriptionGuid", "Subscription GUID", "Abonnement GUID"); + + builder.AddOrUpdate("Admin.DataExchange.ColumnMapping.Note", + "You can optionally set for each field of the import file whether and for which object property the data should be imported. Fields with equal names are always imported as long as they are not explicitly ignored. Not yet selected properties are highlighted in the selection list. It is also possible to define a default value which is applied when the import field is empty. Stored assignments becomes invalid and reset when the delimiter changes.", + "Sie knnen optional fr jedes Feld der Importdatei festlegen, ob und nach welcher Objekteigenschaft dessen Daten zu importieren sind. Gleichnamige Felder werden grundstzlich immer importiert, sofern sie nicht explizit ignoriert werden sollen. Noch nicht ausgewhlte Eigenschaften sind in der Auswahlliste hervorgehoben. Zudem ist die Angabe eines Standardwertes mglich, der angewendet wird, wenn das Importfeld leer ist. Durch nderung des Trennzeichens werden gespeicherte Zuordnungen ungltig und zurckgesetzt."); + + builder.AddOrUpdate("Admin.DataExchange.ColumnMapping.ImportField", "Import Field", "Importfeld"); + builder.AddOrUpdate("Admin.DataExchange.ColumnMapping.EntityProperty", "Object property", "Eigenschaft des Objektes"); + builder.AddOrUpdate("Admin.DataExchange.ColumnMapping.DefaultValue", "Default Value", "Standardwert"); + + + builder.Delete( + "Admin.DataExchange.Export.LastExecution", + "Admin.DataExchange.Export.Offset", + "Admin.DataExchange.Export.Limit", + "Admin.Promotions.NewsLetterSubscriptions.ImportEmailsSuccess", + "Admin.Common.ImportFromCsv", + "Admin.Common.CsvFile", + + "Admin.Common.ImportFromExcel", + "Admin.Common.ExcelFile", + "Admin.Common.ImportFromExcel.InProgress", + "Admin.Common.ImportFromExcel.LastResultTitle", + "Admin.Common.ImportFromExcel.ProcessedCount", + "Admin.Common.ImportFromExcel.QuickStats", + "Admin.Common.ImportFromExcel.ActiveSince", + "Admin.Common.ImportFromExcel.CancelPrompt", + "Admin.Common.ImportFromExcel.Cancel", + "Admin.Common.ImportFromExcel.Cancelled", + "Admin.Common.ImportFromExcel.DownloadReport", + "Admin.Common.ImportFromExcel.NoReportAvailable", + + "Admin.Configuration.ActivityLog.ActivityLog.Fields.ActivityLogTypeColumn", + "Plugins.ExchangeRate.EcbExchange.SetCurrencyToEURO" + ); + + builder.AddOrUpdate("ActivityLog.DeleteOrder", "Deleted order {0}", "Auftrag {0} gelscht"); + + builder.AddOrUpdate("Admin.System.SystemCustomerNames.SearchEngine", "Search Engine", "Suchmaschine"); + builder.AddOrUpdate("Admin.System.SystemCustomerNames.BackgroundTask", "Background Task", "Geplante Aufgabe"); + builder.AddOrUpdate("Admin.System.SystemCustomerNames.PdfConverter", "PDF Converter", "PDF-Konvertierer"); + + builder.AddOrUpdate("Admin.Configuration.ActivityLog.ActivityLog.Fields.CustomerSystemAccount", + "Customer system account", + "Kundensystemkonto", + "Filters results by customer system accounts.", + "Filtert Ergebnisse nach Kundenystemkonten."); + + builder.AddOrUpdate("Admin.Configuration.ActivityLog.ActivityLog.Fields.CustomerEmail", + "Customer Email", + "Kunden-E-Mail", + "Filters results by customer email address.", + "Filtert Ergebnisse nach E-Mail-Adresse der Kunden."); + + builder.AddOrUpdate("Admin.Configuration.Plugins.UnknownError", + "An unknown error occurred when calling a plugin. Please refer to the following message for details.", + "Beim Aufruf eines Plugins ist ein unbekannter Fehler aufgetreten. Details entnehmen Sie bitte der folgenden Meldung."); + + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.AllowUnicodeCharsInUrls", + "Allow unicode characters", + "Unicode-Zeichen erlauben", + "Check whether SEO names can contain letters that are classified as unicode characters.", + "Legt fest, ob als Unicode-Zeichen eingestufte Buchstaben in SEO relevanten Namen erlaubt sind."); + + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.SeoNameCharConversion", + "Characters to be converted", + "Zu konvertierende Zeichen", + "Allows an individual conversion of characters for SEO name creation. Enter the old and the new character separated by a semicolon, e.g. ;ae. Each entry has to be entered in a new line.", + "Ermglicht das individuelle Konvertieren von Zeichen bei der Erstellung SEO Namen. Geben Sie hier durch Semikolon getrennt das alte und das neue Zeichen ein, z.B. ;ae. Jeder Eintrag muss in einer neuen Zeile erfolgen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.TestSeoNameCreation", + "Check string", + "Zeichenkette prfen", + "Enter any string to check the SEO name creation. Changed settings must be saved before.", + "Geben Sie eine beliebige Zeichenkette ein, um daraus den SEO Namen zu erstellen. Genderte Einstellungen mssen zuvor gespeichert werden."); + + + builder.AddOrUpdate("Admin.System.Warnings.NoPermissionsDefined", + "There are no permissions defined.", + "Es sind keine Zugriffsrechte festgelegt."); + + builder.AddOrUpdate("Admin.System.Warnings.NoCustomerRolesDefined", + "There are no customer roles defined.", + "Es sind keine Kundengruppen festgelegt."); + + builder.AddOrUpdate("Admin.System.Warnings.AccessDeniedToAnonymousRequest", + "Access denied to anonymous request on {0}.", + "Zugriffsverweigerung durch anonyme Anfrage bei {0}."); + + builder.AddOrUpdate("Admin.System.Warnings.AccessDeniedToUser", + "Access denied to user #{0} '{1}' on {2}.", + "Zugriffsverweigerung durch Kunde #{0} '{1}' bei {2}."); + + builder.AddOrUpdate("Admin.Configuration.Countries.CannotDeleteDueToAssociatedAddresses", + "The country cannot be deleted because it has associated addresses.", + "Das Land kann nicht gelscht werden, weil ihm Adressen zugeordnet sind."); + + builder.AddOrUpdate("Admin.Configuration.Countries.States.CantDeleteWithAddresses", + "The state\\province cannot be deleted because it has associated addresses.", + "Das Bundesland\\Region kann nicht gelscht werden, weil ihm Adressen zugeordnet sind."); + + builder.AddOrUpdate("Admin.Configuration.Shipping.Methods.NoMethodsLoaded", + "No shipping methods could be loaded.", + "Es konnten keine Versandarten geladen werden."); + + builder.AddOrUpdate("Admin.System.Warnings.NoShipmentItems", + "No shipment items", + "Keine Versand-Artikel"); + + builder.AddOrUpdate("Admin.System.Warnings.DigitsOnly", + "Please enter digits only.", + "Bitte nur Ziffern eingeben."); + + builder.AddOrUpdate("Account.Register.Errors.CannotRegisterSearchEngine", + "A search engine can't be registered.", + "Eine Suchmaschine kann nicht registriert werden."); + + builder.AddOrUpdate("Account.Register.Errors.CannotRegisterTaskAccount", + "A background task account can't be registered.", + "Das Konto einer geplanten Aufgabe kann nicht registriert werden."); + + builder.AddOrUpdate("Account.Register.Errors.AlreadyRegistered", + "The customer is already registered.", + "Der Kunde ist bereits registriert."); + + builder.AddOrUpdate("Admin.Customers.CustomerRoles.CannotFoundRole", + "The customer role \"{0}\" cannot be found.", + "Die Kundengruppe \"{0}\" wurde nicht gefunden."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.RegisterCustomerRole", + "Customer role at registrations", + "Kundengruppe bei Registrierungen", + "Specifies a customer role that will be assigned to newly registered customers.", + "Legt eine Kundengruppe fest, die neu registrierten Kunden zugeordnet wird."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Order.DisplayOrdersOfAllStores", + "Display orders of all stores", + "Auftrge aller Shops anzeigen", + "Specifies whether to display the orders of all stores to the customer. If this option is disabled, only the orders of the current store are displayed.", + "Legt fest, ob dem Kunden die Auftrge aller Shops angezeigt werden sollen. Ist diese Option deaktiviert, so werden nur die Auftrge des aktuellen Shops angezeigt."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Order.GiftCards_Deactivated") + .Value("de", "Geschenkgutschein wird deaktiviert, wenn Auftragsstatus..."); + + builder.AddOrUpdate("Admin.Configuration.Languages.Fields.UniqueSeoCode.Required", + "Please select a SEO language code.", + "Bitte legen Sie einen SEO Sprach-Code fest."); + + builder.AddOrUpdate("Admin.Configuration.Languages.Fields.FlagImageFileName", + "Flag image", + "Flaggenbild", + "Specifies the flag image. The files for the flag images must be stored in /Content/Images/flags/.", + "Legt das Flaggenbild fest. Die Dateien der Flaggenbilder mssen in /Content/Images/flags/ liegen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.HideCategoryDefaultPictures", + "Hide default picture for categories", + "Standardbild bei Warengruppen ausblenden", + "Specifies whether to hide the default image for categories. The default image is shown when no image is assigned to a category.", + "Legt fest, ob das Standardbild bei Warengruppen ausgeblendet werden soll. Das Standardbild wird angezeigt, wenn der Warengruppe kein Bild zugeordnet ist."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.HideProductDefaultPictures", + "Hide default picture for products", + "Standardbild bei Produkten ausblenden", + "Specifies whether to hide the default image for products. The default image is shown when no image is assigned to a product.", + "Legt fest, ob das Standardbild bei Produkten ausgeblendet werden soll. Das Standardbild wird angezeigt, wenn dem Produkt kein Bild zugeordnet ist."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Media.MessageProductThumbPictureSize", + "Thumbnail size of products in emails", + "Thumbnail-Gre von Produkten in E-Mails", + "Specifies the thumbnail image size (pixels) of products in emails. Enter 0 to not display thumbnails.", + "Legt die Thumbnail-Bildgre (in Pixel) von Produkten in E-Mails fest. Geben Sie 0 ein, um keine Thumbnails anzuzeigen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.MetaRobotsContent", + "Meta robots", + "Meta Robots", + "Specifies if and how search engines indexing the pages of your store.", + "Legt fest, ob und wie Suchmaschinen die Seiten Ihres Shops indexieren."); + + builder.AddOrUpdate("Providers.ExchangeRate.EcbExchange.SetCurrencyToEURO", + "You can use ECB (European central bank) exchange rate provider only when exchange rate currency code is set to EURO.", + "Der EZB-Wechselkursdienst kann nur genutzt werden, wenn der Wechselkurs-Whrungscode auf EUR gesetzt ist."); + + + builder.AddOrUpdate("Common.Loading", "Loading", "Lade"); + builder.AddOrUpdate("Common.ShowMore", "Show more", "Mehr anzeigen"); + builder.AddOrUpdate("Common.Published", "Published", "Verffentlicht"); + builder.AddOrUpdate("Common.Unpublished", "Unpublished", "Unverffentlicht"); + builder.AddOrUpdate("Common.NotSelectable", "Not selectable", "Nicht auswhlbar"); + + builder.AddOrUpdate("Common.EntityPicker.SinglePickNote", + "Click on an item to select it and OK to apply it.", + "Klicken Sie auf ein Element, um es auszuwhlen und OK, um es zu bernehmen."); + + builder.AddOrUpdate("Common.EntityPicker.MultiPickNote", + "Click on an item to select or deselect it and OK to apply the selection.", + "Klicken Sie auf ein Element, um es aus- bzw. abzuwhlen und OK, um die Auswahl zu bernehmen."); + + builder.AddOrUpdate("Common.EntityPicker.NoMoreItemsFound", + "There were no more items found.", + "Es wurden keine weiteren Elemente gefunden."); + + builder.AddOrUpdate("Admin.Catalog.Products.BundleItems.NotesOnProductBundles", + "Notes on product bundles", + "Hinweise zu Produkt-Bundles"); + + builder.AddOrUpdate("Admin.Catalog.Products.RelatedProducts.AddNew", + "Add cross-selling product", + "Cross-Selling-Produkt hinzufgen"); + + builder.AddOrUpdate("Admin.Catalog.Products.RelatedProducts.SaveBeforeEdit", + "You need to save the product before you can add cross-selling products for this product page.", + "Sie mssen das Produkt speichern, bevor Sie Cross-Selling-Produkte hinzufgen knnen."); + + builder.AddOrUpdate("Admin.Catalog.Products.CrossSells.AddNew", + "Add checkout-selling product", + "Checkout-Selling-Produkt hinzufgen"); + + builder.AddOrUpdate("Admin.Catalog.Products.CrossSells.SaveBeforeEdit", + "You need to save the product before you can add checkout-selling products for this product page.", + "Sie mssen das Produkt speichern, bevor Sie Checkout-Selling-Produkte hinzufgen knnen."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.resx b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.resx new file mode 100644 index 0000000000..fa9e8cc53c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.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/201601262000441_ImportFramework1.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.Designer.cs new file mode 100644 index 0000000000..b8d27247a3 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.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 ImportFramework1 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ImportFramework1)); + + string IMigrationMetadata.Id + { + get { return "201601262000441_ImportFramework1"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs new file mode 100644 index 0000000000..8c61a2784f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -0,0 +1,478 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using Setup; + + public partial class ImportFramework1 : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ImportProfile", "UpdateOnly", c => c.Boolean(nullable: false)); + AddColumn("dbo.ImportProfile", "KeyFieldNames", c => c.String(maxLength: 1000)); + AddColumn("dbo.ImportProfile", "ResultInfo", c => c.String()); + } + + public override void Down() + { + DropColumn("dbo.ImportProfile", "ResultInfo"); + DropColumn("dbo.ImportProfile", "KeyFieldNames"); + DropColumn("dbo.ImportProfile", "UpdateOnly"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.DataExchange.Import.MultipleFilesSameFileTypeNote", + "For multiple import files please make sure that they are of the same file type and that the content follows the same pattern (e.g. same column headings).", + "Bei mehreren Importdateien ist darauf zu achten, dass diese vom selben Dateityp sind und deren Inhalt demselben Schema folgt (z.B. gleiche Spaltenberschriften)."); + + builder.AddOrUpdate("Admin.DataExchange.Import.ProfileEntitySelectNote", + "Please select an object that you want to import.", + "Whlen Sie bitte ein Objekt aus, das Sie importieren mchten."); + + builder.AddOrUpdate("Admin.DataExchange.Import.ProfileCreationNote", + "Please upload an import file, enter a meaningful name for the import profile and save.", + "Laden Sie bitte eine Importdatei hoch, legen Sie einen aussagekrftigen Namen fr das Importprofil fest und speichern Sie."); + + builder.AddOrUpdate("Admin.DataExchange.Import.AddAnotherFile", + "Add import file...", + "Importdatei hinzufgen..."); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.RunNow.Progress.DataImportTask", + "The task is now running in the background. You will receive an email as soon as it is completed. The progress can be tracked in the import profile list.", + "Die Aufgabe wird jetzt im Hintergrund ausgefhrt. Sie erhalten eine E-Mail, sobald sie abgeschlossen ist. Den Fortschritt knnen Sie in der Importprofilliste verfolgen."); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.RunNow.Progress.DataExportTask", + "The task is now running in the background. You will receive an email as soon as it is completed. The progress can be tracked in the export profile list.", + "Die Aufgabe wird jetzt im Hintergrund ausgefhrt. Sie erhalten eine E-Mail, sobald sie abgeschlossen ist. Den Fortschritt knnen Sie in der Exportprofilliste verfolgen."); + + builder.AddOrUpdate("Admin.DataExchange.Import.DefaultProfileNames", + "My product import;My category import;My customer import;My newsletter subscription import", + "Mein Produktimport;Mein Warengruppenimport;Mein Kundenimport;Mein Newsletter-Abonnement-Import"); + + builder.AddOrUpdate("Admin.DataExchange.Import.LastImportResult", + "Last import result", + "Letztes Importergebnis"); + + builder.AddOrUpdate("Admin.Common.TotalRows", "Total rows", "Zeilen insgesamt"); + builder.AddOrUpdate("Admin.Common.Skipped", "Skipped", "Ausgelassen"); + builder.AddOrUpdate("Admin.Common.NewRecords", "New records", "Neue Datenstze"); + builder.AddOrUpdate("Admin.Common.Updated", "Updated", "Aktualisiert"); + builder.AddOrUpdate("Admin.Common.Warnings", "Warnings", "Warnungen"); + builder.AddOrUpdate("Admin.Common.Errors", "Errors", "Fehler"); + builder.AddOrUpdate("Admin.Common.UnsupportedEntityType", "Unsupported entity type '{0}'", "Nicht untersttzter Entittstyp '{0}'"); + builder.AddOrUpdate("Admin.Common.DataExchange", "Data exchange", "Datenaustausch"); + + builder.AddOrUpdate("Admin.DataExchange.Import.CompletedEmail.Body", + "This is an automatic notification of store \"{0}\" about a recent data import. Summary:", + "Dies ist eine automatische Benachrichtung von Shop \"{0}\" ber einen erfolgten Datenimport. Zusammenfassung:"); + + builder.AddOrUpdate("Admin.DataExchange.Import.CompletedEmail.Subject", + "Import of \"{0}\" has been finished", + "Import von \"{0}\" ist abgeschlossen"); + + builder.AddOrUpdate("Admin.DataExchange.Import.ColumnMapping", + "Assignment of import fields", + "Zuordnung der Importfelder"); + + builder.AddOrUpdate("Admin.DataExchange.Import.SelectTargetProperty", + "Create new assignment here", + "Hier neue Zuordnung vornehmen"); + + builder.AddOrUpdate("Admin.DataExchange.Import.UpdateOnly", + "Only update", + "Nur aktualisieren", + "If this option is enabled, only existing data is updated but no new records are added.", + "Ist diese Option aktiviert, werden nur vorhandene Daten aktualisiert, aber keine neue Datenstze hinzugefgt."); + + builder.AddOrUpdate("Admin.DataExchange.Import.KeyFieldNames", + "Key fields", + "Schlsselfelder", + "Existing records can be identified for updates on the basis of key fields. The key fields are processed in the order how they are defined here.", + "Anhand von Schlsselfeldern knnen vorhandene Datenstze zwecks Aktualisierung identifiziert werden. Die Schlsselfelder werden in der hier festgelegten Reihenfolge verarbeitet."); + + builder.AddOrUpdate("Admin.DataExchange.Import.Validate.OneKeyFieldRequired", + "At least one key field is required.", + "Es ist mindestens ein Schlsselfeld erforderlich."); + + builder.AddOrUpdate("Admin.DataExchange.ColumnMapping.Validate.MultipleMappedIgnored", + "The following object properties were multiple assigned and thus ignored: {0}", + "Die folgenden Objekteigenschaft wurden mehrfach zugeodnet und deshalb ignoriert: {0}"); + + builder.AddOrUpdate("Admin.DataExchange.ColumnMapping.Validate.MappingsReset", + "The stored field assignments are invalid due to the change of the delimiter and were reset.", + "Die gespeicherten Feldzuordnungen sind aufgrund der nderung des Trennzeichens ungltig und wurden zurckgesetzt."); + + + builder.AddOrUpdate("Common.Download.NoDataAvailable", + "Download data is not available anymore.", + "Es sind keine Daten zum Herunterladen mehr verfgbar."); + + builder.AddOrUpdate("Common.Download.NotAvailable", + "Download is not available any more.", + "Der Download ist nicht mehr verfgbar."); + + builder.AddOrUpdate("Common.Download.SampleNotAvailable", + "Sample download is not available anymore.", + "Der Download einer Beispieldatei ist nicht mehr verfgbar."); + + builder.AddOrUpdate("Common.Download.HasNoSample", + "The product variant doesn't have a sample download.", + "Fr die Produktvariante ist der Download einer Beispieldatei nicht verfgbar."); + + builder.AddOrUpdate("Common.Download.NotAllowed", + "Downloads are not allowed.", + "Downloads sind nicht gestattet."); + + builder.AddOrUpdate("Common.Download.MaxNumberReached", + "You have reached the maximum number of downloads {0}.", + "Sie haben die maximale Anzahl an Downloads {0} erreicht."); + + builder.AddOrUpdate("Account.CustomerOrders.NotYourOrder", + "This is not your order.", + "Dieser Auftrag konnte Ihnen nicht zugeordnet werden."); + + builder.AddOrUpdate("Shipping.CouldNotLoadMethod", + "The shipping rate computation method could not be loaded.", + "Die Berechnungsmethode fr Versandkosten konnte nicht geladen werden."); + + builder.AddOrUpdate("Shipping.OneActiveMethodProviderRequired", + "At least one shipping rate computation method provider is required to be active.", + "Mindestens ein Provider zur Berechnung von Versandkosten muss aktiviert sein."); + + builder.AddOrUpdate("Payment.CouldNotLoadMethod", + "The payment method could not be loaded.", + "Die Zahlungsart konnte nicht geladen werden."); + + builder.AddOrUpdate("Payment.MethodNotAvailable", + "The payment method is not available.", + "Die Zahlungsart steht nicht zur Verfgung."); + + builder.AddOrUpdate("Payment.OneActiveMethodProviderRequired", + "At least one payment method provider is required to be active.", + "Mindestens ein Zahlungsart-Provider muss aktiviert sein."); + + builder.AddOrUpdate("Payment.RecurringPaymentNotSupported", + "Recurring payments are not supported by selected payment method.", + "Wiederkehrende Zahlungen sind fr die gewhlte Zahlungsart nicht mglich."); + + builder.AddOrUpdate("Payment.RecurringPaymentNotActive", + "Recurring payment is not active.", + "Wiederkehrende Zahlung ist inaktiv."); + + builder.AddOrUpdate("Payment.RecurringPaymentTypeUnknown", + "The recurring payment type is not supported.", + "Der Typ von wiederkehrender Zahlung wird nicht untersttzt."); + + builder.AddOrUpdate("Payment.CannotCalculateNextPaymentDate", + "The next payment date could not be calculated.", + "Das Datum der nchsten Zahlung kann nicht ermittelt werden."); + + builder.AddOrUpdate("Payment.PayingFailed", + "Unfortunately we can not handle this purchasing via your preferred payment method. Please select an alternate payment option to complete your order.", + "Leider knnen wir diesen Einkauf nicht ber die gewnschte Zahlungsart abwickeln. Bitte whlen Sie eine alternative Zahlungsoption aus, um Ihre Bestellung abzuschlieen."); + + builder.AddOrUpdate("Order.InitialOrderDoesNotExistForRecurringPayment", + "No initial order exists for the recurring payment.", + "Fr die wiederkehrende Zahlung existiert kein Ausgangsauftrag."); + + builder.AddOrUpdate("Order.CannotCalculateShippingTotal", + "The shipping total could not be calculated.", + "Die Versandkosten konnten nicht berechnet werden."); + + builder.AddOrUpdate("Order.CannotCalculateOrderTotal", + "The order total could not be calculated.", + "Die Auftragssumme konnte nicht berechnet werden."); + + builder.AddOrUpdate("Order.BillingAddressMissing", + "The billing address is missing.", + "Die Rechnungsanschrift fehlt."); + + builder.AddOrUpdate("Order.ShippingAddressMissing", + "The shipping address is missing.", + "Die Lieferanschrift fehlt."); + + builder.AddOrUpdate("Order.CountryNotAllowedForBilling", + "The country '{0}' is not allowed for billing.", + "Eine Rechnungslegung ist fr das Land '{0}' unzulssig."); + + builder.AddOrUpdate("Order.CountryNotAllowedForShipping", + "The country '{0}' is not allowed for shipping.", + "Ein Versand ist fr das Land '{0}' unzulssig."); + + builder.AddOrUpdate("Order.NoRecurringProducts", + "There are no recurring products.", + "Keine wiederkehrenden Produkte."); + + builder.AddOrUpdate("Order.NotFound", + "The order {0} was not found.", + "Der Auftrag {0} wurde nicht gefunden."); + + builder.AddOrUpdate("Order.CannotCancel", + "Cannot cancel order.", + "Der Auftrag kann nicht storniert werden."); + + builder.AddOrUpdate("Order.CannotMarkCompleted", + "Cannot mark order as completed.", + "Der Auftrag kann nicht als abgeschlossen markiert werden."); + + builder.AddOrUpdate("Order.CannotCapture", + "Cannot capture order.", + "Der Auftrag kann nicht gebucht werden."); + + builder.AddOrUpdate("Order.CannotMarkPaid", + "Cannot mark order as paid.", + "Der Auftrag kann nicht als bezahlt markiert werden."); + + builder.AddOrUpdate("Order.CannotRefund", + "Cannot do refund for order.", + "Eine Rckerstattung ist fr diesen Auftrag nicht mglich."); + + builder.AddOrUpdate("Order.CannotPartialRefund", + "Cannot do partial refund for order.", + "Eine Teilrckerstattung ist fr diesen Auftrag nicht mglich."); + + builder.AddOrUpdate("Order.CannotVoid", + "Cannot do void for order.", + "Eine Stornierung dieses Auftrages ist nicht mglich."); + + builder.AddOrUpdate("Shipment.AlreadyShipped", + "This shipment is already shipped.", + "Diese Sendung wird bereits ausgeliefert."); + + builder.AddOrUpdate("Shipment.AlreadyDelivered", + "This shipment is already delivered.", + "Diese Sendung wird bereits zugestellt."); + + builder.AddOrUpdate("Customer.DoesNotExist", + "The customer does not exist.", + "Der Kunde existiert nicht."); + + builder.AddOrUpdate("Checkout.AnonymousNotAllowed", + "An anonymous checkout is not allowed.", + "Ein anonymer Checkout ist nicht zulssig."); + + builder.AddOrUpdate("Common.Error.InvalidEmail", + "The email address is not valid.", + "Die E-Mail-Adresse ist ungltig."); + + builder.AddOrUpdate("Common.Error.NoActiveLanguage", + "No active language could be loaded.", + "Es wurde keine aktive Sprache gefunden."); + + builder.AddOrUpdate("Common.Error.NoEmailAccount", + "No email account could be loaded.", + "Es wurde kein E-Mail-Konto gefunden."); + + builder.AddOrUpdate("Admin.OrderNotice.RecurringPaymentCancellationError", + "Unable to cancel recurring payment for order {0}.", + "Es ist ein Fehler bei der Stornierung einer wiederkehrenden Zahlung fr Auftrag {0} aufgetreten."); + + builder.AddOrUpdate("Admin.OrderNotice.OrderRefundError", + "Unable to refund order {0}.", + "Es ist ein Fehler bei einer Rckerstattung zu Auftrag {0} aufgetreten."); + + builder.AddOrUpdate("Admin.OrderNotice.OrderPartiallyRefundError", + "Unable to partially refund order {0}.", + "Es ist ein Fehler bei einer Teilerstattung zu Auftrag {0} aufgetreten."); + + builder.AddOrUpdate("Admin.OrderNotice.OrderVoidError", + "Unable to void payment transaction of order {0}.", + "Es ist ein Fehler bei der Stornierung einer Zahlungstransaktion zu Auftrag {0} aufgetreten."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.SortFilterResultsByMatches", + "Sort filter results by number of matches", + "Filterergebnisse nach Trefferanzahl sortieren", + "Specifies to sort filter results by number of matches in descending order. If this option is deactivated then the result is sorted by the display order of the values.", + "Legt fest, das Filterergebnisse absteigend nach der Anzahl an bereinstimmungen sortiert werden. Ist diese Option deaktiviert, so wird in der fr die Werte festgelegten Reihenfolge sortiert."); + + builder.AddOrUpdate("Wishlist.IsDisabled", + "The wishlist is disabled.", + "Die Wunschliste ist deaktiviert."); + + builder.AddOrUpdate("ShoppingCart.IsDisabled", + "The shoping cart is disabled.", + "Der Warenkorb ist deaktiviert."); + + builder.AddOrUpdate("Products.NotFound", + "The product {0} was not found.", + "Das Produkt {0} wurde nicht gefunden."); + + builder.AddOrUpdate("Products.Variants.NotFound", + "The product variant {0} was not found.", + "Die Produktvariante {0} wurde nicht gefunden."); + + builder.AddOrUpdate("Reviews.NotFound", + "The product review {0} was not found.", + "Die Produktbewertung {0} wurde nicht gefunden."); + + builder.AddOrUpdate("Polls.AnswerNotFound", + "The poll answer {0} was not found.", + "Eine Umfrageantwort {0} wurde nicht gefunden."); + + builder.AddOrUpdate("Polls.NotAvailable", + "The poll is not available.", + "Die Umfrage ist nicht verfgbar."); + + builder.AddOrUpdate("Install.LanguageNotRegistered", + "The install language '{0}' is not registered.", + "Die Installationssprache '{0}' ist nicht registriert."); + + builder.AddOrUpdate("Admin.Catalog.Categories.DescriptionToggle", + "Show other description", + "Andere Beschreibung anzeigen"); + + builder.AddOrUpdate("Admin.Catalog.Categories.Fields.Description", + "Top description", + "Obere Beschreibung", + "Description of the category that is displayed above products on the category page.", + "Beschreibung der Warengruppe, die auf der Warengruppenseite oberhalb der Produkte angezeigt wird."); + + builder.AddOrUpdate("Common.CaptchaUnableToVerify", + "The API call to verify a CAPTCHA has failed.", + "Der API-Aufruf zur Prfung eines CAPTCHAs ist fehlgeschlagen."); + + builder.AddOrUpdate("Common.WrongCaptcha", + "Please confirm that you are not a \"robot\".", + "Bitte besttigen Sie, dass Sie kein \"Roboter\" sind."); + + builder.AddOrUpdate("DownloadableProducts.UserAgreementConfirmation", + "Yes, I agree to the user agreement for this product.", + "Ja, ich stimme der Nutzungsvereinbarung fr dieses Produkt zu."); + + builder.AddOrUpdate("DownloadableProducts.HasNoUserAgreement", + "The product has no user agreement.", + "Das Produkt besitzt keine Nutzungsvereinbarung."); + + builder.AddOrUpdate("Checkout.DownloadUserAgreement.PleaseAgree", + "Please agree to the user agreement for downloadable products.", + "Bitte stimmen Sie der Nutzungsvereinbarung fr herunterladbare Produkte zu."); + + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.OrderConfirmationPage", + "Order confirmation page", + "Bestellabschlussseite"); + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ShowEsdRevocationWaiverBox", + "Show revocation waiver box for electronic services", + "Widerrufsverzichtbox fr elektronische Leistungen anzeigen", + "Specifies whether the customer must agree a revocation waiver for electronic services on the order confirmation page.", + "Legt fest, ob der Kunde auf der Bestellabschlussseite einem Widerrufsverzicht fr elektronische Leistungen zustimmen muss."); + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ShowCommentBox", + "Show comment box", + "Kommentarbox anzeigen", + "Specifies whether comment box is displayed on the order confirmation page.", + "Legt fest, ob der Kunde auf der Bestellabschlussseite einen Kommentar zu seiner Bestellung hinterlegen kann."); + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ShowConfirmOrderLegalHint", + "Show legal hints in order summary", + "Rechtliche Hinweise in der Warenkorbbersicht anzeigen", + "Specifies whether to show hints in order summary on the confirm order page. This text can be altered in the language resources.", + "Legt fest, ob rechtliche Hinweise in der Warenkorbbersicht auf der Bestellabschluseite angezeigt werden. Dieser Text kann in den Sprachresourcen gendert werden."); + + + builder.AddOrUpdate("Checkout.EsdRevocationWaiverConfirmation", + "Yes, I want access to the digital content immediately and know that my right of revocation expires with the access.", + "Ja, ich mchte sofort Zugang zu dem digitalen Inhalt und wei, dass mein Widerrufsrecht mit dem Zugang erlischt."); + + builder.AddOrUpdate("Checkout.EsdRevocationWaiverConfirmation.PleaseAgree", + "Please confirm that you would like access to the digital content immediately.", + "Bitte besttigen Sie, dass Sie sofort Zugang zu dem digitalen Inhalt wnschen."); + + + builder.AddOrUpdate("Admin.Configuration.Settings.DataExchange.MaxFileNameLength", + "Maximum length of file and folder names", + "Maximale Lnge von Datei- und Ordnernamen", + "Specifies the maximum length of file and folder names created during an import or export.", + "Legt die maximale Lnge von Datei- und Ordnernamen fest, die im Rahmen eines Imports\\Exports erzeugt wurden."); + + builder.AddOrUpdate("Admin.Configuration.Settings.DataExchange.ImageImportFolder", + "Image folder (relative path)", + "Bilderordner (relativer Pfad)", + "Specifies a relative path to a folder with images to be imported (e.g. Content\\Images).", + "Legt einen relativen Pfad zu einem Ordner mit zu importierenden Bildern fest (z.B. Inhalt\\Bilder)."); + + builder.AddOrUpdate("Admin.Configuration.Settings.DataExchange.ImageDownloadTimeout", + "Timeout for image download (minutes)", + "Zeitlimit fr Bilder-Download (Minuten)", + "Specifies the timeout for the image download in minutes.", + "Legt das Zeitlimit fr den Bilder-Download in Minuten fest."); + + builder.AddOrUpdate("Admin.System.Maintenance.SqlQuery.Succeeded", + "The SQL command was executed successfully.", + "Die SQL Anweisung wurde erfolgreich ausgefhrt."); + + builder.AddOrUpdate("Admin.DataExchange.Import.KeyFieldNames.Note", + "Please only use the ID field as a key field, if the data sourced from the same database to which it will be imported. Otherwise it is possible that the wrong records are updated.", + "Benutzen Sie das ID-Feld bitte nur dann als Schlsselfeld, wenn die Daten aus der derselben Datenbank stammen, in der sie importiert werden sollen. Ansonsten werden u.U. die falschen Datenstze aktualisiert."); + + builder.AddOrUpdate("Admin.Configuration.Settings.RewardPoints.RoundDownRewardPoints", + "Round down points", + "Punkte abrunden", + "Specifies whether to round down calculated points. Otherwise the bonus points will be rounded up.", + "Legt fest, ob bei der Punkteberechnung abgerundet werden soll. Ansonsten werden Bonuspunkte aufgerundet."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Order.GiftCards_Deactivated", + "Gift card deactivation order status", + "Geschenkgutschein wird deaktiviert, wenn Auftragsstatus..."); + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.NewsLetterSubscription", + "Subscribe to newsletters", + "Abonnieren von Newslettern", + "Specifies if customers can subscribe to newsletters when ordering and if the checkbox is enabled by default.", + "Legt fest, ob Kunden bei einer Bestellung Newsletter abonnieren knnen und ob die Checkbox standardmig aktiviert ist."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.CheckoutNewsLetterSubscription.None", "Do not show", "Nicht anzeigen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.CheckoutNewsLetterSubscription.Deactivated", "Show deactivated", "Deaktiviert anzeigen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.CheckoutNewsLetterSubscription.Activated", "Show activated", "Aktiviert anzeigen"); + + builder.AddOrUpdate("Common.Options", "Options", "Optionen"); + + builder.AddOrUpdate("Checkout.SubscribeToNewsLetter", + "Subscribed to newsletter", + "Newsletter abonnieren"); + + builder.AddOrUpdate("Admin.OrderNotice.NewsLetterSubscriptionAdded", + "Subscribed to newsletter", + "Newsletter wurde abonniert"); + + builder.AddOrUpdate("Admin.OrderNotice.NewsLetterSubscriptionRemoved", + "Newsletter subscriber has been removed", + "Newsletter-Abonnent wurde entfernt"); + + builder.AddOrUpdate("Admin.Orders.Fields.CaptureTransactionID", + "Capture transaction ID", + "Transaktions-ID fr Buchung", + "Capture transaction identifier received from your payment gateway.", + "Vom Zahlungsanbieter erhaltene Transaktions-ID fr die Buchung."); + + builder.AddOrUpdate("Admin.Orders.Fields.AuthorizationTransactionID", + "Authorization transaction ID", + "Transaktions-ID fr Autorisierung", + "Authorization transaction identifier received from your payment gateway.", + "Vom Zahlungsanbieter erhaltene Transaktions-ID fr die Autorisierung."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.SearchDescriptions", + "Search product description", + "Produktbeschreibung durchsuchen", + "Specifies whether the product description should be included in the search.", + "Legt fest, ob die Produktbeschreibung in der Suche einbezogen werden soll."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.NumberOfPictures", + "Number of pictures", + "Anzahl der Bilder", + "Specifies the number of images per object to be exported.", + "Legt die Anzahl der zu exportierenden Bilder pro Objekt fest."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.resx b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.resx new file mode 100644 index 0000000000..4e7843d120 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.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/201603121451066_ThirdPartyEmailHandOver.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.Designer.cs new file mode 100644 index 0000000000..f7e948a531 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.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 ThirdPartyEmailHandOver : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ThirdPartyEmailHandOver)); + + string IMigrationMetadata.Id + { + get { return "201603121451066_ThirdPartyEmailHandOver"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs new file mode 100644 index 0000000000..9b5970f35a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs @@ -0,0 +1,235 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + using SmartStore.Core.Domain.Media; + using System.Linq; + + public partial class ThirdPartyEmailHandOver : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Order", "AcceptThirdPartyEmailHandOver", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.Order", "AcceptThirdPartyEmailHandOver"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + // Some users have disabled the "TransientMediaClearTask" due to a bug. + // When the task is enabled again, it would delete media files, which are marked as transient + // but are permanent actually. To avoid this, we have to set IsTransient to false. + var transientPictures = context.Set().Where(x => x.IsTransient == true).ToList(); + transientPictures.Each(x => x.IsTransient = false); + + var transientDownloads = context.Set().Where(x => x.IsTransient == true).ToList(); + transientDownloads.Each(x => x.IsTransient = false); + + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Common.Ignore", "Ignore", "Ignorieren"); + builder.AddOrUpdate("Common.My", "My", "Mein"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.CheckoutThirdPartyEmailHandOver.None", "Do not show", "Nicht anzeigen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.CheckoutThirdPartyEmailHandOver.Deactivated", "Show deactivated", "Deaktiviert anzeigen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.CheckoutThirdPartyEmailHandOver.Activated", "Show activated", "Aktiviert anzeigen"); + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ThirdPartyEmailHandOver", + "Consent for email transfer to third parties", + "Zustimmung zur E-Mail Weitergabe an Dritte", + "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.", + "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."); + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ThirdPartyEmailHandOverLabel", + "Text for email transfer consent", + "Text fr E-Mail Weitergabe", + "Specifies the text to be displayed to the customer. Please choose a specific reason, e.g. 'I agree to the transfer and storage of my email address for TrustedShops buyer protection.'", + "Legt den Text fr die Zustimmung zur Weitergabe der E-Mail Adresse an Dritte fest. Whlen Sie bitte einen konkreten Grund fr die Weitergabe, z.B. 'Mit der bermittlung und Speicherung meiner E-Mail-Adresse zur Abwicklung des Kuferschutzes durch Trusted Shops bin ich einverstanden.'"); + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ThirdPartyEmailHandOverLabel.Default", + "I agree to the transfer and storage of my email address by third parties.", + "Mit der bermittlung und Speicherung meiner E-Mail-Adresse durch dritte Parteien bin ich einverstanden."); + + builder.AddOrUpdate("Admin.Orders.Fields.AcceptThirdPartyEmailHandOver", + "Accepts transfer of email", + "Akzeptiert Weitergabe der E-Mail", + "Indicates whether the customer has agreed to a transfer of his email address to third parties.", + "Gibt an, ob der Kunde bei der Bestellung einer Weitergabe seiner E-Mail Adresse an Dritte zugestimmt hat oder nicht."); + + builder.AddOrUpdate("Admin.OrderNotice.OrderCaptureError", + "Unable to capture payment for order {0}.", + "Es ist ein Fehler bei der Zahlungsbuchung zu Auftrag {0} aufgetreten."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.None", + "None", "Keine"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowManufacturerPicturesInProductDetail", + "Show manufacturer pictures", + "Bilder von Herstellern anzeigen", + "Specifies whether to show manufacturer pictures on product detail page.", + "Legt fest, ob Herstellerbilder auf der Produktdetailseite angezeigt werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.HideManufacturerDefaultPictures", + "Hide default picture for manufacturers", + "Standardbild bei Herstellern ausblenden", + "Specifies whether to hide the default image for manufacturers. The default image is shown when no image is assigned to a manufacturer.", + "Legt fest, ob das Standardbild bei Herstellern ausgeblendet werden soll. Das Standardbild wird angezeigt, wenn dem Hersteller kein Bild zugeordnet ist."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Partition.Note", + "With the following settings you can partition the data to be exported. This includes
  • Skipping the first n records
  • The maximum number of records to be exported
  • The number of records per export file
  • Export data for each shop in a separate file
By default, all data of a store will be exported into one file.", + "Mit den folgenden Einstellungen lassen sich die zu exportierenden Daten aufteilen. Dazu zhlt
  • Das berspringen der ersten n Datenstze
  • Die maximale Anzahl zu exportierender Datenstze
  • Die Anzahl der Datenstze pro Exportdatei
  • Daten von jedem Shop in eine separate Datei exportieren
Standardmig werden alle Daten eines Shops in eine Datei exportiert."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Partition.Validate", + "Partitioning setting values must be greater than or equal to 0.", + "Einstellungen zur Aufteilung mssen grer oder gleich 0 sein."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.IsActiveSubscriber", + "Only active subscribers", + "Nur aktive Abonnenten", + "Filter by active or inactive newsletter subscribers.", + "Nach aktiven bzw. inaktiven Newsletter Abonnenten filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.IsActiveCustomer", + "Only active customers", + "Nur aktive Kunden", + "Filter by active or inactive customers.", + "Nach aktiven bzw. inaktiven Kunden filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.IsTaxExempt", + "Only tax exempt customers", + "Nur MwSt. befreite Kunden", + "Filter by tax exempt customers.", + "Nach MwSt. befreiten Kunden filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.BillingCountryIds", + "Billing countries", + "Rechnungslnder", + "Filter by billing countries.", + "Nach Rechnungslndern filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.ShippingCountryIds", + "Shipping countries", + "Versandlnder", + "Filter by shipping countries.", + "Nach Versandlndern filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.LastActivityFrom", + "Last activity from", + "Zuletzt aktiv von", + "Filter by date of last store activity.", + "Nach dem Datum der letzten Shop Aktivitt filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.LastActivityTo", + "Last active until", + "Zuletzt aktiv bis", + "Filter by date of last store activity.", + "Nach dem Datum der letzten Shop-Aktivitt filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.HasSpentAtLeastAmount", + "Has spent amount x", + "Hat Betrag x ausgegeben", + "Filter by spent amount.", + "Nach dem insgesamt ausgegebenen Betrag filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.HasPlacedAtLeastOrders", + "Has placed x orders", + "Hat x Bestellungen", + "Filter by number of placed orders.", + "Nach der Anzahl der gettigten Bestellungen filtern."); + + builder.AddOrUpdate("Admin.DataExchange.Export.SystemProfileNote", + "The following list contains system profiles, which are provided by plugins such as the Data Export Plugin. You can customize system profiles as desired, but cannot create new ones. These profiles also add additional action buttons. You will find these above data lists, such as the product or order list.", + "Die folgende Liste enthlt Systemprofile, die von Plugins wie bspw. dem Datenexporte Plugin bereitgestellt werden. Sie knnen Systemprofile nach Belieben anpassen, aber keine Neuen erstellen. Fr diese Profile stehen auerdem zustzliche Aktions-Buttons zur Verfgung. Sie finden diese ber den entsprechenden Listen, wie z.B. der Produkt- oder Auftragsliste."); + + builder.AddOrUpdate("Admin.DataExchange.AddNewProfile", + "New profile", + "Neues Profil"); + + builder.AddOrUpdate("Admin.DataExchange.Import.ProfileCreationNote", + "Please select the import object and upload an import file.", + "Whlen Sie bitte das zu importierende Objekt und laden Sie eine Importdatei hoch."); + + builder.Delete("Admin.DataExchange.Import.ProfileEntitySelectNote"); + + builder.AddOrUpdate("Admin.Configuration.Restriction.SaveBeforeEdit", + "You need to save before you can specify restrictions.", + "Sie mssen zunchst speichern, bevor Sie Einschrnkungen festlegen knnen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerNumberMethod", + "Customer numbers", + "Kundennummern", + "Specifies whether to assign customer numbers and whether they should be created automatically.", + "Legt fest, ob Kundennummern vergeben werden und ob diese automatisch vergeben werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerNumberVisibility", + "Customer number presentation", + "Darstellung der Kundennummer", + "Specifies the presentation and handling of the customer number to the customer.", + "Legt die Darstellung und Handhabung der Kundennummer gegenber dem Kunden fest."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberMethod.Disabled", "Disabled", "Deaktiviert"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberMethod.Enabled", "Enabled", "Aktiviert"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberMethod.AutomaticallySet", "Automatically assigned", "Automatisch vergeben"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.None", "Do not display", "Nicht anzeigen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.Display", "Display", "Anzeigen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.EditableIfEmpty", "Editable if empty", "Editierbar falls leer"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.Editable", "Always editable", "Immer editierbar"); + + builder.AddOrUpdate("Admin.Common.FileInUse", + "The file is in use and cannot be opened.", + "Die Datei ist in Benutzung und kann daher nicht geffnet werden."); + + + builder.AddOrUpdate("Admin.DataExchange.Export.UserProfilesTitle", + "User profiles", + "Benutzerprofile"); + builder.AddOrUpdate("Admin.DataExchange.Export.SystemProfilesTitle", + "System profiles", + "Systemprofile"); + + builder.AddOrUpdate("Admin.DataExchange.Export.CompletedEmailAddresses", + "Email addresses to", + "E-Mail Adressen an", + "Specifies the email addresses where to send the notification message.", + "Legt die E-Mail Adressen fest, an die die Benachrichtigung geschickt werden soll."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.EmailAddresses", + "Email addresses to", + "E-Mail Adressen an", + "Specifies the email addresses where to send the data.", + "Legt die E-Mail Adressen fest, an die die Daten verschickt werden soll."); + + builder.AddOrUpdate("Admin.Common.FileNotFound", "File not found", "Datei nicht gefunden"); + + builder.AddOrUpdate("Admin.Common.EnterEmailAdress", + "Please enter an email address.", + "Bitte geben Sie eine E-Mail-Adresse ein."); + + builder.AddOrUpdate("Admin.Configuration.EmailAccounts.TestingEmail", + "Testing email functionality.", + "Test der E-Mail-Funktion."); + + builder.AddOrUpdate("Admin.Common.EmailSuccessfullySent", + "The email has been successfully sent.", + "Die E-Mail wurde erfolgreich versendet."); + + builder.AddOrUpdate("Admin.Common.SkipAndTakeGreaterThanOrEqualZero", + "Values for skip and limit must be greater than or equal to 0.", + "Werte fr berspringen und Begrenzen mssen grer oder gleich 0 sein."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.resx b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.resx new file mode 100644 index 0000000000..43d0084f04 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.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/201605020640016_GtinMpnIndex.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.Designer.cs new file mode 100644 index 0000000000..90e9ba6504 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.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 GtinMpnIndex : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(GtinMpnIndex)); + + string IMigrationMetadata.Id + { + get { return "201605020640016_GtinMpnIndex"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.cs b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.cs new file mode 100644 index 0000000000..98f1936ecc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.cs @@ -0,0 +1,19 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + + public partial class GtinMpnIndex : DbMigration + { + public override void Up() + { + CreateIndex("dbo.Product", "ManufacturerPartNumber"); + CreateIndex("dbo.Product", "Gtin"); + } + + public override void Down() + { + DropIndex("dbo.Product", new[] { "Gtin" }); + DropIndex("dbo.Product", new[] { "ManufacturerPartNumber" }); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.resx b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.resx new file mode 100644 index 0000000000..15bcc90554 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.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/201605061916117_SwapColumnMappingValues.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.Designer.cs new file mode 100644 index 0000000000..1fa094db33 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.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 SwapColumnMappingValues : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(SwapColumnMappingValues)); + + string IMigrationMetadata.Id + { + get { return "201605061916117_SwapColumnMappingValues"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs new file mode 100644 index 0000000000..f16431d45b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.cs @@ -0,0 +1,105 @@ +namespace SmartStore.Data.Migrations +{ + using System.Collections.Generic; + using System.Data.Entity.Migrations; + using System.Linq; + using Core.Domain; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using Setup; + + public partial class SwapColumnMappingValues : 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); + + var importProfiles = context.Set().Where(x => x.ColumnMapping != null).ToList(); + + foreach (var profile in importProfiles) + { + var dic = new Dictionary>(); + var storeMapping = true; + + try + { + var json = JObject.Parse(profile.ColumnMapping); + + foreach (var kvp in json) + { + dynamic value = kvp.Value; + + var mappedName = (string)value.MappedName; + var property = (string)value.Property; + var defaultValue = (string)value.Default; + + if (mappedName.HasValue()) + { + // break migration because data is already migrated + storeMapping = false; + break; + } + else if (property.HasValue()) + { + if (!kvp.Key.IsCaseInsensitiveEqual(property) || defaultValue.HasValue()) + { + // swap value + dic.Add(property, new Dictionary + { + { "MappedName", kvp.Key }, + { "Default", defaultValue } + }); + } + else + { + // ignore because persisting not required anymore + } + } + else + { + // explicitly ignored property + dic.Add(property, new Dictionary + { + { "MappedName", property }, + { "Default", "[IGNOREPROPERTY]" } + }); + } + } + } + catch + { + storeMapping = true; + dic.Clear(); + } + + if (storeMapping) + { + if (dic.Any()) + profile.ColumnMapping = JsonConvert.SerializeObject(dic); + else + profile.ColumnMapping = null; + } + } + + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.Delete("Admin.DataExchange.ColumnMapping.Validate.MultipleMappedIgnored"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.resx b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.resx new file mode 100644 index 0000000000..15bcc90554 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605061916117_SwapColumnMappingValues.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/201605111140288_ImportExtraData.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.Designer.cs new file mode 100644 index 0000000000..6cac518be6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.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 ImportExtraData : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ImportExtraData)); + + string IMigrationMetadata.Id + { + get { return "201605111140288_ImportExtraData"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs new file mode 100644 index 0000000000..191ace97ba --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -0,0 +1,72 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using Setup; + + public partial class ImportExtraData : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ImportProfile", "ExtraData", c => c.String()); + } + + public override void Down() + { + DropColumn("dbo.ImportProfile", "ExtraData"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + context.MigrateSettings(x => + { + x.Add("MediaSettings.VariantValueThumbPictureSize", "20"); + x.Delete("MediaSettings.AutoCompleteSearchThumbPictureSize"); + }); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.DataExchange.Import.NumberOfPictures", + "Number of pictures", + "Anzahl der Bilder", + "Specifies the number of images per object to be imported.", + "Legt die Anzahl der zu importierenden Bilder pro Objekt fest."); + + builder.Update("Admin.Configuration.Settings.Catalog.DefaultPageSizeOptions") + .Value("en", "Number of displayed products per page"); + + builder.AddOrUpdate("Admin.Validation.ValueGreaterZero", + "The value must be greater than 0.", + "Der Wert muss grer 0 sein."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Order.OrderListPageSize", + "Number of displayed orders per page", + "Anzahl der Auftrge pro Seite", + "Specifies the number of displayed orders per page.", + "Legt die Anzahl der dargestellten Auftrge pro Seite fest."); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.RunningError", + "Error while running scheduled task \"{0}\"", + "Fehler beim Ausfhren der Aufgabe \"{0}\""); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.Cancellation", + "The scheduled task \"{0}\" has been canceled", + "Die geplante Aufgabe \"{0}\" wurde abgebrochen"); + + builder.AddOrUpdate("Admin.Common.HttpStatus", + "HTTP status {0} ({1}).", + "HTTP-Status {0} ({1})."); + + builder.AddOrUpdate("Admin.System.Warnings.SitemapReachable.MethodNotAllowed", + "The reachability of the sitemap could not be validated.", + "Die Erreichbarkeit der Sitemap konnte nicht berprft werden."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.resx b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.resx new file mode 100644 index 0000000000..738509bbb4 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.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/201605191216116_CheckoutAttributeMultiStore.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.Designer.cs new file mode 100644 index 0000000000..d4625babee --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.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 CheckoutAttributeMultiStore : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(CheckoutAttributeMultiStore)); + + string IMigrationMetadata.Id + { + get { return "201605191216116_CheckoutAttributeMultiStore"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.cs b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.cs new file mode 100644 index 0000000000..1b6ba5a31f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.cs @@ -0,0 +1,17 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + + public partial class CheckoutAttributeMultiStore : DbMigration + { + public override void Up() + { + AddColumn("dbo.CheckoutAttribute", "LimitedToStores", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.CheckoutAttribute", "LimitedToStores"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.resx b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.resx new file mode 100644 index 0000000000..d743254b0e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.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/201605201911421_ExportRevision.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.Designer.cs new file mode 100644 index 0000000000..e3a2d0014f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.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 ExportRevision : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ExportRevision)); + + string IMigrationMetadata.Id + { + get { return "201605201911421_ExportRevision"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs new file mode 100644 index 0000000000..d168932260 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -0,0 +1,204 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Linq; + using Core.Domain; + using Core.Domain.DataExchange; + using Setup; + using Utilities; + + public partial class ExportRevision : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ExportDeployment", "ResultInfo", c => c.String()); + AddColumn("dbo.ExportDeployment", "SubFolder", c => c.String(maxLength: 400)); + AlterColumn("dbo.ExportProfile", "FolderName", c => c.String(nullable: false, maxLength: 400)); + DropColumn("dbo.ExportDeployment", "CreateZip"); + } + + public override void Down() + { + AddColumn("dbo.ExportDeployment", "CreateZip", c => c.Boolean(nullable: false)); + AlterColumn("dbo.ExportProfile", "FolderName", c => c.String(nullable: false, maxLength: 100)); + DropColumn("dbo.ExportDeployment", "SubFolder"); + DropColumn("dbo.ExportDeployment", "ResultInfo"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + // migrate folder name to folder path + var rootPath = "~/App_Data/ExportProfiles/"; + var exportProfiles = context.Set().ToList(); + + foreach (var profile in exportProfiles) + { + if (!profile.FolderName.EmptyNull().StartsWith(rootPath)) + { + profile.FolderName = rootPath + profile.FolderName; + } + } + + context.SaveChanges(); + + // migrate public file system deployment to new public deployment + if (context.ColumnExists("ExportDeployment", "IsPublic")) + { + var fileSystemDeploymentTypeId = (int)ExportDeploymentType.FileSystem; + var publicFolderDeploymentTypeId = (int)ExportDeploymentType.PublicFolder; + + context.ExecuteSqlCommand("Update [ExportDeployment] Set DeploymentTypeId = {0} Where DeploymentTypeId = {1} And IsPublic = 1", + true, null, publicFolderDeploymentTypeId, fileSystemDeploymentTypeId); + + context.ColumnDelete("ExportDeployment", "IsPublic"); + } + + var oldFileManagerPath = CommonHelper.MapPath("~/Content/filemanager"); + FileSystemHelper.ClearDirectory(oldFileManagerPath, true); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.DataExchange.Export.FolderName", + "Folder path", + "Ordnerpfad", + "Specifies the relative path of the folder where to export the data.", + "Legt den relativen Pfad des Ordners fest, in den die Daten exportiert werden."); + + builder.AddOrUpdate("Admin.DataExchange.Export.FileNamePattern.Validate", + "Please enter a valid pattern for file names. Example for file names: %Store.Id%-%Profile.Id%-%File.Index%-%Profile.SeoName%", + "Bitte ein gltiges Muster fr Dateinamen eingeben. Beispiel: %Store.Id%-%Profile.Id%-%File.Index%-%Profile.SeoName%"); + + builder.AddOrUpdate("Admin.DataExchange.Export.FolderName.Validate", + "Please enter a valid, relative folder path for the export data.", + "Bitte einen gltigen, relativen Ordnerpfad fr die zu exportierenden Daten eingeben."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Http", "HTTP POST", "HTTP POST"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.PublicFolder", "Public folder", "ffentlicher Ordner"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.SubFolder", + "Name of subfolder", + "Name des Unterordners", + "Specifies the name of a subfolder where to publish the data.", + "Legt den Namen eines Unterordners fest, in den die Daten verffentlicht werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.ZipUsageNote", + "If there are a large number of export files, it is recommended to use the option Create ZIP archive. This saves time and avoids problems, such as a full email mailbox.", + "Bei einer groen Anzahl an Exportdateien wird empfohlen die Option ZIP-Archiv erstellen zu benutzen. Das spart Zeit und vermeidet Probleme, wie z.B. ein volles E-Mail Postfach."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Cleanup", + "Clean up after successful deployment", + "Nach erfolgreicher Verffentlichung aufrumen", + "Specifies whether to delete unneeded files after all deployments succeeded.", + "Legt fest, ob nicht mehr bentigte Dateien gelscht werden sollen, nachdem alle Verffentlichungen erfolgreich waren."); + + builder.AddOrUpdate("Admin.Common.FtpStatus", + "FTP status {0} ({1}).", + "FTP-Status {0} ({1})."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.CopyFileFailed", + "At least one file could not be copied.", + "Mindestens eine Datei konnte nicht kopiert werden."); + + builder.AddOrUpdate("Admin.Common.LastRun", "Last run", "Letzte Ausfhrung"); + builder.AddOrUpdate("Admin.Common.SuccessfulOn", "Successful on", "Erfolgreich am"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.Name", + "Name of profile", + "Name des Profils", + "Specifies the name of the publishing profile.", + "Legt den Namen des Verffentlichungsprofils fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.ProfilesTitle", + "Publishing profiles", + "Verffentlichungsprofile"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.NoProfiles", + "There are no publishing profiles.", + "Es liegen keine Verffentlichungsprofile vor."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.Note", + "Click New profile to add one or multiple publishing profiles to specify how to further proceed with the export files.", + "Legen Sie ber Neues Profil ein oder mehrere Verffentlichungsprofile an, um festzulegen wie mit den Exportdateien weiter zu verfahren ist."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.PublishingTarget", + "Publishing target", + "Verffentlichungsziel"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.DeploymentType", + "Publishing type", + "Art der Verffentlichung", + "Specifies the type of publishing.", + "Legt die Art Verffentlichung fest."); + + builder.AddOrUpdate("Common.Publishing", + "Publishing", + "Verffentlichung"); + + builder.AddOrUpdate("Common.NoFilesAvailable", + "There are no files available.", + "Es sind keine Dateien vorhanden."); + + builder.AddOrUpdate("Common.CopyToClipboard", "Copy to clipboard", "In die Zwischenablage kopieren"); + builder.AddOrUpdate("Common.CopyToClipboard.Succeeded", "Copied!", "Kopiert!"); + builder.AddOrUpdate("Common.CopyToClipboard.Failded", "Failed to copy.", "Kopieren ist fehlgeschlagen."); + + builder.AddOrUpdate("Products.NoBundledItems", + "No bundle items available", + "Keine Bundle-Bestandteile vorhanden"); + + builder.AddOrUpdate("Common.NoFileUploaded", + "There was no file uploaded.", + "Es wurde keine Datei hochgeladen."); + + builder.AddOrUpdate("Products.BasePriceInfo", + "Content: {0} {1} ({2} / {3} {1})", + "Inhalt: {0} {1} ({2} / {3} {1})"); + + builder.AddOrUpdate("Products.BasePriceInfo.LanguageInsensitive", + "{0} {1} ({2} / {3} {1})", + "{0} {1} ({2} / {3} {1})"); + + builder.AddOrUpdate("PrivateMessages.Disabled", + "Private messages are disabled.", + "Private Nachrichten sind deaktiviert."); + + builder.AddOrUpdate("Common.MethodNotSupportedForGuests", + "This function is not available for guests.", + "Diese Funktion steht fr Gste nicht zur Verfgung."); + + builder.AddOrUpdate("ContactUs.PrivacyAgreement", + "Privacy consent", + "Einwilligungserklrung Datenschutz"); + + builder.AddOrUpdate("ContactUs.PrivacyAgreement.MustBeAccepted", + "Please agree to the storage of your data.", + "Bitte stimmen Sie der Speicherung Ihrer Daten zu."); + + builder.AddOrUpdate("ContactUs.PrivacyAgreement.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("Admin.Configuration.Settings.CustomerUser.DisplayPrivacyAgreementOnContactUs", + "Get privacy consent for contact requests", + "Einwilligungserklrung im Kontaktformular fordern", + "Specifies whether a checkbox will be displayed on the contact page which requests the user to agree on storage of his data.", + "Bestimmt ob im Kontaktformular eine Checkbox angezeigt wird, die den Benutzer auffordert der Speicherung seiner Daten zuzustimmen."); + + + builder.Delete( + "Admin.DataExchange.Export.FolderAndFileName.Validate", + "Admin.DataExchange.Export.Deployment.IsPublic", + "Admin.DataExchange.Export.Deployment.CreateZip", + "Admin.Common.TemporaryFiles", + "Admin.Common.NoTempFilesFound"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.resx b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.resx new file mode 100644 index 0000000000..a653f94065 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.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/201607111548571_FixExportDeploymentIsPublic.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.Designer.cs new file mode 100644 index 0000000000..16b788408f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.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 FixExportDeploymentIsPublic : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(FixExportDeploymentIsPublic)); + + string IMigrationMetadata.Id + { + get { return "201607111548571_FixExportDeploymentIsPublic"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.cs b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.cs new file mode 100644 index 0000000000..f48207c9fc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.cs @@ -0,0 +1,21 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using Core.Data; + + public partial class FixExportDeploymentIsPublic : DbMigration + { + public override void Up() + { + if (HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer) + { + Sql("IF EXISTS(SELECT TOP 1 1 FROM sys.objects o INNER JOIN sys.columns c ON o.object_id = c.object_id WHERE o.name = 'ExportDeployment' AND c.name = 'IsPublic') ALTER TABLE [dbo].[ExportDeployment] DROP COLUMN [IsPublic];"); + } + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.resx b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.resx new file mode 100644 index 0000000000..55e598ca59 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index ea5109256d..49ae7dd7a8 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -10,6 +10,7 @@ public sealed class MigrationsConfiguration : DbMigrationsConfiguration - /// Object context - ///
+ /// + /// Object context + /// [DbConfigurationType(typeof(SmartDbConfiguration))] public abstract class ObjectContextBase : DbContext, IDbContext { @@ -44,6 +41,7 @@ protected ObjectContextBase(string nameOrConnectionString, string alias = null) : base(nameOrConnectionString) { this.HooksEnabled = true; + this.AutoCommitEnabled = true; this.Alias = null; this.EventPublisher = NullEventPublisher.Instance; } @@ -80,7 +78,7 @@ private void PerformPreSaveActions(out IList modifiedEntries, out if (hooksEnabled) { modifiedHookEntries = modifiedEntries - .Select(x => new HookedEntityEntry() + .Select(x => new HookedEntityEntry { Entity = x.Entity, PreSaveState = (SmartStore.Core.Data.EntityState)((int)x.State) @@ -193,7 +191,7 @@ private IEnumerable ToParameters(params object[] parameters) { for (int i = 0; i < result.Count; i++) { - result[i] = AttachEntityToContext(result[i]); + result[i] = Attach(result[i]); } } } @@ -226,7 +224,7 @@ private IEnumerable ToParameters(params object[] parameters) { for (int i = 0; i < result.Count; i++) { - result[i] = AttachEntityToContext(result[i]); + result[i] = Attach(result[i]); } } // close up the reader, we're done saving results @@ -334,12 +332,19 @@ public IDictionary GetModifiedProperties(BaseEntity entity) var props = new Dictionary(); var entry = this.Entry(entity); - var modifiedPropertyNames = from p in entry.CurrentValues.PropertyNames - where entry.Property(p).IsModified - select p; - foreach (var name in modifiedPropertyNames) + + // be aware of the entity state. you cannot get modified properties for detached entities. + if (entry.State != System.Data.Entity.EntityState.Detached) { - props.Add(name, entry.Property(name).OriginalValue); + 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); + } } return props; @@ -354,7 +359,7 @@ public override int SaveChanges() // SAVE NOW!!! bool validateOnSaveEnabled = this.Configuration.ValidateOnSaveEnabled; this.Configuration.ValidateOnSaveEnabled = false; - int result = this.Commit(); + int result = base.SaveChanges(); this.Configuration.ValidateOnSaveEnabled = validateOnSaveEnabled; PerformPostSaveActions(modifiedEntries, modifiedHookEntries); @@ -371,7 +376,7 @@ public override Task SaveChangesAsync() // SAVE NOW!!! bool validateOnSaveEnabled = this.Configuration.ValidateOnSaveEnabled; this.Configuration.ValidateOnSaveEnabled = false; - var result = this.CommitAsync(); + var result = base.SaveChangesAsync(); result.ContinueWith((t) => { @@ -382,7 +387,7 @@ public override Task SaveChangesAsync() return result; } - // codehint: sm-add (required for UoW implementation) + // required for UoW implementation public string Alias { get; internal set; } // performance on bulk inserts @@ -423,8 +428,22 @@ public bool ProxyCreationEnabled } } + public bool LazyLoadingEnabled + { + get + { + return this.Configuration.LazyLoadingEnabled; + } + set + { + this.Configuration.LazyLoadingEnabled = value; + } + } + public bool ForceNoTracking { get; set; } + public bool AutoCommitEnabled { get; set; } + public ITransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Unspecified) { var dbContextTransaction = this.Database.BeginTransaction(isolationLevel); @@ -477,79 +496,70 @@ protected internal bool IsSqlServer2012OrHigher() return s_isSqlServer2012OrHigher.Value; } - /// - /// Attach an entity to the context or return an already attached entity (if it was already attached) - /// - /// TEntity - /// Entity - /// Attached entity - protected virtual TEntity AttachEntityToContext(TEntity entity) where TEntity : BaseEntity, new() + public TEntity Attach(TEntity entity) where TEntity : BaseEntity { - // little hack here until Entity Framework really supports stored procedures - // otherwise, navigation properties of loaded entities are not loaded until an entity is attached to the context - var alreadyAttached = Set().Local.Where(x => x.Id == entity.Id).FirstOrDefault(); + var dbSet = Set(); + var alreadyAttached = dbSet.Local.FirstOrDefault(x => x.Id == entity.Id); + if (alreadyAttached == null) { - // attach new entity - Set().Attach(entity); + dbSet.Attach(entity); return entity; } - else + + return alreadyAttached; + } + + public bool IsAttached(TEntity entity) where TEntity : BaseEntity + { + if (entity != null) { - // entity is already loaded. - return alreadyAttached; + return Set().Local.Any(x => x.Id == entity.Id); } - } - public bool IsAttached(TEntity entity) where TEntity : BaseEntity, new() - { - Guard.ArgumentNotNull(() => entity); - return Set().Local.Where(x => x.Id == entity.Id).FirstOrDefault() != null; + return false; } - public void DetachEntity(TEntity entity) where TEntity : BaseEntity, new() + public void DetachEntity(TEntity entity) where TEntity : BaseEntity { - Guard.ArgumentNotNull(() => entity); - if (this.IsAttached(entity)) - { - ((IObjectContextAdapter)this).ObjectContext.Detach(entity); - } + this.Entry(entity).State = System.Data.Entity.EntityState.Detached; } - public void Detach(object entity) + public int DetachEntities(bool unchangedEntitiesOnly = true) where TEntity : class { - ((IObjectContextAdapter)this).ObjectContext.Detach(entity); - } + Func predicate = x => + { + if (x.Entity is TEntity) + { + if (x.State == System.Data.Entity.EntityState.Detached) + return false; - public int DetachAll() - { - var attachedEntities = this.ChangeTracker.Entries() - .Where(x => x.State != System.Data.Entity.EntityState.Detached) - .ToList(); - attachedEntities.Each(x => this.Entry(x.Entity).State = System.Data.Entity.EntityState.Detached); + if (unchangedEntitiesOnly) + return x.State == System.Data.Entity.EntityState.Unchanged; + + return true; + } + + return false; + }; + + var attachedEntities = this.ChangeTracker.Entries().Where(predicate).ToList(); + attachedEntities.Each(entry => entry.State = System.Data.Entity.EntityState.Detached); return attachedEntities.Count; } - public void ChangeState(TEntity entity, System.Data.Entity.EntityState newState) + public void ChangeState(TEntity entity, System.Data.Entity.EntityState newState) where TEntity : BaseEntity { - ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.ChangeObjectState(entity, newState); + Console.WriteLine("ChangeState ORIGINAL"); + this.Entry(entity).State = newState; } - public bool SetToUnchanged(TEntity entity) + public void ReloadEntity(TEntity entity) where TEntity : BaseEntity { - try - { - ChangeState(entity, System.Data.Entity.EntityState.Unchanged); - return true; - } - catch (Exception exc) - { - exc.Dump(); - return false; - } + this.Entry(entity).Reload(); } - private string FormatValidationExceptionMessage(IEnumerable results) + private string FormatValidationExceptionMessage(IEnumerable results) { var sb = new StringBuilder(); sb.Append("Entity validation failed" + Environment.NewLine); @@ -580,9 +590,10 @@ private void IgnoreMergedData(IList entries, bool ignore) foreach (var entry in entries) { var entityWithPossibleMergedData = entry.Entity as IMergedData; - if (entityWithPossibleMergedData != null) + { entityWithPossibleMergedData.MergedDataIgnore = ignore; + } } } catch { } @@ -590,69 +601,6 @@ private void IgnoreMergedData(IList entries, bool ignore) #endregion - #region EF helpers - - private int Commit() - { - int result = 0; - bool commitFailed = false; - do - { - commitFailed = false; - - try - { - result = base.SaveChanges(); - } - catch (DbUpdateConcurrencyException ex) - { - commitFailed = true; - - foreach (var entry in ex.Entries) - { - entry.Reload(); - } - } - } - while (commitFailed); - - return result; - } - - private Task CommitAsync() - { - var tcs = new TaskCompletionSource(); - - base.SaveChangesAsync().ContinueWith((t) => - { - if (!t.IsFaulted) - { - //if (t.IsCanceled) - //{ - // tcs.TrySetCanceled(); - // return; - //} - tcs.TrySetResult(t.Result); - return; - } - - var ex = t.Exception.InnerException; - if (ex != null && ex is DbUpdateConcurrencyException) - { - // try again - tcs.TrySetResult(this.CommitAsync().Result); - } - else - { - tcs.TrySetException(ex); - } - }); - - return tcs.Task; - } - - #endregion - #region Nested classes private class DbContextTransactionWrapper : ITransaction diff --git a/src/Libraries/SmartStore.Data/Setup/Builder/ActivityLogTypeMigrator.cs b/src/Libraries/SmartStore.Data/Setup/Builder/ActivityLogTypeMigrator.cs new file mode 100644 index 0000000000..b397af928a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Setup/Builder/ActivityLogTypeMigrator.cs @@ -0,0 +1,68 @@ +using System.Data.Entity; +using System.Linq; +using SmartStore.Core.Domain.Configuration; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Logging; + +namespace SmartStore.Data.Setup +{ + internal class ActivityLogTypeMigrator + { + private readonly SmartObjectContext _ctx; + private readonly DbSet _activityLogTypeRecords; + + public ActivityLogTypeMigrator(SmartObjectContext ctx) + { + Guard.ArgumentNotNull(() => ctx); + + _ctx = ctx; + _activityLogTypeRecords = _ctx.Set(); + } + + private Language GetDefaultAdminLanguage(DbSet settingRecords, DbSet languageRecords) + { + const string settingKey = "LocalizationSettings.DefaultAdminLanguageId"; + + var defaultAdminLanguageSetting = settingRecords.FirstOrDefault(x => x.Name == settingKey && x.StoreId == 0); + + if (defaultAdminLanguageSetting == null) + defaultAdminLanguageSetting = settingRecords.FirstOrDefault(x => x.Name == settingKey); + + if (defaultAdminLanguageSetting != null) + { + var defaultAdminLanguageId = defaultAdminLanguageSetting.Value.ToInt(); + if (defaultAdminLanguageId != 0) + { + var language = languageRecords.FirstOrDefault(x => x.Id == defaultAdminLanguageId); + if (language != null) + return language; + } + } + + return languageRecords.First(); + } + + public void AddActivityLogType(string systemKeyword, string enName, string deName) + { + Guard.ArgumentNotEmpty(() => systemKeyword); + Guard.ArgumentNotEmpty(() => enName); + Guard.ArgumentNotEmpty(() => deName); + + var record = _activityLogTypeRecords.FirstOrDefault(x => x.SystemKeyword == systemKeyword); + + if (record == null) + { + var language = GetDefaultAdminLanguage(_ctx.Set(), _ctx.Set()); + + _activityLogTypeRecords.Add(new ActivityLogType + { + Enabled = true, + SystemKeyword = systemKeyword, + Name = (language.UniqueSeoCode.IsCaseInsensitiveEqual("de") ? deName : enName) + }); + + _ctx.SaveChanges(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesMigrator.cs b/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesMigrator.cs index cdef6d5f8d..92d059bec8 100644 --- a/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesMigrator.cs +++ b/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesMigrator.cs @@ -35,7 +35,7 @@ public void Migrate(IEnumerable entries, bool updateTouched using (var scope = new DbContextScope(_ctx, autoDetectChanges: false)) { - var langMap = _languages.ToDictionary(x => x.UniqueSeoCode.EmptyNull().ToLower()); + var langMap = _languages.ToDictionarySafe(x => x.UniqueSeoCode.EmptyNull().ToLower()); var toDelete = new List(); var toUpdate = new List(); @@ -102,7 +102,7 @@ public void Migrate(IEnumerable entries, bool updateTouched toUpdate.Each(x => _ctx.Entry(x).State = System.Data.Entity.EntityState.Modified); // save now - _ctx.SaveChanges(); + int affectedRows = _ctx.SaveChanges(); } } diff --git a/src/Libraries/SmartStore.Data/Setup/Builder/PermissionMigrator.cs b/src/Libraries/SmartStore.Data/Setup/Builder/PermissionMigrator.cs new file mode 100644 index 0000000000..fe509e1cdf --- /dev/null +++ b/src/Libraries/SmartStore.Data/Setup/Builder/PermissionMigrator.cs @@ -0,0 +1,55 @@ +using System.Data.Entity; +using System.Linq; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Security; + +namespace SmartStore.Data.Setup +{ + internal class PermissionMigrator + { + private readonly SmartObjectContext _ctx; + private readonly DbSet _permissionRecords; + private readonly IQueryable _customerRoles; + + public PermissionMigrator(SmartObjectContext ctx) + { + Guard.ArgumentNotNull(() => ctx); + + _ctx = ctx; + _permissionRecords = _ctx.Set(); + _customerRoles = _ctx.Set().Expand(x => x.PermissionRecords); + } + + public void AddPermission(PermissionRecord permission, string[] rolesToMap) + { + Guard.ArgumentNotNull(() => permission); + Guard.ArgumentNotNull(() => rolesToMap); + + if (permission.SystemName.IsEmpty()) + return; + + var permissionRecord = _permissionRecords.FirstOrDefault(x => x.SystemName == permission.SystemName); + + if (permissionRecord == null) + { + _permissionRecords.Add(permission); + + _ctx.SaveChanges(); + } + + permissionRecord = _permissionRecords.FirstOrDefault(x => x.SystemName == permission.SystemName); + + foreach (var roleName in rolesToMap) + { + var role = _customerRoles.FirstOrDefault(x => x.SystemName == roleName); + if (role != null && !role.PermissionRecords.Any(x => x.SystemName == permission.SystemName)) + { + role.PermissionRecords.Add(permissionRecord); + } + } + + _ctx.SaveChanges(); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Setup/IDbMigrationExtensions.cs b/src/Libraries/SmartStore.Data/Setup/IDbMigrationExtensions.cs index 70bebdd73d..44f470e318 100644 --- a/src/Libraries/SmartStore.Data/Setup/IDbMigrationExtensions.cs +++ b/src/Libraries/SmartStore.Data/Setup/IDbMigrationExtensions.cs @@ -16,7 +16,7 @@ namespace SmartStore.Data.Setup public static class IDbMigrationExtensions { - public static void SqlFile(this IDbMigration migration, string fileName, Assembly assembly = null, string location = null) + public static void SqlFileOrResource(this IDbMigration migration, string fileName, Assembly assembly = null, string location = null) { Guard.ArgumentNotEmpty(() => fileName); diff --git a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs index 17027c7b7f..c5c2745302 100644 --- a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs +++ b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs @@ -56,7 +56,7 @@ public void Initialize(SmartObjectContext context) 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")), @@ -73,6 +73,10 @@ public IList Stores() var seName = GetSeName("company-logo"); var imgCompanyLogo = _ctx.Set().Where(x => x.SeoFilename == seName).FirstOrDefault(); + var currency = _ctx.Set().FirstOrDefault(x => x.CurrencyCode == "EUR"); + if (currency == null) + currency = _ctx.Set().First(); + var entities = new List() { new Store() @@ -82,7 +86,9 @@ public IList Stores() Hosts = "yourstore.com,www.yourstore.com", SslEnabled = false, DisplayOrder = 1, - LogoPictureId = imgCompanyLogo.Id + LogoPictureId = imgCompanyLogo.Id, + PrimaryStoreCurrencyId = currency.Id, + PrimaryExchangeRateCurrencyId = currency.Id } }; this.Alter(entities); @@ -3768,7 +3774,7 @@ public Address AdminAddress() public Customer SearchEngineUser() { - var entity = new Customer() + var entity = new Customer { Email = "builtin@search-engine-record.com", CustomerGuid = Guid.NewGuid(), @@ -3787,7 +3793,7 @@ public Customer SearchEngineUser() public Customer BackgroundTaskUser() { - var entity = new Customer() + var entity = new Customer { Email = "builtin@background-task-record.com", CustomerGuid = Guid.NewGuid(), @@ -3804,6 +3810,25 @@ public Customer BackgroundTaskUser() return entity; } + public Customer PdfConverterUser() + { + var entity = new Customer + { + Email = "builtin@pdf-converter-record.com", + CustomerGuid = Guid.NewGuid(), + PasswordFormat = PasswordFormat.Clear, + AdminComment = "Built-in system record used for the PDF converter.", + Active = true, + IsSystemAccount = true, + SystemName = SystemCustomerNames.PdfConverter, + CreatedOnUtc = DateTime.UtcNow, + LastActivityDateUtc = DateTime.UtcNow, + }; + + this.Alter(entity); + return entity; + } + public IList EmailAccounts() { var entities = new List() @@ -3958,7 +3983,7 @@ public IList MessageTemplates() { 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%
Zahlart: %Order.PaymentMethod%

%Order.Product(s)%

" + templateFooter, + 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, }, @@ -3966,7 +3991,7 @@ public IList MessageTemplates() { 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%
Zahlart: %Order.PaymentMethod%

%Order.Product(s)%

" + templateFooter, + 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, }, @@ -3974,7 +3999,7 @@ public IList MessageTemplates() { 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%
Zahlart: %Order.PaymentMethod%

Delivered Products:

%Shipment.Product(s)%

" + templateFooter, + 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, }, @@ -3983,7 +4008,7 @@ public IList MessageTemplates() { 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%
Zahlart: %Order.PaymentMethod%

%Order.Product(s)%

" + templateFooter, + 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, }, @@ -3991,7 +4016,7 @@ public IList MessageTemplates() { 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%
Zahlart: %Order.PaymentMethod%

%Order.Product(s)%

" + templateFooter, + 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, }, @@ -3999,7 +4024,7 @@ public IList MessageTemplates() { 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%
Zahlart: %Order.PaymentMethod%

Shipped Products:

%Shipment.Product(s)%

" + templateFooter, + 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, }, @@ -4023,7 +4048,7 @@ public IList MessageTemplates() { 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.

" + templateFooter, + Body = templateHeader + "

%Store.Name%



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

" + templateFooter, IsActive = true, EmailAccountId = eaGeneral.Id, }, @@ -4063,7 +4088,7 @@ public IList MessageTemplates() { Name = "Product.AskQuestion", Subject = "%Store.Name% - Question concerning '%Product.Name%' from %ProductQuestion.SenderName%", - Body = templateHeader + "

%ProductQuestion.Message%

%ProductQuestion.Message%

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

" + templateFooter, + Body = templateHeader + "

%ProductQuestion.Message%

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

" + templateFooter, IsActive = true, EmailAccountId = eaGeneral.Id, }, @@ -4225,8 +4250,6 @@ public IList Settings() }, new CurrencySettings() { - PrimaryStoreCurrencyId = _ctx.Set().First().Id, - PrimaryExchangeRateCurrencyId = _ctx.Set().First().Id, }, new MeasureSettings() { @@ -4748,23 +4771,15 @@ public IList ScheduleTasks() new ScheduleTask { Name = "Send emails", - Seconds = 60, + CronExpression = "* * * * *", // every Minute Type = "SmartStore.Services.Messages.QueuedMessagesSendTask, SmartStore.Services", Enabled = true, StopOnError = false, }, new ScheduleTask - { - Name = "Keep alive", - Seconds = 300, - Type = "SmartStore.Services.Common.KeepAliveTask, SmartStore.Services", - Enabled = true, - StopOnError = false, - }, - new ScheduleTask { Name = "Delete guests", - Seconds = 600, + CronExpression = "*/10 * * * *", // Every 10 minutes Type = "SmartStore.Services.Customers.DeleteGuestsTask, SmartStore.Services", Enabled = true, StopOnError = false, @@ -4772,7 +4787,7 @@ public IList ScheduleTasks() new ScheduleTask { Name = "Delete logs", - Seconds = 86400, // 1 day + CronExpression = "0 1 * * *", // At 01:00 Type = "SmartStore.Services.Logging.DeleteLogsTask, SmartStore.Services", Enabled = true, StopOnError = false, @@ -4780,7 +4795,7 @@ public IList ScheduleTasks() new ScheduleTask { Name = "Clear cache", - Seconds = 600, + CronExpression = "0 */4 * * *", // Every 04 hours Type = "SmartStore.Services.Caching.ClearCacheTask, SmartStore.Services", Enabled = false, StopOnError = false, @@ -4788,11 +4803,35 @@ public IList ScheduleTasks() new ScheduleTask { Name = "Update currency exchange rates", - Seconds = 900, + CronExpression = "0/15 * * * *", // Every 15 minutes Type = "SmartStore.Services.Directory.UpdateExchangeRateTask, SmartStore.Services", Enabled = true, StopOnError = false, }, + new ScheduleTask + { + Name = "Clear transient uploads", + CronExpression = "30 1,13 * * *", // At 01:30 and 13:30 + Type = "SmartStore.Services.Media.TransientMediaClearTask, SmartStore.Services", + Enabled = true, + StopOnError = false, + }, + new ScheduleTask + { + Name = "Clear email queue", + CronExpression = "0 2 * * *", // At 02:00 + Type = "SmartStore.Services.Messages.QueuedMessagesClearTask, SmartStore.Services", + Enabled = true, + StopOnError = false, + }, + new ScheduleTask + { + Name = "Cleanup temporary files", + CronExpression = "30 3 * * *", // At 03:30 + Type = "SmartStore.Services.Common.TempFileCleanupTask, SmartStore.Services", + Enabled = true, + StopOnError = false + } }; this.Alter(entities); return entities; @@ -6112,7 +6151,7 @@ public IList CategoriesFirstLevel() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_computers.jpeg"), "image/jpeg", GetSeName("Computers")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_computers.png"), "image/png", GetSeName("Computers")), Published = true, DisplayOrder = 2, CreatedOnUtc = DateTime.UtcNow, @@ -6128,7 +6167,7 @@ public IList CategoriesFirstLevel() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming.jpg"), "image/jpeg", GetSeName("Gaming")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming.png"), "image/png", GetSeName("Gaming")), Published = true, DisplayOrder = 3, CreatedOnUtc = DateTime.UtcNow, @@ -6146,7 +6185,7 @@ public IList CategoriesFirstLevel() PageSizeOptions = "12,18,36,72,150", //ParentCategoryId = categoryElectronics.Id, - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_cell_phones.jpeg"), "image/jpeg", GetSeName("Cell phones")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_cellphone.png"), "image/png", GetSeName("Cell phones")), Published = true, DisplayOrder = 4, CreatedOnUtc = DateTime.UtcNow, @@ -6162,7 +6201,7 @@ public IList CategoriesFirstLevel() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_digital_downloads.jpeg"), "image/jpeg", GetSeName("Digital downloads")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_digitaldownloads.jpg"), "image/jpeg", GetSeName("Digital downloads")), Published = true, DisplayOrder = 6, CreatedOnUtc = DateTime.UtcNow, @@ -6178,7 +6217,7 @@ public IList CategoriesFirstLevel() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gift_cards.jpeg"), "image/jpeg", GetSeName("Gift Cards")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_giftcards.png"), "image/png", GetSeName("Gift Cards")), Published = true, DisplayOrder = 12, CreatedOnUtc = DateTime.UtcNow, @@ -6194,7 +6233,7 @@ public IList CategoriesFirstLevel() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0001514_uhren.jpeg"), "image/jpeg", GetSeName("Watches")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_watches.png"), "image/png", GetSeName("Watches")), Published = true, DisplayOrder = 10, CreatedOnUtc = DateTime.UtcNow, @@ -6268,7 +6307,7 @@ public IList CategoriesSecondLevel() 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.jpg"), "image/jpeg", GetSeName("Desktops")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_desktops.png"), "image/png", GetSeName("Desktops")), PriceRanges = "-1000;1000-1200;1200-;", Published = true, DisplayOrder = 1, @@ -6286,7 +6325,7 @@ public IList CategoriesSecondLevel() 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.jpg"), "image/pjpeg", GetSeName("Notebooks")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_notebooks.png"), "image/png", GetSeName("Notebooks")), Published = true, DisplayOrder = 2, CreatedOnUtc = DateTime.UtcNow, @@ -6303,7 +6342,7 @@ public IList CategoriesSecondLevel() 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.jpg"), "image/jpeg", GetSeName("Gaming Accessories")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming_accessories.png"), "image/png", GetSeName("Gaming Accessories")), Published = true, DisplayOrder = 2, CreatedOnUtc = DateTime.UtcNow, @@ -6320,7 +6359,7 @@ public IList CategoriesSecondLevel() 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.jpg"), "image/jpeg", GetSeName("Games")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_games.png"), "image/png", GetSeName("Games")), Published = true, DisplayOrder = 3, CreatedOnUtc = DateTime.UtcNow, @@ -6361,7 +6400,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000746_apple_125.png"), "image/png", GetSeName("Apple")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_apple.png"), "image/png", GetSeName("Apple")), Published = true, DisplayOrder = 1, CreatedOnUtc = DateTime.UtcNow, @@ -6415,7 +6454,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "Dell_Logo.png"), "image/pjpeg", GetSeName("Dell")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-dell.png"), "image/png", GetSeName("Dell")), Published = true, DisplayOrder = 4, CreatedOnUtc = DateTime.UtcNow, @@ -6433,7 +6472,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "HP_Logo_2012.svg.png"), "image/png", GetSeName("HP")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-hp.png"), "image/png", GetSeName("HP")), Published = true, DisplayOrder = 5, CreatedOnUtc = DateTime.UtcNow, @@ -6469,7 +6508,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000904_samsung_125.jpeg"), "image/pjpeg", GetSeName("Samsung")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-samsung.png"), "image/png", GetSeName("Samsung")), Published = true, DisplayOrder = 7, CreatedOnUtc = DateTime.UtcNow, @@ -6505,7 +6544,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000993_trekstor.jpeg"), "image/pjpeg", GetSeName("TrekStor")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-trekstor.png"), "image/png", GetSeName("TrekStor")), Published = true, DisplayOrder = 9, CreatedOnUtc = DateTime.UtcNow, @@ -6523,7 +6562,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000994_western-digital.jpeg"), "image/pjpeg", GetSeName("Western Digital")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-westerndigital.png"), "image/png", GetSeName("Western Digital")), Published = true, DisplayOrder = 10, CreatedOnUtc = DateTime.UtcNow, @@ -6541,7 +6580,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "msi_logo1.jpg"), "image/pjpeg", GetSeName("MSI")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-msi.png"), "image/png", GetSeName("MSI")), Published = true, DisplayOrder = 11, CreatedOnUtc = DateTime.UtcNow, @@ -6559,7 +6598,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0001085_canon.jpeg"), "image/pjpeg", GetSeName("Canon")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-canon.png"), "image/png", GetSeName("Canon")), Published = true, DisplayOrder = 12, CreatedOnUtc = DateTime.UtcNow, @@ -6577,7 +6616,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0001086_casio.jpeg"), "image/pjpeg", GetSeName("Casio")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-casio.png"), "image/png", GetSeName("Casio")), Published = true, DisplayOrder = 13, CreatedOnUtc = DateTime.UtcNow, @@ -6595,7 +6634,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0001087_panasonic.jpeg"), "image/pjpeg", GetSeName("Panasonic")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-panasonic.png"), "image/png", GetSeName("Panasonic")), Published = true, DisplayOrder = 14, CreatedOnUtc = DateTime.UtcNow, @@ -6613,7 +6652,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000898_blackberry.jpeg"), "image/pjpeg", GetSeName("BlackBerry")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-blackberry.png"), "image/png", GetSeName("BlackBerry")), Published = true, DisplayOrder = 15, CreatedOnUtc = DateTime.UtcNow, @@ -6631,7 +6670,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000899_htc.png"), "image/pjpeg", GetSeName("HTC")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-htc.png"), "image/png", GetSeName("HTC")), Published = true, DisplayOrder = 16, CreatedOnUtc = DateTime.UtcNow, @@ -6649,7 +6688,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0001591_festina.jpeg"), "image/pjpeg", GetSeName("Festina")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_festina.png"), "image/png", GetSeName("Festina")), Published = true, DisplayOrder = 17, CreatedOnUtc = DateTime.UtcNow, @@ -6667,7 +6706,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0001328_certina_125.jpeg"), "image/pjpeg", GetSeName("Certina")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-certina.png"), "image/png", GetSeName("Certina")), Published = true, DisplayOrder = 18, CreatedOnUtc = DateTime.UtcNow, @@ -6685,7 +6724,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "sony-logo.jpg"), "image/pjpeg", GetSeName("Sony")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_sony.png"), "image/png", GetSeName("Sony")), Published = true, DisplayOrder = 19, CreatedOnUtc = DateTime.UtcNow, @@ -6703,7 +6742,7 @@ public IList Manufacturers() PageSize = 12, AllowCustomersToSelectPageSize = true, PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ubisoft-logo.jpg"), "image/pjpeg", GetSeName("Ubisoft")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_ubisoft.png"), "image/png", GetSeName("Ubisoft")), Published = true, DisplayOrder = 20, CreatedOnUtc = DateTime.UtcNow, @@ -6782,7 +6821,7 @@ public IList Products() product5GiftCard.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_5giftcart.jpeg"), "image/jpeg", GetSeName(product5GiftCard.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_giftcart.png"), "image/png", GetSeName(product5GiftCard.Name)), DisplayOrder = 1, }); @@ -6820,7 +6859,7 @@ public IList Products() product25GiftCard.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_25giftcart.jpeg"), "image/jpeg", GetSeName(product25GiftCard.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_giftcart.png"), "image/png", GetSeName(product25GiftCard.Name)), DisplayOrder = 1, }); @@ -6858,7 +6897,7 @@ public IList Products() product50GiftCard.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_50giftcart.jpeg"), "image/jpeg", GetSeName(product50GiftCard.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_giftcart.png"), "image/png", GetSeName(product50GiftCard.Name)), DisplayOrder = 1, }); @@ -7028,7 +7067,7 @@ public IList Products() //pictures productBooksBestGrillingRecipes.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000937_best-grilling-recipes.jpeg"), "image/jpeg", GetSeName(productBooksBestGrillingRecipes.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_bestgrillingrecipes.jpg"), "image/jpeg", GetSeName(productBooksBestGrillingRecipes.Name)), DisplayOrder = 1, }); @@ -7092,7 +7131,7 @@ public IList Products() //pictures productBooksCookingForTwo.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000938_cooking-for-two.jpeg"), "image/jpeg", GetSeName(productBooksCookingForTwo.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_cookingfortwo.jpg"), "image/jpeg", GetSeName(productBooksCookingForTwo.Name)), DisplayOrder = 1, }); @@ -7488,7 +7527,7 @@ public IList Products() //pictures productComputerDellInspiron23.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000953_dell-inspiron-one-23.jpeg"), "image/jpeg", GetSeName(productComputerDellInspiron23.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_dellinspiron23.png"), "image/png", GetSeName(productComputerDellInspiron23.Name)), DisplayOrder = 1, }); productComputerDellInspiron23.ProductPictures.Add(new ProductPicture() @@ -7596,7 +7635,7 @@ public IList Products() //pictures productComputerDellOptiplex3010.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000953_dell-inspiron-one-23.jpeg"), "image/jpeg", GetSeName(productComputerDellOptiplex3010.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_dellinspiron23.png"), "image/png", GetSeName(productComputerDellOptiplex3010.Name)), DisplayOrder = 1, }); productComputerDellOptiplex3010.ProductPictures.Add(new ProductPicture() @@ -7739,7 +7778,7 @@ public IList Products() #region pictures productComputerAcerAspireOne.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "acer-aspire-one.jpg"), "image/jpeg", GetSeName(productComputerAcerAspireOne.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_aceraspiresl1500.png"), "image/png", GetSeName(productComputerAcerAspireOne.Name)), DisplayOrder = 1, }); productComputerAcerAspireOne.ProductPictures.Add(new ProductPicture() @@ -7875,7 +7914,7 @@ public IList Products() #region Antonio Vivaldi: then spring - var productInstantDownloadVivaldi = new Product() + var productInstantDownloadVivaldi = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -7899,7 +7938,7 @@ public IList Products() AllowBackInStockSubscriptions = false, IsDownload = true, HasSampleDownload = true, - SampleDownload = new Download() + SampleDownload = new Download { DownloadGuid = Guid.NewGuid(), ContentType = "audio/mp3", @@ -8061,7 +8100,7 @@ public IList Products() //pictures productWatchesCertinaDSPodiumBigSize.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0001424-certina-ds-podium-big-size-herrenchronograph.jpg"), "image/jpeg", GetSeName(productWatchesCertinaDSPodiumBigSize.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_certina_ds_podium_big.png"), "image/png", GetSeName(productWatchesCertinaDSPodiumBigSize.Name)), DisplayOrder = 1, }); @@ -8159,7 +8198,7 @@ public IList Products() productPs3.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "sony-ps3-black.jpg"), "image/jpeg", GetSeName(productPs3.Name) + "-black"), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps3_black.png"), "image/png", GetSeName(productPs3.Name) + "-black"), DisplayOrder = 1 }); productPs3.ProductPictures.Add(new ProductPicture() @@ -8199,7 +8238,7 @@ public IList Products() productDualshock3Controller.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "sony-Dualshock3WirelessCont.jpg"), "image/jpeg", GetSeName(productDualshock3Controller.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_playstation3dualshock3.png"), "image/png", GetSeName(productDualshock3Controller.Name)), DisplayOrder = 1 }); @@ -8312,7 +8351,7 @@ public IList Products() productPs4.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "sony-ps4.jpg"), "image/jpeg", GetSeName(productPs4.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4.png"), "image/png", GetSeName(productPs4.Name)), DisplayOrder = 1 }); @@ -8347,7 +8386,7 @@ public IList Products() productDualshock4Controller.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "sony-Dualshock4WirelessCont.jpg"), "image/jpeg", GetSeName(productDualshock4Controller.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_dualshock4_wirelesscontroller.png"), "image/png", GetSeName(productDualshock4Controller.Name)), DisplayOrder = 1 }); @@ -8382,7 +8421,7 @@ public IList Products() productPs4Camera.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "sony-ps4-camera.jpg"), "image/jpeg", GetSeName(productPs4Camera.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4_camera.png"), "image/png", GetSeName(productPs4Camera.Name)), DisplayOrder = 1 }); @@ -8420,7 +8459,7 @@ public IList Products() productBundlePs4.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "sony-ps4-bundle.jpg"), "image/jpeg", GetSeName(productBundlePs4.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4_bundle.png"), "image/png", GetSeName(productBundlePs4.Name)), DisplayOrder = 1 }); @@ -8458,7 +8497,7 @@ public IList Products() productGroupAccessories.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming_accessories.jpg"), "image/jpeg", GetSeName(productGroupAccessories.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming_accessories.png"), "image/png", GetSeName(productGroupAccessories.Name)), DisplayOrder = 1 }); @@ -8599,7 +8638,7 @@ public IList Products() productPs3OneGame.ProductPictures.Add(new ProductPicture() { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "sony-ps3-plus-game.jpg"), "image/jpeg", GetSeName(productPs3OneGame.Name)), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps3_plus_game.png"), "image/png", GetSeName(productPs3OneGame.Name)), DisplayOrder = 1 }); @@ -8947,11 +8986,11 @@ 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!

smartstore.net is a fully customizable shop-system. It's stable and highly usable.
From downloads to documentation, www.smartstore.com offers a comprehensive base of information, resources, and support to the smartstore.net community.

", + 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!", + MetaTitle = "SmartStore.NET new release!", CreatedOnUtc = DateTime.Now }; var news2 = new NewsItem() @@ -8959,11 +8998,10 @@ public IList NewsItems() 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.", + 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.UtcNow.AddSeconds(1), CreatedOnUtc = DateTime.Now }; diff --git a/src/Libraries/SmartStore.Data/Setup/TestDatabaseInitializer.cs b/src/Libraries/SmartStore.Data/Setup/TestDatabaseInitializer.cs deleted file mode 100644 index 26128ae48f..0000000000 --- a/src/Libraries/SmartStore.Data/Setup/TestDatabaseInitializer.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Data.Entity; -using System.Data.Entity.Infrastructure; -using System.Data.Entity.Migrations; -using System.Data.Entity.Migrations.Infrastructure; - -namespace SmartStore.Data.Tests -{ - public class TestDatabaseInitializer : IDatabaseInitializer - where TContext : DbContext, new() - where TConfig : DbMigrationsConfiguration, new() - { - private readonly string _connectionString; - - public TestDatabaseInitializer(string connectionString) - { - Guard.ArgumentNotEmpty(() => connectionString); - this._connectionString = connectionString; - } - - protected virtual TConfig CreateConfiguration() - { - var config = new TConfig(); - config.TargetDatabase = new DbConnectionInfo(_connectionString, "System.Data.SqlServerCe.4.0"); - return config; - } - - - public void InitializeDatabase(TContext context) - { - var config = this.CreateConfiguration(); - new DbMigrator(config).Update(); - } - } -} diff --git a/src/Libraries/SmartStore.Data/SmartDbConfiguration.cs b/src/Libraries/SmartStore.Data/SmartDbConfiguration.cs index 476f612085..f67db1a271 100644 --- a/src/Libraries/SmartStore.Data/SmartDbConfiguration.cs +++ b/src/Libraries/SmartStore.Data/SmartDbConfiguration.cs @@ -1,11 +1,15 @@ using System; using System.Collections.Generic; 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; namespace SmartStore.Data { @@ -24,6 +28,26 @@ public SmartDbConfiguration() 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(typeof(SmartStore.Core.Caching.StaticCache))); + } + catch + { + cache = new InMemoryCache(); + } + + var transactionHandler = new CacheTransactionHandler(cache); + AddInterceptor(transactionHandler); + + Loaded += + (sender, args) => args.ReplaceService( + (s, _) => new CachingProviderServices(s, transactionHandler, + new EfCachingPolicy())); } } } diff --git a/src/Libraries/SmartStore.Data/SmartObjectContext.cs b/src/Libraries/SmartStore.Data/SmartObjectContext.cs index 97a8982b71..bbe46338db 100644 --- a/src/Libraries/SmartStore.Data/SmartObjectContext.cs +++ b/src/Libraries/SmartStore.Data/SmartObjectContext.cs @@ -12,14 +12,12 @@ using SmartStore.Data.Setup; namespace SmartStore.Data -{ - +{ /// /// Object context /// public class SmartObjectContext : ObjectContextBase { - static SmartObjectContext() { var initializer = new MigrateDatabaseInitializer @@ -53,7 +51,7 @@ protected override void OnModelCreating(DbModelBuilder modelBuilder) // && type.BaseType != null // && type.BaseType.IsGenericType // && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>)); - + var typesToRegister = from t in Assembly.GetExecutingAssembly().GetTypes() where t.Namespace.HasValue() && t.BaseType != null && diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index cd2bfbddaa..ff7fd39e2b 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -10,7 +10,7 @@ Properties SmartStore.Data SmartStore.Data - v4.5 + v4.5.1 512 @@ -62,20 +62,24 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + True + + + ..\..\packages\EntityFramework.Cache.1.0.0\lib\net45\EFCache.dll False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll False - ..\..\packages\EntityFramework.SqlServerCompact.6.1.0\lib\net45\EntityFramework.SqlServerCompact.dll + ..\..\packages\EntityFramework.SqlServerCompact.6.1.3\lib\net45\EntityFramework.SqlServerCompact.dll True @@ -89,9 +93,9 @@ True ..\..\packages\Microsoft.SqlServer.Scripting.11.0.2100.61\lib\Microsoft.SqlServer.Smo.dll - - False - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True @@ -115,7 +119,15 @@ Properties\AssemblyVersionInfo.cs + + + + + + + + 201403112331027_Initial.cs @@ -232,6 +244,172 @@ 201503032227054_V22Res.cs + + + 201503101851123_PdfConverterUser.cs + + + + 201502120858030_Licensing.cs + + + + 201504131412183_NewCategoryProperties.cs + + + + 201504171629262_V22Final.cs + + + + 201504232202590_AutoUpdateRes.cs + + + + 201504270946381_TempFileCleanupTask.cs + + + + 201504290937512_SendEmailsManually.cs + + + + 201505052021483_PdfConverterRoleFix.cs + + + + 201505061813010_CountryMultistore.cs + + + + 201505272236124_V222.cs + + + + 201506181858349_AclRecordCustomerRole.cs + + + + 201506211043073_PaymentShippingRestrictions.cs + + + + 201506261018157_Merge.cs + + + + 201506261756463_PrimaryStoreCurrencyMultiStore.cs + + + + 201507132241575_QueuedEmailAttachments.cs + + + + 201507072138058_MessageTemplateAttachments.cs + + + + 201507092146153_DownloadGuidIndex.cs + + + + 201507102159496_TransientMedia.cs + + + + 201507141647299_CustomerTablePerf.cs + + + + 201507200832223_SortFilterHomepageProducts.cs + + + + 201507210952098_PaymentMethodDescription.cs + + + + 201507242008201_WebScheduler.cs + + + + 201507250039446_Merge2.cs + + + + 201508042207146_RemoveKeepAlive.cs + + + + 201508121735397_AddSyncMapping.cs + + + + 201508142203054_CronExpressions.cs + + + + 201509031112324_Merge3.cs + + + + 201508091512101_ExportFramework.cs + + + + 201508211346171_Merge1.cs + + + + 201509021536425_ExportFramework1.cs + + + + 201509150931528_ExportFramework2.cs + + + + 201511271019577_ExportFramework3.cs + + + + 201512151526290_ImportFramework.cs + + + + 201601262000441_ImportFramework1.cs + + + + 201603121451066_ThirdPartyEmailHandOver.cs + + + + 201605020640016_GtinMpnIndex.cs + + + + 201605061916117_SwapColumnMappingValues.cs + + + + 201605111140288_ImportExtraData.cs + + + + 201605191216116_CheckoutAttributeMultiStore.cs + + + + 201605201911421_ExportRevision.cs + + + + 201607111548571_FixExportDeploymentIsPublic.cs + + + @@ -240,7 +418,6 @@ - @@ -277,7 +454,6 @@ - @@ -460,6 +636,129 @@ 201503032227054_V22Res.cs + + 201503101851123_PdfConverterUser.cs + + + 201502120858030_Licensing.cs + + + 201504131412183_NewCategoryProperties.cs + + + 201504171629262_V22Final.cs + + + 201504232202590_AutoUpdateRes.cs + + + 201504270946381_TempFileCleanupTask.cs + + + 201504290937512_SendEmailsManually.cs + + + 201505052021483_PdfConverterRoleFix.cs + + + 201505061813010_CountryMultistore.cs + + + 201505272236124_V222.cs + + + 201506181858349_AclRecordCustomerRole.cs + + + 201506211043073_PaymentShippingRestrictions.cs + + + 201506261018157_Merge.cs + + + 201506261756463_PrimaryStoreCurrencyMultiStore.cs + + + 201507132241575_QueuedEmailAttachments.cs + + + 201507072138058_MessageTemplateAttachments.cs + + + 201507092146153_DownloadGuidIndex.cs + + + 201507102159496_TransientMedia.cs + + + 201507141647299_CustomerTablePerf.cs + + + 201507200832223_SortFilterHomepageProducts.cs + + + 201507210952098_PaymentMethodDescription.cs + + + 201507242008201_WebScheduler.cs + + + 201507250039446_Merge2.cs + + + 201508042207146_RemoveKeepAlive.cs + + + 201508121735397_AddSyncMapping.cs + + + 201508142203054_CronExpressions.cs + + + 201509031112324_Merge3.cs + + + 201508091512101_ExportFramework.cs + + + 201508211346171_Merge1.cs + + + 201509021536425_ExportFramework1.cs + + + 201509150931528_ExportFramework2.cs + + + 201511271019577_ExportFramework3.cs + + + 201512151526290_ImportFramework.cs + + + 201601262000441_ImportFramework1.cs + + + 201603121451066_ThirdPartyEmailHandOver.cs + + + 201605020640016_GtinMpnIndex.cs + + + 201605061916117_SwapColumnMappingValues.cs + + + 201605111140288_ImportExtraData.cs + + + 201605191216116_CheckoutAttributeMultiStore.cs + + + 201605201911421_ExportRevision.cs + + + 201607111548571_FixExportDeploymentIsPublic.cs + @@ -475,6 +774,10 @@ + + + + diff --git a/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql b/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql new file mode 100644 index 0000000000..522b70dc27 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql @@ -0,0 +1,688 @@ +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/app.config b/src/Libraries/SmartStore.Data/app.config index 1c02a8052f..284b7ee573 100644 --- a/src/Libraries/SmartStore.Data/app.config +++ b/src/Libraries/SmartStore.Data/app.config @@ -4,12 +4,12 @@ - + - + - \ No newline at end of file + diff --git a/src/Libraries/SmartStore.Data/packages.config b/src/Libraries/SmartStore.Data/packages.config index 14cad35b89..b1c169951c 100644 --- a/src/Libraries/SmartStore.Data/packages.config +++ b/src/Libraries/SmartStore.Data/packages.config @@ -1,9 +1,10 @@  - - - + + + + - + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs b/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs index dcc6c9762c..35e853bbfc 100644 --- a/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs +++ b/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs @@ -17,7 +17,7 @@ public AuthorizeState(string returnUrl, OpenAuthenticationStatus openAuthenticat _returnUrl = returnUrl; AuthenticationStatus = openAuthenticationStatus; - //in way SEO friendly language URLs will be persisted + // in a way SEO friendly language URLs will be persisted if (AuthenticationStatus == OpenAuthenticationStatus.Authenticated) Result = new RedirectResult(!string.IsNullOrEmpty(_returnUrl) ? _returnUrl : "~/"); } diff --git a/src/Libraries/SmartStore.Services/Authentication/FormsAuthenticationService.cs b/src/Libraries/SmartStore.Services/Authentication/FormsAuthenticationService.cs index d13a5d60b5..05cddc7f7b 100644 --- a/src/Libraries/SmartStore.Services/Authentication/FormsAuthenticationService.cs +++ b/src/Libraries/SmartStore.Services/Authentication/FormsAuthenticationService.cs @@ -1,6 +1,7 @@ using System; using System.Web; using System.Web.Security; +using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.Services.Customers; @@ -11,7 +12,7 @@ namespace SmartStore.Services.Authentication /// public partial class FormsAuthenticationService : IAuthenticationService { - private readonly HttpContextBase _httpContext; + private readonly HttpContextBase _httpContext; private readonly ICustomerService _customerService; private readonly CustomerSettings _customerSettings; private readonly TimeSpan _expirationTimeSpan; @@ -24,8 +25,7 @@ public partial class FormsAuthenticationService : IAuthenticationService /// HTTP context /// Customer service /// Customer settings - public FormsAuthenticationService(HttpContextBase httpContext, - ICustomerService customerService, CustomerSettings customerSettings) + public FormsAuthenticationService(HttpContextBase httpContext, ICustomerService customerService, CustomerSettings customerSettings) { this._httpContext = httpContext; this._customerService = customerService; @@ -33,7 +33,6 @@ public FormsAuthenticationService(HttpContextBase httpContext, this._expirationTimeSpan = FormsAuthentication.Timeout; } - public virtual void SignIn(Customer customer, bool createPersistentCookie) { var now = DateTime.UtcNow.ToLocalTime(); @@ -77,18 +76,42 @@ public virtual Customer GetAuthenticatedCustomer() if (_cachedCustomer != null) return _cachedCustomer; - if (_httpContext == null || - _httpContext.Request == null || - !_httpContext.Request.IsAuthenticated || - !(_httpContext.User.Identity is FormsIdentity)) - { + if (_httpContext == null || _httpContext.Request == null || !_httpContext.Request.IsAuthenticated || _httpContext.User == null) return null; - } - var formsIdentity = (FormsIdentity)_httpContext.User.Identity; - var customer = GetAuthenticatedCustomerFromTicket(formsIdentity.Ticket); - if (customer != null && customer.Active && !customer.Deleted && customer.IsRegistered()) - _cachedCustomer = customer; + Customer customer = null; + FormsIdentity formsIdentity = null; + SmartStoreIdentity smartNetIdentity = null; + + if ((formsIdentity = _httpContext.User.Identity as FormsIdentity) != null) + { + customer = GetAuthenticatedCustomerFromTicket(formsIdentity.Ticket); + } + else if ((smartNetIdentity = _httpContext.User.Identity as SmartStoreIdentity) != null) + { + customer = _customerService.GetCustomerById(smartNetIdentity.CustomerId); + } + + if (customer != null && customer.Active && !customer.Deleted && customer.IsRegistered()) + { + if (customer.LastLoginDateUtc == null) + { + try + { + // This is most probably the very first "login" after registering. Delete the + // ASP.NET anonymous id cookie so that a new guest account can be created + // upon signing out. + System.Web.Security.AnonymousIdentificationModule.ClearAnonymousIdentifier(); + } + finally + { + customer.LastLoginDateUtc = DateTime.UtcNow; + _customerService.UpdateCustomer(customer); + } + } + _cachedCustomer = customer; + } + return _cachedCustomer; } @@ -101,10 +124,12 @@ public virtual Customer GetAuthenticatedCustomerFromTicket(FormsAuthenticationTi if (String.IsNullOrWhiteSpace(usernameOrEmail)) return null; + var customer = _customerSettings.UsernamesEnabled ? _customerService.GetCustomerByUsername(usernameOrEmail) : _customerService.GetCustomerByEmail(usernameOrEmail); + return customer; } - } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Blogs/BlogService.cs b/src/Libraries/SmartStore.Services/Blogs/BlogService.cs index 79a9d607e2..f3bb8103bb 100644 --- a/src/Libraries/SmartStore.Services/Blogs/BlogService.cs +++ b/src/Libraries/SmartStore.Services/Blogs/BlogService.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using System.Linq; using SmartStore.Core; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Blogs; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; +using SmartStore.Services.Localization; namespace SmartStore.Services.Blogs { @@ -15,13 +15,14 @@ namespace SmartStore.Services.Blogs /// public partial class BlogService : IBlogService { - #region Fields private readonly IRepository _blogPostRepository; private readonly IRepository _storeMappingRepository; - private readonly ICacheManager _cacheManager; - private readonly IEventPublisher _eventPublisher; + private readonly ICommonServices _services; + private readonly ILanguageService _languageService; + + private readonly BlogSettings _blogSettings; #endregion @@ -29,13 +30,15 @@ public partial class BlogService : IBlogService public BlogService(IRepository blogPostRepository, IRepository storeMappingRepository, - ICacheManager cacheManager, - IEventPublisher eventPublisher) + ICommonServices services, + ILanguageService languageService, + BlogSettings blogSettings) { _blogPostRepository = blogPostRepository; _storeMappingRepository = storeMappingRepository; - _cacheManager = cacheManager; - _eventPublisher = eventPublisher; + _services = services; + _languageService = languageService; + _blogSettings = blogSettings; this.QuerySettings = DbQuerySettings.Default; } @@ -58,7 +61,7 @@ public virtual void DeleteBlogPost(BlogPost blogPost) _blogPostRepository.Delete(blogPost); //event notification - _eventPublisher.EntityDeleted(blogPost); + _services.EventPublisher.EntityDeleted(blogPost); } /// @@ -84,17 +87,25 @@ public virtual BlogPost GetBlogPostById(int blogPostId) /// Page index /// Page size /// A value indicating whether to show hidden records + /// The maximum age of returned blog posts /// Blog posts public virtual IPagedList GetAllBlogPosts(int storeId, int languageId, - DateTime? dateFrom, DateTime? dateTo, int pageIndex, int pageSize, bool showHidden = false) + DateTime? dateFrom, DateTime? dateTo, int pageIndex, int pageSize, bool showHidden = false, DateTime? maxAge = null) { var query = _blogPostRepository.Table; + if (dateFrom.HasValue) query = query.Where(b => dateFrom.Value <= b.CreatedOnUtc); + if (dateTo.HasValue) query = query.Where(b => dateTo.Value >= b.CreatedOnUtc); + if (languageId > 0) query = query.Where(b => languageId == b.LanguageId); + + if (maxAge.HasValue) + query = query.Where(b => b.CreatedOnUtc >= maxAge.Value); + if (!showHidden) { var utcNow = DateTime.UtcNow; @@ -202,7 +213,7 @@ public virtual void InsertBlogPost(BlogPost blogPost) _blogPostRepository.Insert(blogPost); //event notification - _eventPublisher.EntityInserted(blogPost); + _services.EventPublisher.EntityInserted(blogPost); } /// @@ -217,7 +228,7 @@ public virtual void UpdateBlogPost(BlogPost blogPost) _blogPostRepository.Update(blogPost); //event notification - _eventPublisher.EntityUpdated(blogPost); + _services.EventPublisher.EntityUpdated(blogPost); } /// diff --git a/src/Libraries/SmartStore.Services/Blogs/IBlogService.cs b/src/Libraries/SmartStore.Services/Blogs/IBlogService.cs index eabf47a802..682ddbe191 100644 --- a/src/Libraries/SmartStore.Services/Blogs/IBlogService.cs +++ b/src/Libraries/SmartStore.Services/Blogs/IBlogService.cs @@ -33,9 +33,10 @@ public partial interface IBlogService /// Page index /// Page size /// A value indicating whether to show hidden records + /// The maximum age of returned blog posts /// Blog posts IPagedList GetAllBlogPosts(int storeId, int languageId, - DateTime? dateFrom, DateTime? dateTo, int pageIndex, int pageSize, bool showHidden = false); + DateTime? dateFrom, DateTime? dateTo, int pageIndex, int pageSize, bool showHidden = false, DateTime? maxAge = null); /// /// Gets all blog posts diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs index 939501292a..39825192a6 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs @@ -47,17 +47,17 @@ public static IList SortCategoriesForTree(this IList source, /// Product identifier /// Category identifier /// A ProductCategory that has the specified values; otherwise null - public static ProductCategory FindProductCategory(this IList source, - int productId, int categoryId) + public static ProductCategory FindProductCategory(this IList source, int productId, int categoryId) { - foreach (var productCategory in source) - if (productCategory.ProductId == productId && productCategory.CategoryId == categoryId) - return productCategory; - + foreach (var productCategory in source) + { + if (productCategory.ProductId == productId && productCategory.CategoryId == categoryId) + return productCategory; + } return null; } - public static string GetFullCategoryName(this Category category) + public static string GetCategoryNameWithAlias(this Category category) { if (category != null) { @@ -77,7 +77,7 @@ public static string GetCategoryNameWithPrefix(this Category category, ICategory { if (String.IsNullOrEmpty(result)) { - result = category.GetFullCategoryName(); + result = category.GetCategoryNameWithAlias(); } else { @@ -103,14 +103,13 @@ public static string GetCategoryBreadCrumb(this Category category, ICategoryServ while (category != null && !category.Deleted) { - // codehint: sm-edit if (String.IsNullOrEmpty(result)) { - result = category.GetFullCategoryName(); + result = category.GetCategoryNameWithAlias(); } else { - result = category.GetFullCategoryName() + " >> " + result; + result = category.GetCategoryNameWithAlias() + " >> " + result; } int parentId = category.ParentCategoryId; diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs index 0b16a2a4a0..86d8e52b2a 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; @@ -8,6 +9,7 @@ using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; +using SmartStore.Services.Customers; using SmartStore.Services.Localization; using SmartStore.Services.Security; using SmartStore.Services.Stores; @@ -44,7 +46,10 @@ public partial class CategoryService : ICategoryService private readonly IStoreMappingService _storeMappingService; private readonly IAclService _aclService; private readonly Lazy> _navigationFilters; - + private readonly ICustomerService _customerService; + private readonly IProductService _productService; + private readonly IStoreService _storeService; + #endregion #region Ctor @@ -72,7 +77,10 @@ public CategoryService(ICacheManager cacheManager, IEventPublisher eventPublisher, IStoreMappingService storeMappingService, IAclService aclService, - Lazy> navigationFilters) + Lazy> navigationFilters, + ICustomerService customerService, + IProductService productService, + IStoreService storeService) { this._cacheManager = cacheManager; this._categoryRepository = categoryRepository; @@ -86,6 +94,9 @@ public CategoryService(ICacheManager cacheManager, this._storeMappingService = storeMappingService; this._aclService = aclService; this._navigationFilters = navigationFilters; + this._customerService = customerService; + this._productService = productService; + this._storeService = storeService; this.QuerySettings = DbQuerySettings.Default; } @@ -109,13 +120,184 @@ private void DeleteAllCategories(IList categories, bool delete) var childCategories = GetAllCategoriesByParentCategoryId(category.Id, true); DeleteAllCategories(childCategories, delete); - } + } } #endregion #region Methods + 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)) + { + _aclRepository.AutoCommitEnabled = false; + + foreach (var subcategory in subcategories) + { + if (subcategory.SubjectToAcl != category.SubjectToAcl) + { + subcategory.SubjectToAcl = category.SubjectToAcl; + _categoryRepository.Update(subcategory); + } + + var existingAclRecords = _aclService.GetAclRecords(subcategory).ToDictionarySafe(x => x.CustomerRoleId); + + foreach (var customerRole in allCustomerRoles) + { + if (categoryCustomerRoles.Contains(customerRole.Id)) + { + if (!existingAclRecords.ContainsKey(customerRole.Id)) + { + _aclRepository.Insert(new AclRecord { CustomerRole = customerRole, CustomerRoleId = customerRole.Id, EntityId = subcategory.Id, EntityName = "Category" }); + } + } + else + { + AclRecord aclRecordToDelete; + if (existingAclRecords.TryGetValue(customerRole.Id, out aclRecordToDelete)) + { + _aclRepository.Delete(aclRecordToDelete); + } + } + } + } + + _aclRepository.Context.SaveChanges(); + + foreach (var product in products) + { + if (product.SubjectToAcl != category.SubjectToAcl) + { + product.SubjectToAcl = category.SubjectToAcl; + _productRepository.Update(product); + } + + var existingAclRecords = _aclService.GetAclRecords(product).ToDictionarySafe(x => x.CustomerRoleId); + + foreach (var customerRole in allCustomerRoles) + { + if (categoryCustomerRoles.Contains(customerRole.Id)) + { + if (!existingAclRecords.ContainsKey(customerRole.Id)) + { + _aclRepository.Insert(new AclRecord { CustomerRole = customerRole, CustomerRoleId = customerRole.Id, EntityId = product.Id, EntityName = "Product" }); + } + } + else + { + AclRecord aclRecordToDelete; + if (existingAclRecords.TryGetValue(customerRole.Id, out aclRecordToDelete)) + { + _aclRepository.Delete(aclRecordToDelete); + } + } + } + } + + _aclRepository.Context.SaveChanges(); + } + } + + 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); + + using (var scope = new DbContextScope(ctx: _storeMappingRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) + { + _storeMappingRepository.AutoCommitEnabled = false; + + foreach (var subcategory in subcategories) + { + if (subcategory.LimitedToStores != category.LimitedToStores) + { + subcategory.LimitedToStores = category.LimitedToStores; + _categoryRepository.Update(subcategory); + } + + var existingStoreMappingsRecords = _storeMappingService.GetStoreMappings(subcategory).ToDictionary(x => x.StoreId); + + foreach (var store in allStores) + { + if (categoryStoreMappings.Contains(store.Id)) + { + if (!existingStoreMappingsRecords.ContainsKey(store.Id)) + { + _storeMappingRepository.Insert(new StoreMapping { StoreId = store.Id, EntityId = subcategory.Id, EntityName = "Category" }); + } + } + else + { + StoreMapping storeMappingToDelete; + if (existingStoreMappingsRecords.TryGetValue(store.Id, out storeMappingToDelete)) + { + _storeMappingRepository.Delete(storeMappingToDelete); + } + } + } + } + + _storeMappingRepository.Context.SaveChanges(); + + foreach (var product in products) + { + if (product.LimitedToStores != category.LimitedToStores) + { + product.LimitedToStores = category.LimitedToStores; + _productRepository.Update(product); + } + + var existingStoreMappingsRecords = _storeMappingService.GetStoreMappings(product).ToDictionary(x => x.StoreId); + + foreach (var store in allStores) + { + if (categoryStoreMappings.Contains(store.Id)) + { + if (!existingStoreMappingsRecords.ContainsKey(store.Id)) + { + _storeMappingRepository.Insert(new StoreMapping { StoreId = store.Id, EntityId = product.Id, EntityName = "Product" }); + } + } + else + { + StoreMapping storeMappingToDelete; + if (existingStoreMappingsRecords.TryGetValue(store.Id, out storeMappingToDelete)) + { + _storeMappingRepository.Delete(storeMappingToDelete); + } + } + } + } + + _storeMappingRepository.Context.SaveChanges(); + } + } + /// /// Delete category /// @@ -132,6 +314,51 @@ public virtual void DeleteCategory(Category category, bool deleteChilds = false) var childCategories = GetAllCategoriesByParentCategoryId(category.Id, true); DeleteAllCategories(childCategories, deleteChilds); } + + public virtual IQueryable GetCategories( + string categoryName = "", + bool showHidden = false, + string alias = null, + bool applyNavigationFilters = true, + int storeId = 0) + { + var query = _categoryRepository.Table; + + if (!showHidden) + query = query.Where(c => c.Published); + + if (categoryName.HasValue()) + query = query.Where(c => c.Name.Contains(categoryName) || c.FullName.Contains(categoryName)); + + 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 = query.Where(c => !c.Deleted); + + return query; + } /// /// Gets all categories @@ -143,25 +370,17 @@ public virtual void DeleteCategory(Category category, bool deleteChilds = false) /// 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) + bool applyNavigationFilters = true, bool ignoreCategoriesWithoutExistingParent = true, int storeId = 0) { - var query = _categoryRepository.Table; - if (!showHidden) - query = query.Where(c => c.Published); - if (!String.IsNullOrWhiteSpace(categoryName)) - query = query.Where(c => c.Name.Contains(categoryName)); - if (!String.IsNullOrWhiteSpace(alias)) - query = query.Where(c => c.Alias.Contains(alias)); - query = query.Where(c => !c.Deleted); - query = query.OrderBy(c => c.ParentCategoryId).ThenBy(c => c.DisplayOrder); - - if (!showHidden) - { - query = ApplyHiddenCategoriesFilter(query, applyNavigationFilters); - query = query.OrderBy(c => c.ParentCategoryId).ThenBy(c => c.DisplayOrder); - } + var query = GetCategories(categoryName, showHidden, alias, applyNavigationFilters, storeId); + + query = query + .OrderBy(x => x.ParentCategoryId) + .ThenBy(x => x.DisplayOrder) + .ThenBy(x => x.Name); var unsortedCategories = query.ToList(); @@ -180,7 +399,8 @@ public virtual IPagedList GetAllCategories(string categoryName = "", i /// Category collection public IList GetAllCategoriesByParentCategoryId(int parentCategoryId, bool showHidden = false) { - string key = string.Format(CATEGORIES_BY_PARENT_CATEGORY_ID_KEY, parentCategoryId, showHidden, _workContext.CurrentCustomer.Id, _storeContext.CurrentStore.Id); + int storeId = _storeContext.CurrentStore.Id; + string key = string.Format(CATEGORIES_BY_PARENT_CATEGORY_ID_KEY, parentCategoryId, showHidden, _workContext.CurrentCustomer.Id, storeId); return _cacheManager.Get(key, () => { var query = _categoryRepository.Table; @@ -192,7 +412,7 @@ public IList GetAllCategoriesByParentCategoryId(int parentCategoryId, if (!showHidden) { - query = ApplyHiddenCategoriesFilter(query, false); + query = ApplyHiddenCategoriesFilter(query, false, storeId); query = query.OrderBy(c => c.DisplayOrder); } @@ -202,13 +422,13 @@ public IList GetAllCategoriesByParentCategoryId(int parentCategoryId, } - protected virtual IQueryable ApplyHiddenCategoriesFilter(IQueryable query, bool applyNavigationFilters) + protected virtual IQueryable ApplyHiddenCategoriesFilter(IQueryable query, bool applyNavigationFilters, int storeId = 0) { // ACL (access control list) - var allowedCustomerRolesIds = _workContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); - if (!QuerySettings.IgnoreAcl) { + 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 @@ -217,19 +437,18 @@ from acl in c_acl.DefaultIfEmpty() select c; } - if (!QuerySettings.IgnoreMultiStore) + // Store mapping + if (!QuerySettings.IgnoreMultiStore && storeId > 0) { - //Store mapping - var currentStoreId = _storeContext.CurrentStore.Id; 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 || currentStoreId == sm.StoreId + where !c.LimitedToStores || storeId == sm.StoreId select c; } - //only distinct categories (group by ID) + // only distinct categories (group by ID) query = from c in query group c by c.Id into cGroup orderby cGroup.Key @@ -378,24 +597,27 @@ public virtual IPagedList GetProductCategoriesByCategoryId(int if (categoryId == 0) return new PagedList(new List(), pageIndex, pageSize); - string key = string.Format(PRODUCTCATEGORIES_ALLBYCATEGORYID_KEY, showHidden, categoryId, pageIndex, pageSize, _workContext.CurrentCustomer.Id, _storeContext.CurrentStore.Id); + int storeId = _storeContext.CurrentStore.Id; + string key = string.Format(PRODUCTCATEGORIES_ALLBYCATEGORYID_KEY, showHidden, categoryId, pageIndex, pageSize, _workContext.CurrentCustomer.Id, storeId); + return _cacheManager.Get(key, () => { var query = from pc in _productCategoryRepository.Table join p in _productRepository.Table on pc.ProductId equals p.Id - where pc.CategoryId == categoryId && - !p.Deleted && - (showHidden || p.Published) - orderby pc.DisplayOrder + where pc.CategoryId == categoryId && !p.Deleted && (showHidden || p.Published) select pc; if (!showHidden) { - query = ApplyHiddenProductCategoriesFilter(query); - query = query.OrderBy(pc => pc.DisplayOrder); + query = ApplyHiddenProductCategoriesFilter(query, storeId); } + query = query + .OrderBy(pc => pc.DisplayOrder) + .ThenBy(pc => pc.Id); // required for paging! + var productCategories = new PagedList(query, pageIndex, pageSize); + return productCategories; }); } @@ -414,8 +636,7 @@ public virtual IList GetProductCategoriesByProductId(int produc string key = string.Format(PRODUCTCATEGORIES_ALLBYPRODUCTID_KEY, showHidden, productId, _workContext.CurrentCustomer.Id, _storeContext.CurrentStore.Id); return _cacheManager.Get(key, () => { - var table = _productCategoryRepository.Table; - var query = from pc in _productCategoryRepository.Expand(table, x => x.Category) + var query = from pc in _productCategoryRepository.Table.Expand(x => x.Category) join c in _categoryRepository.Table on pc.CategoryId equals c.Id where pc.ProductId == productId && !c.Deleted && @@ -444,35 +665,89 @@ orderby pc.DisplayOrder }); } - protected virtual IQueryable ApplyHiddenProductCategoriesFilter(IQueryable query) + public virtual Multimap GetProductCategoriesByProductIds(int[] productIds, bool? hasDiscountsApplied = null, bool showHidden = false) + { + Guard.ArgumentNotNull(() => productIds); + + var query = + from pc in _productCategoryRepository.TableUntracked.Expand(x => x.Category).Expand(x => x.Category.Picture) + 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; + + if (hasDiscountsApplied.HasValue) + { + query = query.Where(x => x.Category.HasDiscountsApplied == hasDiscountsApplied); + } + + var list = query.ToList(); + + if (!showHidden) + { + list = list.Where(x => _aclService.Authorize(x.Category) && _storeMappingService.Authorize(x.Category)).ToList(); + } + + var map = list.ToMultimap(x => x.ProductId, x => x); + + return map; + } + + public virtual Multimap GetProductCategoriesByCategoryIds(int[] categoryIds) + { + Guard.ArgumentNotNull(() => categoryIds); + + var query = _productCategoryRepository.TableUntracked + .Where(x => categoryIds.Contains(x.CategoryId)) + .OrderBy(x => x.DisplayOrder); + + var map = query + .ToList() + .ToMultimap(x => x.CategoryId, x => x); + + return map; + } + + protected virtual IQueryable ApplyHiddenProductCategoriesFilter(IQueryable query, int storeId = 0) { + bool group = false; + //ACL (access control list) - var allowedCustomerRolesIds = _workContext.CurrentCustomer.CustomerRoles - .Where(cr => cr.Active).Select(cr => cr.Id).ToList(); + if (!QuerySettings.IgnoreAcl) + { + group = true; + var allowedCustomerRolesIds = _workContext.CurrentCustomer.CustomerRoles.Where(cr => cr.Active).Select(cr => cr.Id).ToList(); - query = from pc in query - join c in _categoryRepository.Table on pc.CategoryId equals c.Id - 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) - select pc; + query = from pc in query + join c in _categoryRepository.Table on pc.CategoryId equals c.Id + 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) + select pc; + } //Store mapping - var currentStoreId = _storeContext.CurrentStore.Id; - query = from pc in query - join c in _categoryRepository.Table on pc.CategoryId equals c.Id - 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 || currentStoreId == sm.StoreId - select pc; - - //only distinct categories (group by ID) - query = from pc in query - group pc by pc.Id into pcGroup - orderby pcGroup.Key - select pcGroup.FirstOrDefault(); + if (!QuerySettings.IgnoreMultiStore && storeId > 0) + { + group = true; + query = from pc in query + join c in _categoryRepository.Table on pc.CategoryId equals c.Id + 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 pc; + } + + if (group) + { + //only distinct categories (group by ID) + query = from pc in query + group pc by pc.Id into pcGroup + orderby pcGroup.Key + select pcGroup.FirstOrDefault(); + } return query; } @@ -528,7 +803,8 @@ public virtual void UpdateProductCategory(ProductCategory productCategory) _eventPublisher.EntityUpdated(productCategory); } - public virtual string GetCategoryPath(Product product, int? languageId, Func pathLookup, Action addPathToCache, Func categoryLookup) + public virtual string GetCategoryPath(Product product, int? languageId, Func pathLookup, Action addPathToCache, Func categoryLookup, + ProductCategory prodCategory = null) { if (product == null) return string.Empty; @@ -540,7 +816,7 @@ public virtual string GetCategoryPath(Product product, int? languageId, Func(); var path = new List(); - var productCategory = GetProductCategoriesByProductId(product.Id).FirstOrDefault(); + var productCategory = prodCategory ?? GetProductCategoriesByProductId(product.Id).FirstOrDefault(); if (productCategory != null && productCategory.Category != null) { diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index 0060405d1c..f786bf9c78 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; using SmartStore.Services.Localization; @@ -83,15 +84,15 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli var utcNow = DateTime.UtcNow; // product download & sample download - int downloadId = product.DownloadId; - int? sampleDownloadId = product.SampleDownloadId; + int downloadId = 0; + int? sampleDownloadId = null; if (product.IsDownload) { var download = _downloadService.GetDownloadById(product.DownloadId); if (download != null) { - var downloadCopy = new Download() + var downloadCopy = new Download { DownloadGuid = Guid.NewGuid(), UseDownloadUrl = download.UseDownloadUrl, @@ -102,6 +103,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli Extension = download.Extension, IsNew = download.IsNew, }; + _downloadService.InsertDownload(downloadCopy); downloadId = downloadCopy.Id; } @@ -111,7 +113,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli var sampleDownload = _downloadService.GetDownloadById(product.SampleDownloadId.GetValueOrDefault()); if (sampleDownload != null) { - var sampleDownloadCopy = new Download() + var sampleDownloadCopy = new Download { DownloadGuid = Guid.NewGuid(), UseDownloadUrl = sampleDownload.UseDownloadUrl, @@ -122,6 +124,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli Extension = sampleDownload.Extension, IsNew = sampleDownload.IsNew }; + _downloadService.InsertDownload(sampleDownloadCopy); sampleDownloadId = sampleDownloadCopy.Id; } @@ -129,7 +132,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli } // product - productCopy = new Product() + productCopy = new Product { ProductTypeId = product.ProductTypeId, ParentGroupedProductId = product.ParentGroupedProductId, @@ -140,6 +143,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli ProductTemplateId = product.ProductTemplateId, AdminComment = product.AdminComment, ShowOnHomePage = product.ShowOnHomePage, + HomePageDisplayOrder = product.HomePageDisplayOrder, MetaKeywords = product.MetaKeywords, MetaDescription = product.MetaDescription, MetaTitle = product.MetaTitle, @@ -275,8 +279,11 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli _pictureService.LoadPictureBinary(picture), picture.MimeType, _pictureService.GetPictureSeName(newName), - true); - _productService.InsertProductPicture(new ProductPicture() + true, + false, + false); + + _productService.InsertProductPicture(new ProductPicture { ProductId = productCopy.Id, PictureId = pictureCopy.Id, @@ -288,7 +295,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // product <-> categories mappings foreach (var productCategory in product.ProductCategories) { - var productCategoryCopy = new ProductCategory() + var productCategoryCopy = new ProductCategory { ProductId = productCopy.Id, CategoryId = productCategory.CategoryId, @@ -302,7 +309,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // product <-> manufacturers mappings foreach (var productManufacturers in product.ProductManufacturers) { - var productManufacturerCopy = new ProductManufacturer() + var productManufacturerCopy = new ProductManufacturer { ProductId = productCopy.Id, ManufacturerId = productManufacturers.ManufacturerId, @@ -316,30 +323,28 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // 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 - }); + _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, - }); + _productService.InsertCrossSellProduct(new CrossSellProduct + { + ProductId1 = productCopy.Id, + ProductId2 = csProduct.ProductId2, + }); } // product specifications foreach (var productSpecificationAttribute in product.ProductSpecificationAttributes) { - var psaCopy = new ProductSpecificationAttribute() + var psaCopy = new ProductSpecificationAttribute { ProductId = productCopy.Id, SpecificationAttributeOptionId = productSpecificationAttribute.SpecificationAttributeOptionId, @@ -347,6 +352,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli ShowOnProductPage = productSpecificationAttribute.ShowOnProductPage, DisplayOrder = productSpecificationAttribute.DisplayOrder }; + _specificationAttributeService.InsertProductSpecificationAttribute(psaCopy); } @@ -360,9 +366,10 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // product <-> attributes mappings var associatedAttributes = new Dictionary(); var associatedAttributeValues = new Dictionary(); + foreach (var productVariantAttribute in _productAttributeService.GetProductVariantAttributesByProductId(product.Id)) { - var productVariantAttributeCopy = new ProductVariantAttribute() + var productVariantAttributeCopy = new ProductVariantAttribute { ProductId = productCopy.Id, ProductAttributeId = productVariantAttribute.ProductAttributeId, @@ -371,15 +378,17 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli AttributeControlTypeId = productVariantAttribute.AttributeControlTypeId, DisplayOrder = productVariantAttribute.DisplayOrder }; + _productAttributeService.InsertProductVariantAttribute(productVariantAttributeCopy); //save associated value (used for combinations copying) associatedAttributes.Add(productVariantAttribute.Id, productVariantAttributeCopy.Id); // product variant attribute values var productVariantAttributeValues = _productAttributeService.GetProductVariantAttributeValues(productVariantAttribute.Id); + foreach (var productVariantAttributeValue in productVariantAttributeValues) { - var pvavCopy = new ProductVariantAttributeValue() + var pvavCopy = new ProductVariantAttributeValue { ProductVariantAttributeId = productVariantAttributeCopy.Id, Name = productVariantAttributeValue.Name, @@ -392,6 +401,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli LinkedProductId = productVariantAttributeValue.LinkedProductId, Quantity = productVariantAttributeValue.Quantity, }; + _productAttributeService.InsertProductVariantAttributeValue(pvavCopy); //save associated value (used for combinations copying) @@ -408,7 +418,12 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli } // attribute combinations - foreach (var combination in _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id)) + using (var scope = new DbContextScope(lazyLoading: false, forceNoTracking: false)) + { + scope.LoadCollection(product, (Product p) => p.ProductVariantAttributeCombinations); + } + + foreach (var combination in product.ProductVariantAttributeCombinations) { //generate new AttributesXml according to new value IDs string newAttributesXml = ""; @@ -434,22 +449,20 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli var newPvav = _productAttributeService.GetProductVariantAttributeValueById(newPvavId); if (newPvav != null) { - newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, - newPva, newPvav.Id.ToString()); + newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, newPva, newPvav.Id.ToString()); } } } else { //just a text - newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, - newPva, oldPvaValueStr); + newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, newPva, oldPvaValueStr); } } } } } - var combinationCopy = new ProductVariantAttributeCombination() + var combinationCopy = new ProductVariantAttributeCombination { ProductId = productCopy.Id, AttributesXml = newAttributesXml, @@ -468,7 +481,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli BasePriceAmount = combination.BasePriceAmount, BasePriceBaseAmount = combination.BasePriceBaseAmount, DeliveryTimeId = combination.DeliveryTimeId, - QuantityUnitId = combination.QuantityUnitId, + QuantityUnitId = combination.QuantityUnitId, IsActive = combination.IsActive //IsDefaultCombination = combination.IsDefaultCombination }; @@ -478,15 +491,14 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // 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 - }); + _productService.InsertTierPrice(new TierPrice + { + ProductId = productCopy.Id, + StoreId = tierPrice.StoreId, + CustomerRoleId = tierPrice.CustomerRoleId, + Quantity = tierPrice.Quantity, + Price = tierPrice.Price + }); } // product <-> discounts mapping @@ -502,10 +514,11 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli _productService.UpdateHasDiscountsApplied(productCopy); // associated products - if (copyAssociatedProducts) + if (copyAssociatedProducts && product.ProductType != ProductType.BundledProduct) { - var searchContext = new ProductSearchContext() + var searchContext = new ProductSearchContext { + OrderBy = ProductSortingEnum.Position, ParentGroupedProductId = product.Id, PageSize = int.MaxValue, ShowHidden = true diff --git a/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs b/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs index 876a4d93af..c19242429d 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; @@ -10,6 +12,31 @@ namespace SmartStore.Services.Catalog /// public partial interface ICategoryService { + + /// + /// Assign acl to sub-categories and products + /// + /// Category Id + /// Reserved for future use: Whether to assign acl's to products which are contained in multiple categories. + /// Reserved for future use: Whether to delete existing Acls. + /// Reserved for future use: Whether to assign acl's only to categories. + void InheritAclIntoChildren(int categoryId, + bool touchProductsWithMultipleCategories = false, + bool touchExistingAcls = false, + bool categoriesOnly = false); + + /// + /// Assign stores to sub-categories and products + /// + /// Category Id + /// Reserved for future use: Whether to assign acl's to products which are contained in multiple categories. + /// Reserved for future use: Whether to delete existing Acls. + /// Reserved for future use: Whether to assign acl's only to categories. + void InheritStoresIntoChildren(int categoryId, + bool touchProductsWithMultipleCategories = false, + bool touchExistingAcls = false, + bool categoriesOnly = false); + /// /// Delete category /// @@ -17,6 +44,22 @@ public partial interface ICategoryService /// Whether to delete child categories or to set them to no parent. void DeleteCategory(Category category, bool deleteChilds = false); + /// + /// Gets categories + /// + /// Category name + /// 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 + /// Store identifier; 0 to load all records + /// Category query + IQueryable GetCategories( + string categoryName = "", + bool showHidden = false, + string alias = null, + bool applyNavigationFilters = true, + int storeId = 0); + /// /// Gets all categories /// @@ -27,9 +70,10 @@ public partial interface ICategoryService /// 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 IPagedList GetAllCategories(string categoryName = "", int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false, string alias = null, - bool applyNavigationFilters = true, bool ignoreCategoriesWithoutExistingParent = true); + bool applyNavigationFilters = true, bool ignoreCategoriesWithoutExistingParent = true, int storeId = 0); /// /// Gets all categories filtered by parent category identifier @@ -95,6 +139,22 @@ IPagedList GetProductCategoriesByCategoryId(int categoryId, /// Product category mapping collection IList GetProductCategoriesByProductId(int productId, bool showHidden = false); + /// + /// Gets product category mappings + /// + /// Product identifiers + /// A value indicating whether to filter categories with applied discounts + /// A value indicating whether to show hidden records + /// Map with product category mappings + Multimap GetProductCategoriesByProductIds(int[] productIds, bool? hasDiscountsApplied = null, bool showHidden = false); + + /// + /// Gets product category mappings + /// + /// Category identifiers + /// Map with product category mappings + Multimap GetProductCategoriesByCategoryIds(int[] categoryIds); + /// /// Gets a product category mapping /// @@ -122,8 +182,10 @@ IPagedList GetProductCategoriesByCategoryId(int categoryId, /// 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); + string GetCategoryPath(Product product, int? languageId, Func pathLookup, Action addPathToCache, Func categoryLookup, + ProductCategory prodCategory = null); } public static class ICategoryServiceExtensions diff --git a/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs b/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs index 63f8b647a8..24917da095 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; @@ -15,6 +17,14 @@ public partial interface IManufacturerService /// Manufacturer void DeleteManufacturer(Manufacturer manufacturer); + /// + /// Get manufacturer query + /// + /// A value indicating whether to show hidden records + /// Store identifier + /// Manufacturer query + IQueryable GetManufacturers(bool showHidden = false, int storeId = 0); + /// /// Gets all manufacturers /// @@ -22,24 +32,26 @@ public partial interface IManufacturerService /// Manufacturer collection IList GetAllManufacturers(bool showHidden = false); - /// - /// Gets all manufacturers - /// - /// Manufacturer name - /// A value indicating whether to show hidden records - /// Manufacturer collection - IList GetAllManufacturers(string manufacturerName, bool showHidden = false); - - /// - /// Gets all manufacturers - /// - /// Manufacturer name - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Manufacturers - IPagedList GetAllManufacturers(string manufacturerName, - int pageIndex, int pageSize, bool showHidden = false); + /// + /// Gets all manufacturers + /// + /// Manufacturer name + /// Whether to filter result by store identifier + /// A value indicating whether to show hidden records + /// Manufacturer collection + IList GetAllManufacturers(string manufacturerName, int storeId = 0, bool showHidden = false); + + /// + /// 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 + IPagedList GetAllManufacturers(string manufacturerName, + int pageIndex, int pageSize, int storeId = 0, bool showHidden = false); /// /// Gets a manufacturer @@ -85,6 +97,20 @@ IPagedList GetProductManufacturersByManufacturerId(int manu /// Product manufacturer mapping collection IList GetProductManufacturersByProductId(int productId, bool showHidden = false); + /// + /// Get product manufacturers by manufacturer identifiers + /// + /// Manufacturer identifiers + /// Product manufacturers + Multimap GetProductManufacturersByManufacturerIds(int[] manufacturerIds); + + /// + /// Get product manufacturers by product identifiers + /// + /// Product identifiers + /// Product manufacturers + Multimap GetProductManufacturersByProductIds(int[] productIds); + /// /// Gets a product manufacturer mapping /// diff --git a/src/Libraries/SmartStore.Services/Catalog/IPriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/IPriceCalculationService.cs index 9777824f0a..e3c7b9d5a6 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IPriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IPriceCalculationService.cs @@ -11,6 +11,13 @@ namespace SmartStore.Services.Catalog /// public partial interface IPriceCalculationService { + /// + /// Creates a price calculation context + /// + /// Products. null to lazy load data if required. + /// Price calculation context + PriceCalculationContext CreatePriceCalculationContext(IEnumerable products = null); + /// /// Get product special price (is valid) /// @@ -59,13 +66,15 @@ decimal GetFinalPrice(Product product, /// A value indicating whether include discounts or not for final price computation /// Shopping cart item quantity /// A product bundle item + /// Object with cargo data for better performance /// Final price decimal GetFinalPrice(Product product, Customer customer, decimal additionalCharge, bool includeDiscounts, int quantity, - ProductBundleItemData bundleItem = null); + ProductBundleItemData bundleItem = null, + PriceCalculationContext context = null); /// /// Gets the final price including bundle per-item pricing @@ -77,26 +86,43 @@ decimal GetFinalPrice(Product product, /// A value indicating whether include discounts or not for final price computation /// Shopping cart item quantity /// A product bundle item + /// Object with cargo data for better performance /// Final price - decimal GetFinalPrice(Product product, IList bundleItems, - Customer customer, decimal additionalCharge, bool includeDiscounts, int quantity, ProductBundleItemData bundleItem = null); + decimal GetFinalPrice(Product product, + IEnumerable bundleItems, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + ProductBundleItemData bundleItem = null, + PriceCalculationContext context = null); /// /// 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. - decimal GetLowestPrice(Product product, out bool displayFromMessage); + decimal GetLowestPrice(Product product, PriceCalculationContext context, out bool displayFromMessage); /// /// Get the lowest price of a grouped product. /// - /// Grouped product. - /// Products associated to product. - /// The associated product with the lowest price. + /// Grouped product + /// 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, IEnumerable associatedProducts, out Product lowestPriceProduct); + decimal? GetLowestPrice(Product product, PriceCalculationContext context, IEnumerable associatedProducts, out Product lowestPriceProduct); + + /// + /// Get the initial price including preselected attributes + /// + /// Product + /// Object with cargo data for better performance + /// Preselected price + decimal GetPreselectedPrice(Product product, PriceCalculationContext context); /// /// Gets the product cost @@ -155,13 +181,15 @@ decimal GetDiscountAmount(Product product, /// Product quantity /// Applied discount /// A product bundle item + /// Object with cargo data for better performance /// Discount amount decimal GetDiscountAmount(Product product, Customer customer, decimal additionalCharge, int quantity, out Discount appliedDiscount, - ProductBundleItemData bundleItem = null); + ProductBundleItemData bundleItem = null, + PriceCalculationContext context = null); /// diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index 21fd8bcdc7..f8565562e5 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -using SmartStore.Core.Domain.Catalog; using SmartStore.Collections; +using SmartStore.Core.Domain.Catalog; namespace SmartStore.Services.Catalog { @@ -14,63 +14,63 @@ public partial interface IProductAttributeParser /// /// Gets selected product variant attributes as a map of integer ids with their corresponding values. /// - /// Attributes XML + /// XML formatted attributes /// The deserialized map - Multimap DeserializeProductVariantAttributes(string attributes); - - /// - /// Gets selected product variant attributes - /// - /// Attributes - /// Selected product variant attributes - IList ParseProductVariantAttributes(string attributes); + Multimap DeserializeProductVariantAttributes(string attributesXml); - /// - /// Gets selected product variant attributes - /// - /// The attribute ids - /// Selected product variant attributes - IEnumerable ParseProductVariantAttributes(ICollection ids); + /// + /// Gets selected product variant attributes + /// + /// XML formatted attributes + /// Selected product variant attributes + IList ParseProductVariantAttributes(string attributesXml); /// /// Get product variant attribute values /// - /// Attributes + /// XML formatted attributes /// Product variant attribute values - IEnumerable ParseProductVariantAttributeValues(string attributes); + IEnumerable ParseProductVariantAttributeValues(string attributesXml); - /// - /// Gets selected product variant attribute value - /// - /// Attributes - /// Product variant attribute identifier - /// Product variant attribute value - IList ParseValues(string attributes, int productVariantAttributeId); + /// + /// Get list of product variant attribute values + /// + /// Map of combined attributes + /// Product variant attributes + /// List of product variant attribute values + IList 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); /// /// Adds an attribute /// - /// Attributes + /// XML formatted attributes /// Product variant attribute /// Value /// Attributes - string AddProductAttribute(string attributes, ProductVariantAttribute pva, string value); + string AddProductAttribute(string attributesXml, ProductVariantAttribute pva, string value); /// /// Are attributes equal /// - /// The attributes of the first product - /// The attributes of the second product + /// The attributes of the first product + /// The attributes of the second product /// Result - bool AreProductAttributesEqual(string attributes1, string attributes2); + bool AreProductAttributesEqual(string attributeXml1, string attributeXml2); - /// - /// Finds a product variant attribute combination by attributes stored in XML - /// - /// Product - /// Attributes in XML format - /// Found product variant attribute combination - ProductVariantAttributeCombination FindProductVariantAttributeCombination(Product product, string attributesXml); + /// + /// Finds a product variant attribute combination by attributes stored in XML + /// + /// Product identifier + /// XML formatted attributes + /// Found product variant attribute combination ProductVariantAttributeCombination FindProductVariantAttributeCombination(int productId, string attributesXml); /// @@ -80,14 +80,48 @@ public partial interface IProductAttributeParser /// List items with following structure: Product.Id, ProductAttribute.Id, Product_ProductAttribute_Mapping.Id, ProductVariantAttributeValue.Id List> DeserializeQueryData(string jsonData); + /// + /// Deserializes attribute data + /// + /// 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 - /// Attribute XML string /// Whether to URL encode /// Json string with attribute data - string SerializeQueryData(int productId, string attributesXml, bool urlEncode = true); + string SerializeQueryData(string attributesXml, int productId, bool urlEncode = true); + + /// + /// Serializes attribute data + /// + /// List with deserialized data + /// Whether to URL encode + /// Json string with attribute data + string SerializeQueryData(List> queryData, bool urlEncode = true); + + /// + /// 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); + + /// + /// Gets the URL of the product page including attributes query string + /// + /// Attribute query data + /// Product SEO name + /// URL of the product page including attributes query string + string GetProductUrlWithAttributes(List> queryData, string productSeName); #endregion @@ -96,26 +130,26 @@ public partial interface IProductAttributeParser /// /// Add gift card attrbibutes /// - /// Attributes + /// XML formatted attributes /// Recipient name /// Recipient email /// Sender name /// Sender email /// Message /// Attributes - string AddGiftCardAttribute(string attributes, string recipientName, + string AddGiftCardAttribute(string attributesXml, string recipientName, string recipientEmail, string senderName, string senderEmail, string giftCardMessage); /// /// Get gift card attrbibutes /// - /// Attributes + /// XML formatted attributes /// Recipient name /// Recipient email /// Sender name /// Sender email /// Message - void GetGiftCardAttribute(string attributes, out string recipientName, + void GetGiftCardAttribute(string attributesXml, out string recipientName, out string recipientEmail, out string senderName, out string senderEmail, out string giftCardMessage); diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs index c23e5de26e..eacf65250e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Media; +using SmartStore.Core; namespace SmartStore.Services.Catalog { @@ -59,6 +60,14 @@ public partial interface IProductAttributeService /// Product variant attribute mapping collection IList GetProductVariantAttributesByProductId(int productId); + /// + /// Gets product variant attribute mappings by multiple product identifiers + /// + /// The product identifiers + /// An optional control type filter. null loads all controls regardless of type. + /// A map with product id as key and a collection of variant attributes as value. + Multimap GetProductVariantAttributesByProductIds(int[] productIds, AttributeControlType? controlType); + /// /// Gets a product variant attribute mapping /// @@ -66,13 +75,13 @@ public partial interface IProductAttributeService /// Product variant attribute mapping ProductVariantAttribute GetProductVariantAttributeById(int productVariantAttributeId); - // codehint: sm-add - /// - /// Gets multiple product variant attribute mappings by their keys - /// - /// a list of keys - /// Product variant attribute mappings - IEnumerable GetProductVariantAttributesByIds(params int[] ids); + /// + /// Gets product variant attribute mappings + /// + /// Enumerable of product variant attribute mapping identifiers + /// Collection of already loaded product attribute mappings to reduce database round trips + /// + IList GetProductVariantAttributesByIds(IEnumerable productVariantAttributeIds, IEnumerable attributes = null); /// /// Inserts a product variant attribute mapping @@ -110,7 +119,6 @@ public partial interface IProductAttributeService /// Product variant attribute value ProductVariantAttributeValue GetProductVariantAttributeValueById(int productVariantAttributeValueId); - // codehint: sm-add /// /// Gets multiple product variant attribute value /// @@ -140,12 +148,30 @@ public partial interface IProductAttributeService /// Product variant attribute combination void DeleteProductVariantAttributeCombination(ProductVariantAttributeCombination combination); - /// - /// Gets all product variant attribute combinations - /// + /// + /// Gets all product variant attribute combinations + /// /// Product identifier - /// Product variant attribute combination collection - IList GetAllProductVariantAttributeCombinations(int productId); + /// Page index + /// Page size + /// Specifies whether loaded entities should be tracked by the state manager + /// Product variant attribute combination collection + IPagedList GetAllProductVariantAttributeCombinations(int productId, int pageIndex, int pageSize, bool untracked = true); + + /// + /// Gets a distinct list of picture identifiers. + /// Only pictures that are explicitly assigned to combinations are taken into account. + /// + /// Product id + /// Picture ids + IList GetAllProductVariantAttributeCombinationPictureIds(int productId); + + /// + /// Gets product variant attribute combinations by multiple product identifiers + /// + /// The product identifiers + /// A map with product id as key and a collection of product variant attribute combinations as value. + Multimap GetProductVariantAttributeCombinations(int[] productIds); /// /// Get the lowest price of all combinations for a product @@ -155,12 +181,19 @@ public partial interface IProductAttributeService decimal? GetLowestCombinationPrice(int productId); /// - /// Gets a product variant attribute combination + /// Gets a product variant attribute combination by identifier /// /// Product variant attribute combination identifier /// Product variant attribute combination ProductVariantAttributeCombination GetProductVariantAttributeCombinationById(int productVariantAttributeCombinationId); + /// + /// /// Gets a product variant attribute combination by SKU + /// + /// SKU + /// Product variant attribute combination + ProductVariantAttributeCombination GetProductVariantAttributeCombinationBySku(string sku); + /// /// Inserts a product variant attribute combination /// @@ -226,4 +259,17 @@ public partial interface IProductAttributeService #endregion } + + public static class IProductAttributeServiceExtensions + { + /// + /// Gets all product variant attribute combinations + /// + /// Product identifier + /// Product variant attribute combination collection + public static IList GetAllProductVariantAttributeCombinations(this IProductAttributeService service, int productId) + { + return service.GetAllProductVariantAttributeCombinations(productId, 0, int.MaxValue, true); + } + } } diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs index b997654c60..0b1f7649d5 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs @@ -1,8 +1,12 @@ +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; using SmartStore.Core.Domain.Orders; namespace SmartStore.Services.Catalog @@ -70,6 +74,15 @@ public partial interface IProductService /// Whether to search localized values. IQueryable PrepareProductSearchQuery(ProductSearchContext ctx, IEnumerable allowedCustomerRolesIds = null, bool searchLocalizedValue = false); + /// + /// Builds a product query based on the options in ProductSearchContext parameter. + /// + /// 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); + /// /// Update product review totals /// @@ -92,10 +105,24 @@ public partial interface IProductService /// /// Gets a product by GTIN /// - /// SKU + /// GTIN /// Product - Product GetProductByGtin(string sku); - + Product GetProductByGtin(string gtin); + + /// + /// Gets a product by manufacturer part number (MPN) + /// + /// Manufacturer part number + /// Product + Product GetProductByManufacturerPartNumber(string manufacturerPartNumber); + + /// + /// Gets a product by name + /// + /// Product name + /// Product + Product GetProductByName(string name); + /// /// Adjusts inventory /// @@ -141,6 +168,27 @@ public partial interface IProductService /// Product void UpdateHasDiscountsApplied(Product product); + /// + /// Get product tags by product identifiers + /// + /// Product identifiers + /// Map of product tags + Multimap GetProductTagsByProductIds(int[] productIds); + + /// + /// Get applied discounts by product identifiers + /// + /// Product identifiers + /// Map of applied discounts + Multimap GetAppliedDiscountsByProductIds(int[] productIds); + + /// + /// Get product specification attributes by product identifiers + /// + /// Product identifiers + /// Map of product specification attributes + Multimap GetProductSpecificationAttributesByProductIds(int[] productIds); + #endregion #region Related products @@ -178,6 +226,13 @@ public partial interface IProductService /// Related product void UpdateRelatedProduct(RelatedProduct relatedProduct); + /// + /// Ensure existence of all mutually related products + /// + /// First product identifier + /// Number of inserted related products + int EnsureMutuallyRelatedProducts(int productId1); + #endregion #region Cross-sell products @@ -223,6 +278,13 @@ public partial interface IProductService /// Cross-sells IList GetCrosssellProductsByShoppingCart(IList cart, int numberOfProducts); + /// + /// Ensure existence of all mutually cross selling products + /// + /// First product identifier + /// Number of inserted cross selling products + int EnsureMutuallyCrossSellProducts(int productId1); + #endregion #region Tier prices @@ -240,6 +302,15 @@ public partial interface IProductService /// Tier price TierPrice GetTierPriceById(int tierPriceId); + /// + /// Gets tier prices by product identifiers + /// + /// Product identifiers + /// Filter tier prices by customer + /// Filter tier prices by store + /// Map of tier prices + Multimap GetTierPricesByProductIds(int[] productIds, Customer customer = null, int storeId = 0); + /// /// Inserts a tier price /// @@ -269,6 +340,14 @@ public partial interface IProductService /// Product pictures IList GetProductPicturesByProductId(int productId); + /// + /// Get product pictures by product identifiers + /// + /// Product identifiers + /// Whether to only load the first picture for each product + /// Product pictures + Multimap GetProductPicturesByProductIds(int[] productIds, bool onlyFirstPicture = false); + /// /// Gets a product picture /// @@ -325,6 +404,14 @@ public partial interface IProductService /// List of bundle items IList GetBundleItems(int bundleProductId, bool showHidden = false); + /// + /// Get bundle items by product identifiers + /// + /// Product identifiers + /// A value indicating whether to show hidden records + /// Map of bundle items + Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false); + #endregion } diff --git a/src/Libraries/SmartStore.Services/Catalog/ISpecificationAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/ISpecificationAttributeService.cs index aac243a206..e9bc4a786e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ISpecificationAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ISpecificationAttributeService.cs @@ -21,9 +21,16 @@ public partial interface ISpecificationAttributeService /// /// Gets specification attributes /// - /// Specification attributes + /// Specification attribute query IQueryable GetSpecificationAttributes(); + /// + /// Gets specification attributes by identifier + /// + /// Identifiers + /// Specification attribute query + IQueryable GetSpecificationAttributesByIds(int[] ids); + /// /// Deletes a specification attribute /// @@ -124,7 +131,6 @@ IList GetProductSpecificationAttributesByProductI /// Product specification attribute mapping void UpdateProductSpecificationAttribute(ProductSpecificationAttribute productSpecificationAttribute); - /// codehint: sm-add 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 new file mode 100644 index 0000000000..3f7315905b --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -0,0 +1,489 @@ +using System; +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.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.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 + { + private readonly IRepository _categoryRepository; + private readonly IRepository _pictureRepository; + private readonly ICommonServices _services; + private readonly ICategoryTemplateService _categoryTemplateService; + private readonly IPictureService _pictureService; + private readonly ILocalizedEntityService _localizedEntityService; + private readonly FileDownloadManager _fileDownloadManager; + + private static readonly Dictionary>> _localizableProperties = new Dictionary>> + { + { "Name", x => x.Name }, + { "FullName", x => x.FullName }, + { "Description", x => x.Description }, + { "BottomDescription", x => x.BottomDescription }, + { "MetaKeywords", x => x.MetaKeywords }, + { "MetaDescription", x => x.MetaDescription }, + { "MetaTitle", x => x.MetaTitle } + }; + + public CategoryImporter( + IRepository categoryRepository, + IRepository pictureRepository, + ICommonServices services, + ICategoryTemplateService categoryTemplateService, + IPictureService pictureService, + ILocalizedEntityService localizedEntityService, + FileDownloadManager fileDownloadManager) + { + _categoryRepository = categoryRepository; + _pictureRepository = pictureRepository; + _services = services; + _categoryTemplateService = categoryTemplateService; + _pictureService = pictureService; + _localizedEntityService = localizedEntityService; + _fileDownloadManager = fileDownloadManager; + } + + protected override void Import(ImportExecuteContext context) + { + var srcToDestId = new Dictionary(); + + 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)) + { + var segmenter = context.DataSegmenter; + + Initialize(context); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var batch = segmenter.GetCurrentBatch(); + + // Perf: detach all entities + _categoryRepository.Context.DetachAll(false); + + context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + try + { + ProcessCategories(context, batch, templateViewPaths, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCategories"); + } + + // 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); + + // process slugs + if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) + { + try + { + _categoryRepository.Context.AutoDetectChangesEnabled = true; + ProcessSlugs(context, batch, typeof(Category).Name); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); + } + finally + { + _categoryRepository.Context.AutoDetectChangesEnabled = false; + } + } + + // process store mappings + if (segmenter.HasColumn("StoreIds")) + { + try + { + ProcessStoreMappings(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); + } + } + + // localizations + try + { + ProcessLocalizations(context, batch, _localizableProperties); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizedProperties"); + } + + // process pictures + if (srcToDestId.Any() && segmenter.HasColumn("ImageUrl") && !segmenter.IsIgnored("PictureId")) + { + try + { + _categoryRepository.Context.AutoDetectChangesEnabled = true; + ProcessPictures(context, batch, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessPictures"); + } + finally + { + _categoryRepository.Context.AutoDetectChangesEnabled = false; + } + } + } + + // map parent id of inserted categories + if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentCategoryId") && !segmenter.IsIgnored("ParentCategoryId")) + { + segmenter.Reset(); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var batch = segmenter.GetCurrentBatch(); + _categoryRepository.Context.DetachAll(false); + + try + { + ProcessParentMappings(context, batch, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); + } + } + } + } + } + + protected virtual int ProcessPictures( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary srcToDestId) + { + 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"); + } + } + } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.ToAllMessages(), row.GetRowInfo(), "ImageUrls"); + } + } + + var num = _categoryRepository.Context.SaveChanges(); + + return num; + } + + protected virtual int ProcessLocalizations( + ImportExecuteContext context, + IEnumerable> batch, + string[] localizedProperties) + { + if (localizedProperties.Length == 0) + { + return 0; + } + + bool shouldSave = false; + + foreach (var row in batch) + { + foreach (var prop in localizedProperties) + { + var lambda = _localizableProperties[prop]; + foreach (var lang in context.Languages) + { + var code = lang.UniqueSeoCode; + string value; + + if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) + { + _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); + shouldSave = true; + } + } + } + } + + if (shouldSave) + { + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } + + return 0; + } + + protected virtual int ProcessParentMappings( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary srcToDestId) + { + foreach (var row in batch) + { + var id = row.GetDataValue("Id"); + var rawParentId = row.GetDataValue("ParentCategoryId"); + var parentId = rawParentId.ToInt(-1); + + if (id != 0 && parentId != -1 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentId)) + { + // only touch hierarchical data if child and parent were inserted + if (srcToDestId[id].Inserted && srcToDestId[parentId].Inserted && srcToDestId[id].DestinationId != 0) + { + var category = _categoryRepository.GetById(srcToDestId[id].DestinationId); + if (category != null) + { + category.ParentCategoryId = srcToDestId[parentId].DestinationId; + + _categoryRepository.Update(category); + } + } + } + } + + var num = _categoryRepository.Context.SaveChanges(); + + return num; + } + + protected virtual int ProcessCategories( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary templateViewPaths, + Dictionary srcToDestId) + { + _categoryRepository.AutoCommitEnabled = true; + + Category lastInserted = null; + Category lastUpdated = null; + var defaultTemplateId = templateViewPaths["CategoryTemplate.ProductsInGridOrLines"]; + + foreach (var row in batch) + { + Category category = null; + var id = row.GetDataValue("Id"); + var name = row.GetDataValue("Name"); + + foreach (var keyName in context.KeyFieldNames) + { + switch (keyName) + { + case "Id": + if (id != 0) + category = _categoryRepository.GetById(id); + break; + case "Name": + if (name.HasValue()) + category = _categoryRepository.Table.FirstOrDefault(x => x.Name == name); + break; + } + + if (category != null) + break; + } + + if (category == null) + { + if (context.UpdateOnly) + { + ++context.Result.SkippedRecords; + continue; + } + + // a Name is required with new categories + if (!row.Segmenter.HasColumn("Name")) + { + ++context.Result.SkippedRecords; + context.Result.AddError("The 'Name' field is required for new categories. Skipping row.", row.GetRowInfo(), "Name"); + continue; + } + + category = new Category(); + } + + row.Initialize(category, name ?? category.Name); + + if (!row.IsNew && !category.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) + { + // Perf: use this later for SeName updates. + row.NameChanged = true; + } + + row.SetProperty(context.Result, (x) => x.Name); + row.SetProperty(context.Result, (x) => x.FullName); + row.SetProperty(context.Result, (x) => x.Description); + row.SetProperty(context.Result, (x) => x.BottomDescription); + 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.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); + row.SetProperty(context.Result, (x) => x.DisplayOrder); + row.SetProperty(context.Result, (x) => x.Alias); + row.SetProperty(context.Result, (x) => x.DefaultViewMode); + // 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()); + + string tvp; + if (row.TryGetDataValue("CategoryTemplateViewPath", out tvp, row.IsTransient)) + { + 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 }); + } + + if (row.IsTransient) + { + _categoryRepository.Insert(category); + lastInserted = category; + } + else + { + _categoryRepository.Update(category); + lastUpdated = category; + } + } + + // commit whole batch at once + var num = _categoryRepository.Context.SaveChanges(); + + // 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); + } + + return num; + } + + public static string[] SupportedKeyFields + { + get + { + return new string[] { "Id", "Name" }; + } + } + + public static string[] DefaultKeyFields + { + get + { + return new string[] { "Name", "Id" }; + } + } + + public class ImportCategoryMapping + { + public int DestinationId { get; set; } + public bool Inserted { get; set; } + } + } + +} diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs new file mode 100644 index 0000000000..6228f5b443 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -0,0 +1,738 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using SmartStore.Core.Async; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Events; +using SmartStore.Services.DataExchange.Import; +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( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary templateViewPaths, + Dictionary srcToDestId) + { + _productRepository.AutoCommitEnabled = false; + + Product lastInserted = null; + Product lastUpdated = null; + var defaultTemplateId = templateViewPaths["ProductTemplate.Simple"]; + + foreach (var row in batch) + { + Product product = null; + var id = row.GetDataValue("Id"); + + foreach (var keyName in context.KeyFieldNames) + { + var keyValue = row.GetDataValue(keyName); + + if (keyValue.HasValue() || id > 0) + { + switch (keyName) + { + case "Id": + product = _productService.GetProductById(id); + break; + case "Sku": + product = _productService.GetProductBySku(keyValue); + break; + case "Gtin": + product = _productService.GetProductByGtin(keyValue); + break; + case "ManufacturerPartNumber": + product = _productService.GetProductByManufacturerPartNumber(keyValue); + break; + case "Name": + product = _productService.GetProductByName(keyValue); + break; + } + } + + if (product != null) + break; + } + + if (product == null) + { + if (context.UpdateOnly) + { + ++context.Result.SkippedRecords; + continue; + } + + // a Name is required for new products. + if (!row.HasDataValue("Name")) + { + ++context.Result.SkippedRecords; + context.Result.AddError("The 'Name' field is required for new products. Skipping row.", row.GetRowInfo(), "Name"); + continue; + } + + product = new Product(); + } + + var name = row.GetDataValue("Name"); + + row.Initialize(product, name ?? product.Name); + + if (!row.IsNew) + { + if (!product.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) + { + // Perf: use this later for SeName updates. + row.NameChanged = true; + } + } + + row.SetProperty(context.Result, (x) => x.ProductTypeId, (int)ProductType.SimpleProduct); + row.SetProperty(context.Result, (x) => x.VisibleIndividually, true); + row.SetProperty(context.Result, (x) => x.Name); + row.SetProperty(context.Result, (x) => x.ShortDescription); + row.SetProperty(context.Result, (x) => x.FullDescription); + row.SetProperty(context.Result, (x) => x.AdminComment); + row.SetProperty(context.Result, (x) => x.ShowOnHomePage); + row.SetProperty(context.Result, (x) => x.HomePageDisplayOrder); + 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.AllowCustomerReviews, true); + row.SetProperty(context.Result, (x) => x.ApprovedRatingSum); + row.SetProperty(context.Result, (x) => x.NotApprovedRatingSum); + row.SetProperty(context.Result, (x) => x.ApprovedTotalReviews); + row.SetProperty(context.Result, (x) => x.NotApprovedTotalReviews); + row.SetProperty(context.Result, (x) => x.Published, true); + row.SetProperty(context.Result, (x) => x.Sku); + row.SetProperty(context.Result, (x) => x.ManufacturerPartNumber); + row.SetProperty(context.Result, (x) => x.Gtin); + row.SetProperty(context.Result, (x) => x.IsGiftCard); + row.SetProperty(context.Result, (x) => x.GiftCardTypeId); + row.SetProperty(context.Result, (x) => x.RequireOtherProducts); + 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.MaxNumberOfDownloads, 10); + row.SetProperty(context.Result, (x) => x.DownloadExpirationDays); + row.SetProperty(context.Result, (x) => x.DownloadActivationTypeId, 1); + row.SetProperty(context.Result, (x) => x.HasSampleDownload); + row.SetProperty(context.Result, (x) => x.SampleDownloadId, (int?)null, ZeroToNull); // TODO: global scope + row.SetProperty(context.Result, (x) => x.HasUserAgreement); + row.SetProperty(context.Result, (x) => x.UserAgreementText); + row.SetProperty(context.Result, (x) => x.IsRecurring); + row.SetProperty(context.Result, (x) => x.RecurringCycleLength, 100); + row.SetProperty(context.Result, (x) => x.RecurringCyclePeriodId); + row.SetProperty(context.Result, (x) => x.RecurringTotalCycles, 10); + row.SetProperty(context.Result, (x) => x.IsShipEnabled, true); + row.SetProperty(context.Result, (x) => x.IsFreeShipping); + row.SetProperty(context.Result, (x) => x.AdditionalShippingCharge); + row.SetProperty(context.Result, (x) => x.IsEsd); + row.SetProperty(context.Result, (x) => x.IsTaxExempt); + row.SetProperty(context.Result, (x) => x.TaxCategoryId, 1); // TODO: global scope + row.SetProperty(context.Result, (x) => x.ManageInventoryMethodId); + row.SetProperty(context.Result, (x) => x.StockQuantity, 10000); + row.SetProperty(context.Result, (x) => x.DisplayStockAvailability); + row.SetProperty(context.Result, (x) => x.DisplayStockQuantity); + row.SetProperty(context.Result, (x) => x.MinStockQuantity); + row.SetProperty(context.Result, (x) => x.LowStockActivityId); + row.SetProperty(context.Result, (x) => x.NotifyAdminForQuantityBelow, 1); + 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.DisableBuyButton); + row.SetProperty(context.Result, (x) => x.DisableWishlistButton); + row.SetProperty(context.Result, (x) => x.AvailableForPreOrder); + row.SetProperty(context.Result, (x) => x.CallForPrice); + row.SetProperty(context.Result, (x) => x.Price); + row.SetProperty(context.Result, (x) => x.OldPrice); + row.SetProperty(context.Result, (x) => x.ProductCost); + row.SetProperty(context.Result, (x) => x.SpecialPrice); + row.SetProperty(context.Result, (x) => x.SpecialPriceStartDateTimeUtc); + row.SetProperty(context.Result, (x) => x.SpecialPriceEndDateTimeUtc); + row.SetProperty(context.Result, (x) => x.CustomerEntersPrice); + row.SetProperty(context.Result, (x) => x.MinimumCustomerEnteredPrice); + row.SetProperty(context.Result, (x) => x.MaximumCustomerEnteredPrice, 1000); + // HasTierPrices... ignore as long as no tier prices are imported + // LowestAttributeCombinationPrice... ignore as long as no combinations are imported + row.SetProperty(context.Result, (x) => x.Weight); + 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.DisplayOrder); + row.SetProperty(context.Result, (x) => x.DeliveryTimeId); // TODO: global scope + row.SetProperty(context.Result, (x) => x.QuantityUnitId); // TODO: global scope + row.SetProperty(context.Result, (x) => x.BasePriceEnabled); + row.SetProperty(context.Result, (x) => x.BasePriceMeasureUnit); + row.SetProperty(context.Result, (x) => x.BasePriceAmount); + row.SetProperty(context.Result, (x) => x.BasePriceBaseAmount); + row.SetProperty(context.Result, (x) => x.BundleTitleText); + row.SetProperty(context.Result, (x) => x.BundlePerItemShipping); + row.SetProperty(context.Result, (x) => x.BundlePerItemPricing); + row.SetProperty(context.Result, (x) => x.BundlePerItemShoppingCart); + row.SetProperty(context.Result, (x) => x.AvailableStartDateTimeUtc); + row.SetProperty(context.Result, (x) => x.AvailableEndDateTimeUtc); + // 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()); + + string tvp; + if (row.TryGetDataValue("ProductTemplateViewPath", out tvp, row.IsTransient)) + { + 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 }); + } + + if (row.IsTransient) + { + _productRepository.Insert(product); + lastInserted = product; + } + else + { + _productRepository.Update(product); + lastUpdated = product; + } + } + + // commit whole batch at once + var num = _productRepository.Context.SaveChanges(); + + // 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); + } + + return num; + } + + protected virtual int ProcessProductMappings( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary srcToDestId) + { + _productRepository.AutoCommitEnabled = false; + + foreach (var row in batch) + { + var id = row.GetDataValue("Id"); + var parentGroupedProductId = row.GetDataValue("ParentGroupedProductId"); + + if (id != 0 && parentGroupedProductId != 0 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentGroupedProductId)) + { + // 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); + } + } + } + } + + var num = _productRepository.Context.SaveChanges(); + + return num; + } + + protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnumerable> batch) + { + // 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); + + foreach (var row in batch) + { + var imageUrls = row.GetDataValue>("ImageUrls"); + if (imageUrls.IsNullOrEmpty()) + continue; + + var imageNumber = 0; + var displayOrder = -1; + var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); + var imageFiles = new List(); + + // collect required image file infos + foreach (var urlOrPath in imageUrls) + { + var image = CreateDownloadImage(urlOrPath, seoName, ++imageNumber); + + if (image != null) + imageFiles.Add(image); + + if (imageFiles.Count >= numberOfPictures) + break; + } + + // download images + if (imageFiles.Any(x => x.Url.HasValue())) + { + // 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 + foreach (var image in imageFiles.OrderBy(x => x.DisplayOrder)) + { + try + { + if ((image.Success ?? false) && File.Exists(image.Path)) + { + Succeeded(image); + var pictureBinary = File.ReadAllBytes(image.Path); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + var currentProductPictures = _productPictureRepository.TableUntracked.Expand(x => x.Picture) + .Where(x => x.ProductId == row.Entity.Id) + .ToList(); + + var currentPictures = currentProductPictures + .Select(x => x.Picture) + .ToList(); + + if (displayOrder == -1) + { + 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 mapping = new ProductPicture + { + ProductId = row.Entity.Id, + PictureId = newPicture.Id, + DisplayOrder = ++displayOrder + }; + + _productPictureRepository.Insert(mapping); + lastInserted = mapping; + } + } + else + { + context.Result.AddInfo("Found equal picture in data store. 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()); + } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.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"); + if (!manufacturerIds.IsNullOrEmpty()) + { + try + { + foreach (var id in manufacturerIds) + { + if (_productManufacturerRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.ManufacturerId == id).FirstOrDefault() == null) + { + // ensure that manufacturer exists + var manufacturer = _manufacturerService.GetManufacturerById(id); + if (manufacturer != null) + { + var productManufacturer = new ProductManufacturer + { + ProductId = row.Entity.Id, + ManufacturerId = manufacturer.Id, + IsFeaturedProduct = false, + DisplayOrder = 1 + }; + _productManufacturerRepository.Insert(productManufacturer); + lastInserted = productManufacturer; + } + } + } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.Message, row.GetRowInfo(), "ManufacturerIds"); + } + } + } + + // 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; + } + + protected virtual int ProcessProductCategories(ImportExecuteContext context, IEnumerable> batch) + { + _productCategoryRepository.AutoCommitEnabled = false; + + ProductCategory lastInserted = null; + + foreach (var row in batch) + { + var categoryIds = row.GetDataValue>("CategoryIds"); + if (!categoryIds.IsNullOrEmpty()) + { + try + { + foreach (var id in categoryIds) + { + if (_productCategoryRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.CategoryId == id).FirstOrDefault() == null) + { + // ensure that category exists + var category = _categoryService.GetCategoryById(id); + if (category != null) + { + var productCategory = new ProductCategory + { + ProductId = row.Entity.Id, + CategoryId = category.Id, + IsFeaturedProduct = false, + DisplayOrder = 1 + }; + _productCategoryRepository.Insert(productCategory); + lastInserted = productCategory; + } + } + } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.Message, row.GetRowInfo(), "CategoryIds"); + } + } + } + + // 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) + { + int result; + if (CommonHelper.TryConvert(value, culture, out result) && result > 0) + { + return result; + } + + return (int?)null; + } + + public static string[] SupportedKeyFields + { + get + { + return new string[] { "Id", "Sku", "Gtin", "ManufacturerPartNumber", "Name" }; + } + } + + public static string[] DefaultKeyFields + { + get + { + return new string[] { "Sku", "Gtin", "ManufacturerPartNumber" }; + } + } + + public class ImportProductMapping + { + public int DestinationId { get; set; } + public bool Inserted { get; set; } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/ManufacturerExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ManufacturerExtensions.cs deleted file mode 100644 index 9d29a61c29..0000000000 --- a/src/Libraries/SmartStore.Services/Catalog/ManufacturerExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using SmartStore.Core.Domain.Catalog; - -namespace SmartStore.Services.Catalog -{ - /// - /// Extensions - /// - public static class ManufacturerExtensions - { - /// - /// Returns a ProductManufacturer that has the specified values - /// - /// Source - /// Product identifier - /// Manufacturer identifier - /// A ProductManufacturer that has the specified values; otherwise null - public static ProductManufacturer FindProductManufacturer(this IList source, - int productId, int manufacturerId) - { - foreach (var productManufacturer in source) - if (productManufacturer.ProductId == productId && productManufacturer.ManufacturerId == manufacturerId) - return productManufacturer; - - return null; - } - - } -} diff --git a/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs b/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs index ed8e8b6407..9128610551 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 SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; @@ -89,6 +90,32 @@ public virtual void DeleteManufacturer(Manufacturer manufacturer) UpdateManufacturer(manufacturer); } + public virtual IQueryable GetManufacturers(bool showHidden = false, int storeId = 0) + { + var query = _manufacturerRepository.Table + .Where(m => !m.Deleted); + + if (!showHidden) + query = query.Where(m => m.Published); + + if (!QuerySettings.IgnoreMultiStore && storeId > 0) + { + query = from m in query + join sm in _storeMappingRepository.Table + on new { c1 = m.Id, c2 = "Manufacturer" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into m_sm + from sm in m_sm.DefaultIfEmpty() + where !m.LimitedToStores || storeId == sm.StoreId + select m; + + query = from m in query + group m by m.Id into mGroup + orderby mGroup.Key + select mGroup.FirstOrDefault(); + } + + return query; + } + /// /// Gets all manufacturers /// @@ -96,65 +123,43 @@ public virtual void DeleteManufacturer(Manufacturer manufacturer) /// Manufacturer collection public virtual IList GetAllManufacturers(bool showHidden = false) { - return GetAllManufacturers(null, showHidden); + 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, bool showHidden = false) + public virtual IList GetAllManufacturers(string manufacturerName, int storeId = 0, bool showHidden = false) { - var query = _manufacturerRepository.Table; - if (!showHidden) - query = query.Where(m => m.Published); - if (!String.IsNullOrWhiteSpace(manufacturerName)) - query = query.Where(m => m.Name.Contains(manufacturerName)); - query = query.Where(m => !m.Deleted); - query = query.OrderBy(m => m.DisplayOrder); - - //Store mapping - if (!showHidden) - { - //Store mapping - if (!QuerySettings.IgnoreMultiStore) - { - var currentStoreId = _storeContext.CurrentStore.Id; - query = from m in query - join sm in _storeMappingRepository.Table - on new { c1 = m.Id, c2 = "Manufacturer" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into m_sm - from sm in m_sm.DefaultIfEmpty() - where !m.LimitedToStores || currentStoreId == sm.StoreId - select m; - } + var query = GetManufacturers(showHidden, storeId); - //only distinct manufacturers (group by ID) - query = from m in query - group m by m.Id into mGroup - orderby mGroup.Key - select mGroup.FirstOrDefault(); + if (manufacturerName.HasValue()) + query = query.Where(m => m.Name.Contains(manufacturerName)); - query = query.OrderBy(m => m.DisplayOrder); - } + query = query.OrderBy(m => m.DisplayOrder) + .ThenBy(m => m.Name); var manufacturers = query.ToList(); return manufacturers; } - - /// - /// Gets all manufacturers - /// - /// Manufacturer name - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Manufacturers - public virtual IPagedList GetAllManufacturers(string manufacturerName, - int pageIndex, int pageSize, bool showHidden = false) + + /// + /// 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) { - var manufacturers = GetAllManufacturers(manufacturerName, showHidden); + var manufacturers = GetAllManufacturers(manufacturerName, storeId, showHidden); return new PagedList(manufacturers, pageIndex, pageSize); } @@ -239,8 +244,7 @@ public virtual void DeleteProductManufacturer(ProductManufacturer productManufac /// 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) + public virtual IPagedList GetProductManufacturersByManufacturerId(int manufacturerId, int pageIndex, int pageSize, bool showHidden = false) { if (manufacturerId == 0) return new PagedList(new List(), pageIndex, pageSize); @@ -299,8 +303,7 @@ public virtual IList GetProductManufacturersByProductId(int string key = string.Format(PRODUCTMANUFACTURERS_ALLBYPRODUCTID_KEY, showHidden, productId, _workContext.CurrentCustomer.Id, _storeContext.CurrentStore.Id); return _cacheManager.Get(key, () => { - var table = _productManufacturerRepository.Table; - var query = from pm in _productManufacturerRepository.Expand(table, x => x.Manufacturer.Picture) + 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 && @@ -337,6 +340,40 @@ orderby mGroup.Key return productManufacturers; }); } + + public virtual Multimap GetProductManufacturersByManufacturerIds(int[] manufacturerIds) + { + Guard.ArgumentNotNull(() => manufacturerIds); + + var query = _productManufacturerRepository.TableUntracked + .Where(x => manufacturerIds.Contains(x.ManufacturerId)) + .OrderBy(x => x.DisplayOrder); + + var map = query + .ToList() + .ToMultimap(x => x.ManufacturerId, x => x); + + return map; + } + + public virtual Multimap GetProductManufacturersByProductIds(int[] productIds) + { + Guard.ArgumentNotNull(() => productIds); + + 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) + select pm; + + var map = query + .OrderBy(x => x.ProductId) + .ThenBy(x => x.DisplayOrder) + .ToList() + .ToMultimap(x => x.ProductId, x => x); + + return map; + } /// /// Gets a product manufacturer mapping diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs new file mode 100644 index 0000000000..4645f4eac0 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Discounts; + +namespace SmartStore.Services.Catalog +{ + /// + /// Cargo data to reduce database round trips during price calculation + /// + public class PriceCalculationContext + { + protected List _productIds; + private List _productIdsTierPrices; + private List _productIdsAppliedDiscounts; + + private Func> _funcAttributes; + private Func> _funcAttributeCombinations; + private Func> _funcTierPrices; + private Func> _funcProductCategories; + private Func> _funcAppliedDiscounts; + + private LazyMultimap _attributes; + private LazyMultimap _attributeCombinations; + private LazyMultimap _tierPrices; + private LazyMultimap _productCategories; + private LazyMultimap _appliedDiscounts; + + public PriceCalculationContext(IEnumerable products, + Func> attributes, + Func> attributeCombinations, + Func> tierPrices, + Func> productCategories, + Func> appliedDiscounts) + { + if (products == null) + { + _productIds = new List(); + _productIdsTierPrices = new List(); + _productIdsAppliedDiscounts = 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)); + } + + _funcAttributes = attributes; + _funcAttributeCombinations = attributeCombinations; + _funcTierPrices = tierPrices; + _funcProductCategories = productCategories; + _funcAppliedDiscounts = appliedDiscounts; + } + + 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(); + } + + public LazyMultimap Attributes + { + get + { + if (_attributes == null) + { + _attributes = new LazyMultimap(keys => _funcAttributes(keys), _productIds); + } + return _attributes; + } + } + + public LazyMultimap AttributeCombinations + { + get + { + if (_attributeCombinations == null) + { + _attributeCombinations = new LazyMultimap(keys => _funcAttributeCombinations(keys), _productIds); + } + return _attributeCombinations; + } + } + + public LazyMultimap TierPrices + { + get + { + if (_tierPrices == null) + { + _tierPrices = new LazyMultimap(keys => _funcTierPrices(keys), _productIdsTierPrices); + } + return _tierPrices; + } + } + + public LazyMultimap ProductCategories + { + get + { + if (_productCategories == null) + { + _productCategories = new LazyMultimap(keys => _funcProductCategories(keys), _productIds); + } + return _productCategories; + } + } + + public LazyMultimap AppliedDiscounts + { + get + { + if (_appliedDiscounts == null) + { + _appliedDiscounts = new LazyMultimap(keys => _funcAppliedDiscounts(keys), _productIdsAppliedDiscounts); + } + return _appliedDiscounts; + } + } + + public void Collect(IEnumerable productIds) + { + Attributes.Collect(productIds); + AttributeCombinations.Collect(productIds); + TierPrices.Collect(productIds); + ProductCategories.Collect(productIds); + AppliedDiscounts.Collect(productIds); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 7a6ced8be1..ca45f495d7 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -1,12 +1,16 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; -using SmartStore.Core; +using System.Web; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Orders; using SmartStore.Services.Discounts; +using SmartStore.Services.Media; +using SmartStore.Services.Tax; namespace SmartStore.Services.Catalog { @@ -15,32 +19,42 @@ namespace SmartStore.Services.Catalog /// public partial class PriceCalculationService : IPriceCalculationService { - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; private readonly IDiscountService _discountService; private readonly ICategoryService _categoryService; 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; - public PriceCalculationService(IWorkContext workContext, - IStoreContext storeContext, + public PriceCalculationService( IDiscountService discountService, ICategoryService categoryService, IProductAttributeParser productAttributeParser, IProductService productService, ShoppingCartSettings shoppingCartSettings, - CatalogSettings catalogSettings) + CatalogSettings catalogSettings, + IProductAttributeService productAttributeService, + IDownloadService downloadService, + ICommonServices services, + HttpRequestBase httpRequestBase, + ITaxService taxService) { - this._workContext = workContext; - this._storeContext = storeContext; 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; } #region Utilities @@ -51,30 +65,44 @@ public PriceCalculationService(IWorkContext workContext, /// Product /// Customer /// Discounts - protected virtual IList GetAllowedDiscounts(Product product, - Customer customer) + protected virtual IList GetAllowedDiscounts(Product product, Customer customer, PriceCalculationContext context = null) { - var allowedDiscounts = new List(); + var result = new List(); if (_catalogSettings.IgnoreDiscounts) - return allowedDiscounts; + return result; if (product.HasDiscountsApplied) { //we use this property ("HasDiscountsApplied") for performance optimziation to avoid unnecessary database calls - foreach (var discount in product.AppliedDiscounts) - { - if (_discountService.IsDiscountValid(discount, customer) && - discount.DiscountType == DiscountType.AssignedToSkus && - !allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); - } + IEnumerable appliedDiscounts = null; + + if (context == null) + appliedDiscounts = product.AppliedDiscounts; + else + appliedDiscounts = context.AppliedDiscounts.Load(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)) + { + result.Add(discount); + } + } + } } - //performance optimization - //load all category discounts just to ensure that we have at least one + //performance optimization. load all category discounts just to ensure that we have at least one if (_discountService.GetAllDiscounts(DiscountType.AssignedToCategories).Any()) { - var productCategories = _categoryService.GetProductCategoriesByProductId(product.Id); + IEnumerable productCategories = null; + + if (context == null) + productCategories = _categoryService.GetProductCategoriesByProductId(product.Id); + else + productCategories = context.ProductCategories.Load(product.Id); + if (productCategories != null) { foreach (var productCategory in productCategories) @@ -85,18 +113,20 @@ protected virtual IList GetAllowedDiscounts(Product product, { //we use this property ("HasDiscountsApplied") for performance optimziation to avoid unnecessary database calls var categoryDiscounts = category.AppliedDiscounts; + foreach (var discount in categoryDiscounts) { - if (_discountService.IsDiscountValid(discount, customer) && - discount.DiscountType == DiscountType.AssignedToCategories && - !allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); + if (discount.DiscountType == DiscountType.AssignedToCategories && !result.Any(x => x.Id == discount.Id) && _discountService.IsDiscountValid(discount, customer)) + { + result.Add(discount); + } } } } } } - return allowedDiscounts; + + return result; } /// @@ -106,20 +136,34 @@ protected virtual IList GetAllowedDiscounts(Product product, /// Customer /// Quantity /// Price - protected virtual decimal? GetMinimumTierPrice(Product product, Customer customer, int quantity) + protected virtual decimal? GetMinimumTierPrice(Product product, Customer customer, int quantity, PriceCalculationContext context = null) { - if (!product.HasTierPrices) + if (!product.HasTierPrices) return decimal.Zero; - var tierPrices = product.TierPrices - .OrderBy(tp => tp.Quantity) - .FilterByStore(_storeContext.CurrentStore.Id) - .FilterForCustomer(customer) - .ToList() - .RemoveDuplicatedQuantities(); + 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.Load(product.Id) + .RemoveDuplicatedQuantities(); + } + + if (tierPrices == null) + return decimal.Zero; int previousQty = 1; decimal? previousPrice = null; + foreach (var tierPrice in tierPrices) { //check quantity @@ -136,6 +180,120 @@ protected virtual IList GetAllowedDiscounts(Product product, return previousPrice; } + protected virtual decimal GetPreselectedPrice(Product product, 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 selectedAttributes = new NameValueCollection(); + var selectedAttributeValues = new List(); + var attributes = context.Attributes.Load(product.Id); + + // 1. fill selectedAttributes with initially selected attributes + foreach (var attribute in attributes.Where(x => x.ProductVariantAttributeValues.Count > 0 && x.ShouldHaveValues())) + { + int preSelectedValueId = 0; + ProductVariantAttributeValue defaultValue = null; + var selectedValueIds = new List(); + var pvaValues = attribute.ProductVariantAttributeValues; + + foreach (var pvaValue in pvaValues) + { + ProductBundleItemAttributeFilter attributeFilter = null; + + if (bundleItem.FilterOut(pvaValue, out attributeFilter)) + continue; + + if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected) + preSelectedValueId = attributeFilter.AttributeValueId; + + if (!isBundlePricing && pvaValue.IsPreSelected) + { + decimal attributeValuePriceAdjustment = GetProductVariantAttributeValuePriceAdjustment(pvaValue); + decimal priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, out taxRate); + + preSelectedPriceAdjustmentBase = decimal.Add(preSelectedPriceAdjustmentBase, priceAdjustmentBase); + } + } + + // 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); + } + else + { + foreach (var value in pvaValues.Where(x => x.IsPreSelected)) + { + selectedAttributeValues.Add(value); + selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, value.Id, product.Id, bundleItemId); + } + } + } + + // 2. find attribute combination for selected attributes and merge it + if (!isBundle && selectedAttributes.Count > 0) + { + var attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, attributes, _productAttributeParser, _services.Localization, + _downloadService, _catalogSettings, _httpRequestBase, new List(), true, bundleItemId); + + var combinations = context.AttributeCombinations.Load(product.Id); + + var selectedCombination = combinations.FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml)); + + if (selectedCombination != null && selectedCombination.IsActive && selectedCombination.Price.HasValue) + { + product.MergedDataValues = new Dictionary { { "Price", selectedCombination.Price.Value } }; + + if (selectedCombination.BasePriceAmount.HasValue) + product.MergedDataValues.Add("BasePriceAmount", selectedCombination.BasePriceAmount.Value); + + if (selectedCombination.BasePriceBaseAmount.HasValue) + product.MergedDataValues.Add("BasePriceBaseAmount", selectedCombination.BasePriceBaseAmount.Value); + } + } + + if (_catalogSettings.EnableDynamicPriceUpdate && !isBundlePricing) + { + if (selectedAttributeValues.Count > 0) + { + selectedAttributeValues.Each(x => attributesTotalPriceBase += GetProductVariantAttributeValuePriceAdjustment(x)); + } + else + { + attributesTotalPriceBase = preSelectedPriceAdjustmentBase; + } + } + + if (bundleItem != null) + { + bundleItem.AdditionalCharge = attributesTotalPriceBase; + } + + var result = GetFinalPrice(product, bundleItems, _services.WorkContext.CurrentCustomer, attributesTotalPriceBase, true, 1, bundleItem, context); + return result; + } + + public virtual PriceCalculationContext CreatePriceCalculationContext(IEnumerable products = null) + { + 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 => _categoryService.GetProductCategoriesByProductIds(x, true), + x => _productService.GetAppliedDiscountsByProductIds(x) + ); + + return context; + } + #endregion #region Methods @@ -180,7 +338,7 @@ protected virtual IList GetAllowedDiscounts(Product product, public virtual decimal GetFinalPrice(Product product, bool includeDiscounts) { - var customer = _workContext.CurrentCustomer; + var customer = _services.WorkContext.CurrentCustomer; return GetFinalPrice(product, customer, includeDiscounts); } @@ -214,22 +372,14 @@ public virtual decimal GetFinalPrice(Product product, return GetFinalPrice(product, customer, additionalCharge, includeDiscounts, 1); } - /// - /// Gets the final price - /// - /// Product - /// The customer - /// Additional charge - /// A value indicating whether include discounts or not for final price computation - /// Shopping cart item quantity - /// A product bundle item - /// Final price - public virtual decimal GetFinalPrice(Product product, + public virtual decimal GetFinalPrice( + Product product, Customer customer, decimal additionalCharge, bool includeDiscounts, int quantity, - ProductBundleItemData bundleItem = null) + ProductBundleItemData bundleItem = null, + PriceCalculationContext context = null) { //initial price decimal result = product.Price; @@ -242,40 +392,38 @@ public virtual decimal GetFinalPrice(Product product, //tier prices if (product.HasTierPrices && !bundleItem.IsValid()) { - decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity); - if (tierPrice.HasValue) - result = Math.Min(result, tierPrice.Value); + decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity, context); + if (tierPrice.HasValue) + result = Math.Min(result, tierPrice.Value); } //discount + additional charge if (includeDiscounts) { Discount appliedDiscount = null; - decimal discountAmount = GetDiscountAmount(product, customer, additionalCharge, quantity, out appliedDiscount, bundleItem); + decimal discountAmount = GetDiscountAmount(product, customer, additionalCharge, quantity, out appliedDiscount, bundleItem, context); result = result + additionalCharge - discountAmount; } else { result = result + additionalCharge; } + if (result < decimal.Zero) result = decimal.Zero; + return result; } - /// - /// Gets the final price including bundle per-item pricing - /// - /// Product - /// Bundle items - /// The customer - /// Additional charge - /// A value indicating whether include discounts or not for final price computation - /// Shopping cart item quantity - /// A product bundle item - /// Final price - public virtual decimal GetFinalPrice(Product product, IList bundleItems, - Customer customer, decimal additionalCharge, bool includeDiscounts, int quantity, ProductBundleItemData bundleItem = null) + public virtual decimal GetFinalPrice( + Product product, + IEnumerable bundleItems, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + ProductBundleItemData bundleItem = null, + PriceCalculationContext context = null) { if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing) { @@ -284,23 +432,24 @@ public virtual decimal GetFinalPrice(Product product, IList x.IsValid())) { - decimal itemPrice = GetFinalPrice(itemData.Item.Product, customer, itemData.AdditionalCharge, includeDiscounts, 1, itemData); + decimal itemPrice = GetFinalPrice(itemData.Item.Product, customer, itemData.AdditionalCharge, includeDiscounts, 1, itemData, context); result = result + decimal.Multiply(itemPrice, itemData.Item.Quantity); } return (result < decimal.Zero ? decimal.Zero : result); } - return GetFinalPrice(product, customer, additionalCharge, includeDiscounts, quantity, bundleItem); + 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, out bool displayFromMessage) + public virtual decimal GetLowestPrice(Product product, PriceCalculationContext context, out bool displayFromMessage) { if (product == null) throw new ArgumentNullException("product"); @@ -308,15 +457,16 @@ public virtual decimal GetLowestPrice(Product product, out bool displayFromMessa if (product.ProductType == ProductType.GroupedProduct) throw Error.InvalidOperation("Choose the other override for products of type grouped product."); - displayFromMessage = false; + // note: attribute price adjustments were never regarded here cause of many reasons + + if (context == null) + context = CreatePriceCalculationContext(); - IList bundleItems = null; bool isBundlePerItemPricing = (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing); - if (isBundlePerItemPricing) - bundleItems = _productService.GetBundleItems(product.Id); + displayFromMessage = isBundlePerItemPricing; - decimal lowestPrice = GetFinalPrice(product, bundleItems, _workContext.CurrentCustomer, decimal.Zero, true, int.MaxValue); + var lowestPrice = GetFinalPrice(product, null, _services.WorkContext.CurrentCustomer, decimal.Zero, true, int.MaxValue, null, context); if (product.LowestAttributeCombinationPrice.HasValue && product.LowestAttributeCombinationPrice.Value < lowestPrice) { @@ -324,29 +474,29 @@ public virtual decimal GetLowestPrice(Product product, out bool displayFromMessa displayFromMessage = true; } - if (!displayFromMessage) + if (lowestPrice == decimal.Zero && product.Price == decimal.Zero) { - foreach (var attribute in product.ProductVariantAttributes) - { - if (attribute.ProductVariantAttributeValues.Any(x => x.PriceAdjustment != decimal.Zero)) - { - displayFromMessage = true; - break; - } - } + lowestPrice = product.LowestAttributeCombinationPrice ?? decimal.Zero; + } + + if (!displayFromMessage && product.ProductType != ProductType.BundledProduct) + { + var attributes = context.Attributes.Load(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) + .RemoveDuplicatedQuantities(); + + displayFromMessage = (tierPrices.Count > 0 && !(tierPrices.Count == 1 && tierPrices.First().Quantity <= 1)); } return lowestPrice; } - /// - /// Get the lowest price of a grouped product. - /// - /// Grouped product. - /// Products associated to product. - /// The associated product with the lowest price. - /// The lowest price. - public virtual decimal? GetLowestPrice(Product product, IEnumerable associatedProducts, out Product lowestPriceProduct) + public virtual decimal? GetLowestPrice(Product product, PriceCalculationContext context, IEnumerable associatedProducts, out Product lowestPriceProduct) { if (product == null) throw new ArgumentNullException("product"); @@ -357,12 +507,16 @@ public virtual decimal GetLowestPrice(Product product, out bool displayFromMessa if (product.ProductType != ProductType.GroupedProduct) throw Error.InvalidOperation("Choose the other override for products not of type grouped product."); - lowestPriceProduct = product; + lowestPriceProduct = null; decimal? lowestPrice = null; + var customer = _services.WorkContext.CurrentCustomer; + + if (context == null) + context = CreatePriceCalculationContext(); foreach (var associatedProduct in associatedProducts) { - var tmpPrice = GetFinalPrice(associatedProduct, _workContext.CurrentCustomer, decimal.Zero, true, int.MaxValue); + var tmpPrice = GetFinalPrice(associatedProduct, customer, decimal.Zero, true, int.MaxValue, null, context); if (associatedProduct.LowestAttributeCombinationPrice.HasValue && associatedProduct.LowestAttributeCombinationPrice.Value < tmpPrice) { @@ -375,9 +529,55 @@ public virtual decimal GetLowestPrice(Product product, out bool displayFromMessa lowestPriceProduct = associatedProduct; } } + + 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) + { + if (product == null) + throw new ArgumentNullException("product"); + + var result = decimal.Zero; + + if (context == null) + context = CreatePriceCalculationContext(); + + if (product.ProductType == ProductType.BundledProduct) + { + var bundleItems = _productService.GetBundleItems(product.Id); + + 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); + } + + result = GetPreselectedPrice(product, context, null, bundleItems); + } + else + { + result = GetPreselectedPrice(product, context, null, null); + } + + return result; + } + + /// /// Gets the product cost /// @@ -394,6 +594,7 @@ public virtual decimal GetProductCost(Product product, string attributesXml) _productAttributeParser .ParseProductVariantAttributeValues(attributesXml) .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage) + .ToList() .Each(x => { var linkedProduct = _productService.GetProductById(x.LinkedProductId); @@ -412,7 +613,7 @@ public virtual decimal GetProductCost(Product product, string attributesXml) /// Discount amount public virtual decimal GetDiscountAmount(Product product) { - var customer = _workContext.CurrentCustomer; + var customer = _services.WorkContext.CurrentCustomer; return GetDiscountAmount(product, customer, decimal.Zero); } @@ -459,23 +660,14 @@ public virtual decimal GetDiscountAmount(Product product, return GetDiscountAmount(product, customer, additionalCharge, 1, out appliedDiscount); } - /// - /// Gets discount amount - /// - /// Product - /// The customer - /// Additional charge - /// Product quantity - /// Applied discount - /// A product bundle item - /// Discount amount public virtual decimal GetDiscountAmount( Product product, Customer customer, decimal additionalCharge, int quantity, out Discount appliedDiscount, - ProductBundleItemData bundleItem = null) + ProductBundleItemData bundleItem = null, + PriceCalculationContext context = null) { appliedDiscount = null; decimal appliedDiscountAmount = decimal.Zero; @@ -485,14 +677,14 @@ public virtual decimal GetDiscountAmount( { if (bundleItem.Item.Discount.HasValue && bundleItem.Item.BundleProduct.BundlePerItemPricing) { - appliedDiscount = new Discount() + appliedDiscount = new Discount { UsePercentage = bundleItem.Item.DiscountPercentage, DiscountPercentage = bundleItem.Item.Discount.Value, DiscountAmount = bundleItem.Item.Discount.Value }; - finalPriceWithoutDiscount = GetFinalPrice(product, customer, additionalCharge, false, quantity, bundleItem); + finalPriceWithoutDiscount = GetFinalPrice(product, customer, additionalCharge, false, quantity, bundleItem, context); appliedDiscountAmount = appliedDiscount.GetDiscountAmount(finalPriceWithoutDiscount); } } @@ -504,13 +696,13 @@ public virtual decimal GetDiscountAmount( return appliedDiscountAmount; } - var allowedDiscounts = GetAllowedDiscounts(product, customer); + var allowedDiscounts = GetAllowedDiscounts(product, customer, context); if (allowedDiscounts.Count == 0) { return appliedDiscountAmount; } - finalPriceWithoutDiscount = GetFinalPrice(product, customer, additionalCharge, false, quantity, bundleItem); + finalPriceWithoutDiscount = GetFinalPrice(product, customer, additionalCharge, false, quantity, bundleItem, context); appliedDiscount = allowedDiscounts.GetPreferredDiscount(finalPriceWithoutDiscount); if (appliedDiscount != null) @@ -558,7 +750,7 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, { foreach (var bundleItem in shoppingCartItem.ChildItems) { - bundleItem.Item.Product.MergeWithCombination(bundleItem.Item.AttributesXml); + bundleItem.Item.Product.MergeWithCombination(bundleItem.Item.AttributesXml, _productAttributeParser); } var bundleItems = shoppingCartItem.ChildItems.Where(x => x.BundleItemData.IsValid()).Select(x => x.BundleItemData).ToList(); @@ -568,13 +760,20 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, } else { - decimal attributesTotalPrice = decimal.Zero; - var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); + product.MergeWithCombination(shoppingCartItem.Item.AttributesXml, _productAttributeParser); + + var attributesTotalPrice = decimal.Zero; - if (pvaValues != null) + var pvaValuesEnum = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); + + if (pvaValuesEnum != null) { + var pvaValues = pvaValuesEnum.ToList(); + foreach (var pvaValue in pvaValues) + { attributesTotalPrice += GetProductVariantAttributeValuePriceAdjustment(pvaValue); + } } finalPrice = GetFinalPrice(product, customer, attributesTotalPrice, includeDiscounts, shoppingCartItem.Item.Quantity, shoppingCartItem.BundleItemData); @@ -616,7 +815,7 @@ public virtual decimal GetDiscountAmount(OrganizedShoppingCartItem shoppingCartI { decimal attributesTotalPrice = decimal.Zero; - var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); + var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml).ToList(); foreach (var pvaValue in pvaValues) { attributesTotalPrice += GetProductVariantAttributeValuePriceAdjustment(pvaValue); diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index 1546a5e9d7..cf4531f322 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -1,44 +1,60 @@ using System; -using System.Linq; 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 SmartStore.Core.Domain.Catalog; -using SmartStore.Collections; +using System.Xml.Linq; using Newtonsoft.Json; -using System.Web; +using SmartStore.Collections; +using SmartStore.Core.Caching; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Catalog; namespace SmartStore.Services.Catalog { - /// - /// Product attribute parser - /// - public partial class ProductAttributeParser : IProductAttributeParser + /// + /// Product attribute parser + /// + public partial class ProductAttributeParser : IProductAttributeParser { - private readonly IProductAttributeService _productAttributeService; + // 0 = ProductId, 1 = AttributeXml Hash + private const string ATTRIBUTECOMBINATION_BY_ID_HASH = "SmartStore.parsedattributecombination.id-{0}-{1}"; + + private readonly IProductAttributeService _productAttributeService; + private readonly IRepository _pvacRepository; + private readonly ICacheManager _cacheManager; - public ProductAttributeParser(IProductAttributeService productAttributeService) + public ProductAttributeParser( + IProductAttributeService productAttributeService, + IRepository pvacRepository, + ICacheManager cacheManager) { - this._productAttributeService = productAttributeService; + _productAttributeService = productAttributeService; + _pvacRepository = pvacRepository; + _cacheManager = cacheManager; } - #region Product attributes + #region Product attributes - /// + /// /// Gets selected product variant attribute identifiers /// - /// Attributes + /// Attributes /// Selected product variant attribute identifiers - private IEnumerable ParseProductVariantAttributeIds(string attributes) + private IEnumerable ParseProductVariantAttributeIds(string attributesXml) { var ids = new List(); - if (String.IsNullOrEmpty(attributes)) + if (String.IsNullOrEmpty(attributesXml)) yield break; try { var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributes); + xmlDoc.LoadXml(attributesXml); var nodeList = xmlDoc.SelectNodes(@"//Attributes/ProductVariantAttribute"); foreach (var node in nodeList.Cast()) @@ -55,126 +71,177 @@ private IEnumerable ParseProductVariantAttributeIds(string attributes) } } finally { } - } - public virtual Multimap DeserializeProductVariantAttributes(string attributes) - { - var attrs = new Multimap(); - if (String.IsNullOrEmpty(attributes)) - return attrs; - - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributes); - - 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) + //{ + // 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) + { + var attrs = new Multimap(); + if (String.IsNullOrEmpty(attributesXml)) + return attrs; - /// - /// Gets selected product variant attributes - /// - /// Attributes - /// Selected product variant attributes - public virtual IList ParseProductVariantAttributes(string attributes) - { - var pvaCollection = new List(); + try + { + var doc = XDocument.Parse(attributesXml); - // codehint: sm-edit - var ids = ParseProductVariantAttributeIds(attributes); - return this.ParseProductVariantAttributes(ids.ToList()).ToList(); - } + // Attributes/ProductVariantAttribute + foreach (var node1 in doc.Descendants("ProductVariantAttribute")) + { + string sid = node1.Attribute("ID").Value; + if (sid.HasValue()) + { + int id = 0; + if (int.TryParse(sid, out id)) + { + // ProductVariantAttributeValue/Value + foreach (var node2 in node1.Descendants("Value")) + { + attrs.Add(id, node2.Value); + } + } + } + } + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } - public virtual IEnumerable ParseProductVariantAttributes(ICollection ids) - { + return attrs; + } - if (ids != null) - { - if (ids.Count == 1) - { - var pva = _productAttributeService.GetProductVariantAttributeById(ids.ElementAt(0)); - if (pva != null) - { - return new ProductVariantAttribute[] { pva }; - } - } - else - { - return _productAttributeService.GetProductVariantAttributesByIds(ids.ToArray()).ToList(); - } - } + /// + /// Gets selected product variant attributes + /// + /// Attributes + /// Selected product variant attributes + public virtual IList ParseProductVariantAttributes(string attributesXml) + { + var ids = ParseProductVariantAttributeIds(attributesXml); - return Enumerable.Empty(); - } + return _productAttributeService.GetProductVariantAttributesByIds(ids.ToList()); + } - /// - /// Get product variant attribute values - /// - /// Attributes - /// Product variant attribute values - public virtual IEnumerable ParseProductVariantAttributeValues(string attributes) + public virtual IEnumerable ParseProductVariantAttributeValues(string attributeXml) { - var pvaValues = Enumerable.Empty(); + //var pvaValues = Enumerable.Empty(); - var attrs = DeserializeProductVariantAttributes(attributes); - var pvaCollection = ParseProductVariantAttributes(attrs.Keys); + 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]; //ParseValues(attributes, pva.Id); - var ids = from id in pvaValuesStr - where id.HasValue() - select id.ToInt(); - var values = _productAttributeService.GetProductVariantAttributeValuesByIds(ids.ToArray()); + var pvaValuesStr = attrs[pva.Id]; + + var ids = + from id in pvaValuesStr + where id.HasValue() + select id.ToInt(); - pvaValues = pvaValues.Concat(values); + allIds.AddRange(ids); + + //var values = _productAttributeService.GetProductVariantAttributeValuesByIds(ids.ToArray()); + //pvaValues = pvaValues.Concat(values); } - return pvaValues; + int[] allDistinctIds = allIds.Distinct().ToArray(); + + var values = _productAttributeService.GetProductVariantAttributeValuesByIds(allDistinctIds); + + return values; } - /// - /// Gets selected product variant attribute value - /// - /// Attributes - /// Product variant attribute identifier - /// Product variant attribute value - public virtual IList ParseValues(string attributes, int productVariantAttributeId) + public virtual IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes) + { + var result = new List(); + + if (attributeCombination == null || !attributeCombination.Any()) + return result; + + var allValueIds = new List(); + + foreach (var pva in attributes.Where(x => x.ShouldHaveValues()).OrderBy(x => x.DisplayOrder)) + { + if (attributeCombination.ContainsKey(pva.Id)) + { + var pvaValuesStr = attributeCombination[pva.Id]; + var ids = pvaValuesStr.Where(x => x.HasValue()).Select(x => x.ToInt()); + + allValueIds.AddRange(ids); + } + } + + foreach (int id in allValueIds.Distinct()) + { + foreach (var attribute in attributes) + { + var attributeValue = attribute.ProductVariantAttributeValues.FirstOrDefault(x => x.Id == id); + if (attributeValue != null && !result.Any(x => x.Id == attributeValue.Id)) + { + result.Add(attributeValue); + break; + } + } + } + + 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) { var selectedProductVariantAttributeValues = new List(); try { var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributes); + xmlDoc.LoadXml(attributesXml); var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductVariantAttribute"); foreach (XmlNode node1 in nodeList1) @@ -209,85 +276,80 @@ public virtual IList ParseValues(string attributes, int productVariantAt /// /// Adds an attribute /// - /// Attributes + /// Attributes /// Product variant attribute /// Value /// Attributes - public virtual string AddProductAttribute(string attributes, ProductVariantAttribute pva, string value) + public virtual string AddProductAttribute(string attributesXml, ProductVariantAttribute pva, string value) { - return pva.AddProductAttribute(attributes, value); + return pva.AddProductAttribute(attributesXml, value); } - /// - /// Are attributes equal - /// - /// The attributes of the first product - /// The attributes of the second product - /// Result - public virtual bool AreProductAttributesEqual(string attributes1, string attributes2) + public virtual bool AreProductAttributesEqual(string attributeXml1, string attributeXml2) { - var attrs1 = DeserializeProductVariantAttributes(attributes1); - var attrs2 = DeserializeProductVariantAttributes(attributes2); + if (attributeXml1.IsCaseInsensitiveEqual(attributeXml2)) + return true; - if (attrs1.Count == attrs2.Count) - { - var pva1Collection = ParseProductVariantAttributes(attrs2.Keys); - var pva2Collection = ParseProductVariantAttributes(attrs1.Keys); - foreach (var pva1 in pva1Collection) - { - foreach (var pva2 in pva2Collection) - { - if (pva1.Id == pva2.Id) - { - var pvaValues1Str = attrs2[pva1.Id]; // ParseValues(attributes2, pva1.Id); - var pvaValues2Str = attrs1[pva2.Id]; // ParseValues(attributes1, pva2.Id); - if (pvaValues1Str.Count == pvaValues2Str.Count) - { - foreach (string str1 in pvaValues1Str) - { - bool hasAttribute = pvaValues2Str.Any(x => x.IsCaseInsensitiveEqual(str1)); - if (!hasAttribute) - { - return false; - } - } - } - else - { - return false; - } - } - } - } - } - else - { - return false; - } + var attributes1 = DeserializeProductVariantAttributes(attributeXml1); + var attributes2 = DeserializeProductVariantAttributes(attributeXml2); - return true; - } + if (attributes1.Count != attributes2.Count) + return false; - /// - /// Finds a product variant attribute combination by attributes stored in XML - /// - /// Product - /// Attributes in XML format - /// Found product variant attribute combination - public virtual ProductVariantAttributeCombination FindProductVariantAttributeCombination(Product product, string attributesXml) - { - if (product == null) - throw new ArgumentNullException("product"); + foreach (var kvp in attributes1) + { + if (!attributes2.ContainsKey(kvp.Key)) + { + // the second list does not contain this id: not equal! + return false; + } + + // compare the values + var values1 = kvp.Value; + var values2 = attributes2[kvp.Key]; + + if (values1.Count != values2.Count) + { + // number of values differ: not equal! + return false; + } + + foreach (var value1 in values1) + { + var str1 = value1.TrimSafe(); + + if (!values2.Any(x => x.TrimSafe().IsCaseInsensitiveEqual(str1))) + { + // the second values list for this attribute does not contain this value: not equal! + return false; + } + } + } - return FindProductVariantAttributeCombination(product.Id, attributesXml); + return true; } - public virtual ProductVariantAttributeCombination FindProductVariantAttributeCombination(int productId, string attributesXml) + public virtual ProductVariantAttributeCombination FindProductVariantAttributeCombination( + int productId, + string attributesXml) { - if (attributesXml.HasValue()) + if (attributesXml.IsEmpty()) + return null; + + var attributesHash = attributesXml.Hash(Encoding.UTF8); + var cacheKey = ATTRIBUTECOMBINATION_BY_ID_HASH.FormatInvariant(productId, attributesHash); + + var result = _cacheManager.Get(cacheKey, () => { - //existing combinations - var combinations = _productAttributeService.GetAllProductVariantAttributeCombinations(productId); + var query = from x in _pvacRepository.TableUntracked + where x.ProductId == productId + select new + { + x.Id, + x.AttributesXml + }; + + var combinations = query.ToList(); if (combinations.Count == 0) return null; @@ -295,63 +357,110 @@ public virtual ProductVariantAttributeCombination FindProductVariantAttributeCom { bool attributesEqual = AreProductAttributesEqual(combination.AttributesXml, attributesXml); if (attributesEqual) - return combination; + return _productAttributeService.GetProductVariantAttributeCombinationById(combination.Id); } - } - return null; + + return null; + }); + + return result; } - /// - /// Deserializes attribute data from an URL query string - /// - /// Json data query string - /// List items with following structure: Product.Id, ProductAttribute.Id, Product_ProductAttribute_Mapping.Id, ProductVariantAttributeValue.Id public virtual List> DeserializeQueryData(string jsonData) { - if (jsonData.HasValue()) + try { - if (jsonData.StartsWith("[")) - return JsonConvert.DeserializeObject>>(jsonData); + if (jsonData.HasValue()) + { + if (jsonData.StartsWith("[")) + { + return JsonConvert.DeserializeObject>>(jsonData); + } - return new List>() { JsonConvert.DeserializeObject>(jsonData) }; + return new List> { JsonConvert.DeserializeObject>(jsonData) }; + } } + catch { } + return new List>(); } - - /// - /// Serializes attribute data - /// - /// Product identifier - /// Attribute XML string - /// Whether to URL encode - /// Json string with attribute data - public virtual string SerializeQueryData(int productId, string attributesXml, bool urlEncode = true) + + public virtual void DeserializeQueryData(List> queryData, string attributesXml, int productId, int bundleItemId = 0) { + Guard.ArgumentNotNull(() => queryData); + if (attributesXml.HasValue() && productId != 0) { - var data = new List>(); var attributeValues = ParseProductVariantAttributeValues(attributesXml).ToList(); foreach (var value in attributeValues) { - data.Add(new List + var lst = new List { productId, value.ProductVariantAttribute.ProductAttributeId, value.ProductVariantAttributeId, value.Id - }); - } + }; - if (data.Count > 0) - { - string result = JsonConvert.SerializeObject(data); - return (urlEncode ? HttpUtility.UrlEncode(result) : result); + if (bundleItemId != 0) + lst.Add(bundleItemId); + + queryData.Add(lst); } } + } + + public virtual string SerializeQueryData(string attributesXml, int productId, bool urlEncode = true) + { + var data = new List>(); + + DeserializeQueryData(data, attributesXml, productId); + + 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 @@ -359,14 +468,14 @@ public virtual string SerializeQueryData(int productId, string attributesXml, bo /// /// Add gift card attrbibutes /// - /// Attributes + /// Attributes /// Recipient name /// Recipient email /// Sender name /// Sender email /// Message /// Attributes - public string AddGiftCardAttribute(string attributes, string recipientName, + public string AddGiftCardAttribute(string attributesXml, string recipientName, string recipientEmail, string senderName, string senderEmail, string giftCardMessage) { string result = string.Empty; @@ -378,14 +487,14 @@ public string AddGiftCardAttribute(string attributes, string recipientName, senderEmail = senderEmail.Trim(); var xmlDoc = new XmlDocument(); - if (String.IsNullOrEmpty(attributes)) + if (String.IsNullOrEmpty(attributesXml)) { var element1 = xmlDoc.CreateElement("Attributes"); xmlDoc.AppendChild(element1); } else { - xmlDoc.LoadXml(attributes); + xmlDoc.LoadXml(attributesXml); } var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); @@ -429,13 +538,13 @@ public string AddGiftCardAttribute(string attributes, string recipientName, /// /// Get gift card attrbibutes /// - /// Attributes + /// Attributes /// Recipient name /// Recipient email /// Sender name /// Sender email /// Message - public void GetGiftCardAttribute(string attributes, out string recipientName, + public void GetGiftCardAttribute(string attributesXml, out string recipientName, out string recipientEmail, out string senderName, out string senderEmail, out string giftCardMessage) { @@ -448,7 +557,7 @@ public void GetGiftCardAttribute(string attributes, out string recipientName, try { var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributes); + xmlDoc.LoadXml(attributesXml); var recipientNameElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/RecipientName"); var recipientEmailElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/RecipientEmail"); diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs index cda03df832..3a886d0913 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs @@ -1,51 +1,42 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Events; using SmartStore.Services.Media; -using SmartStore.Core.Infrastructure; -using SmartStore.Data; -using System.Text; -using System.Linq.Expressions; -using SmartStore.Core.Domain.Media; +using SmartStore.Core; namespace SmartStore.Services.Catalog { - /// - /// Product attribute service - /// + public partial class ProductAttributeService : IProductAttributeService { - #region Constants private const string PRODUCTATTRIBUTES_ALL_KEY = "SmartStore.productattribute.all"; - private const string PRODUCTVARIANTATTRIBUTES_ALL_KEY = "SmartStore.productvariantattribute.all-{0}"; - private const string PRODUCTVARIANTATTRIBUTEVALUES_ALL_KEY = "SmartStore.productvariantattributevalue.all-{0}"; - private const string PRODUCTATTRIBUTES_PATTERN_KEY = "SmartStore.productattribute."; - private const string PRODUCTVARIANTATTRIBUTES_PATTERN_KEY = "SmartStore.productvariantattribute."; - private const string PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY = "SmartStore.productvariantattributevalue."; - private const string PRODUCTATTRIBUTES_BY_ID_KEY = "SmartStore.productattribute.id-{0}"; - private const string PRODUCTVARIANTATTRIBUTES_BY_ID_KEY = "SmartStore.productvariantattribute.id-{0}"; - private const string PRODUCTVARIANTATTRIBUTEVALUES_BY_ID_KEY = "SmartStore.productvariantattributevalue.id-{0}"; + private const string PRODUCTATTRIBUTES_BY_ID_KEY = "SmartStore.productattribute.id-{0}"; + private const string PRODUCTATTRIBUTES_PATTERN_KEY = "SmartStore.productattribute."; - #endregion + 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."; - #region Fields + 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."; - private readonly IRepository _productAttributeRepository; + private readonly IRepository _productAttributeRepository; private readonly IRepository _productVariantAttributeRepository; - private readonly IRepository _productVariantAttributeCombinationRepository; + private readonly IRepository _pvacRepository; private readonly IRepository _productVariantAttributeValueRepository; private readonly IRepository _productBundleItemAttributeFilterRepository; private readonly IEventPublisher _eventPublisher; private readonly ICacheManager _cacheManager; private readonly IPictureService _pictureService; - #endregion - - #region Ctor /// /// Ctor @@ -53,13 +44,13 @@ public partial class ProductAttributeService : IProductAttributeService /// Cache manager /// Product attribute repository /// Product variant attribute mapping repository - /// Product variant attribute combination repository + /// Product variant attribute combination repository /// Product variant attribute value repository /// Event published public ProductAttributeService(ICacheManager cacheManager, IRepository productAttributeRepository, IRepository productVariantAttributeRepository, - IRepository productVariantAttributeCombinationRepository, + IRepository pvacRepository, IRepository productVariantAttributeValueRepository, IRepository productBundleItemAttributeFilterRepository, IEventPublisher eventPublisher, @@ -68,27 +59,43 @@ public ProductAttributeService(ICacheManager cacheManager, _cacheManager = cacheManager; _productAttributeRepository = productAttributeRepository; _productVariantAttributeRepository = productVariantAttributeRepository; - _productVariantAttributeCombinationRepository = productVariantAttributeCombinationRepository; + _pvacRepository = pvacRepository; _productVariantAttributeValueRepository = productVariantAttributeValueRepository; _productBundleItemAttributeFilterRepository = productBundleItemAttributeFilterRepository; _eventPublisher = eventPublisher; _pictureService = pictureService; } - #endregion + #region Utilities + + private IList GetSwitchedLoadedAttributeMappings(ICollection productVariantAttributeIds) + { + if (productVariantAttributeIds != null && productVariantAttributeIds.Count > 0) + { + if (productVariantAttributeIds.Count == 1) + { + var pva = GetProductVariantAttributeById(productVariantAttributeIds.ElementAt(0)); + if (pva != null) + { + return new List { pva }; + } + } + else + { + return _productVariantAttributeRepository.GetMany(productVariantAttributeIds).ToList(); + } + } - //// Autowired Dependency (is a proptery dependency to avoid circularity) - //public virtual IProductAttributeParser AttributeParser { get; set; } + return new List(); + } - #region Methods + #endregion - #region Product attributes + #region Methods - /// - /// Deletes a product attribute - /// - /// Product attribute - public virtual void DeleteProductAttribute(ProductAttribute productAttribute) + #region Product attributes + + public virtual void DeleteProductAttribute(ProductAttribute productAttribute) { if (productAttribute == null) throw new ArgumentNullException("productAttribute"); @@ -104,10 +111,6 @@ public virtual void DeleteProductAttribute(ProductAttribute productAttribute) _eventPublisher.EntityDeleted(productAttribute); } - /// - /// Gets all product attributes - /// - /// Product attribute collection public virtual IList GetAllProductAttributes() { string key = PRODUCTATTRIBUTES_ALL_KEY; @@ -121,11 +124,6 @@ orderby pa.Name }); } - /// - /// Gets a product attribute - /// - /// Product attribute identifier - /// Product attribute public virtual ProductAttribute GetProductAttributeById(int productAttributeId) { if (productAttributeId == 0) @@ -137,10 +135,6 @@ public virtual ProductAttribute GetProductAttributeById(int productAttributeId) }); } - /// - /// Inserts a product attribute - /// - /// Product attribute public virtual void InsertProductAttribute(ProductAttribute productAttribute) { if (productAttribute == null) @@ -156,10 +150,6 @@ public virtual void InsertProductAttribute(ProductAttribute productAttribute) _eventPublisher.EntityInserted(productAttribute); } - /// - /// Updates the product attribute - /// - /// Product attribute public virtual void UpdateProductAttribute(ProductAttribute productAttribute) { if (productAttribute == null) @@ -179,10 +169,6 @@ public virtual void UpdateProductAttribute(ProductAttribute productAttribute) #region Product variant attributes mappings (ProductVariantAttribute) - /// - /// Deletes a product variant attribute mapping - /// - /// Product variant attribute mapping public virtual void DeleteProductVariantAttribute(ProductVariantAttribute productVariantAttribute) { if (productVariantAttribute == null) @@ -198,11 +184,6 @@ public virtual void DeleteProductVariantAttribute(ProductVariantAttribute produc _eventPublisher.EntityDeleted(productVariantAttribute); } - /// - /// Gets product variant attribute mappings by product identifier - /// - /// The product identifier - /// Product variant attribute mapping collection public virtual IList GetProductVariantAttributesByProductId(int productId) { string key = string.Format(PRODUCTVARIANTATTRIBUTES_ALL_KEY, productId); @@ -218,34 +199,73 @@ orderby pva.DisplayOrder }); } - /// - /// Gets a product variant attribute mapping - /// - /// Product variant attribute mapping identifier - /// Product variant attribute mapping + public virtual Multimap GetProductVariantAttributesByProductIds(int[] productIds, AttributeControlType? controlType) + { + Guard.ArgumentNotNull(() => productIds); + + var query = + from pva in _productVariantAttributeRepository.TableUntracked.Expand(x => x.ProductAttribute).Expand(x => x.ProductVariantAttributeValues) + where productIds.Contains(pva.ProductId) + select pva; + + if (controlType.HasValue) + { + query = query.Where(x => x.AttributeControlTypeId == ((int)controlType.Value)); + } + + var map = query + .OrderBy(x => x.ProductId) + .ThenBy(x => x.DisplayOrder) + .ToList() + .ToMultimap(x => x.ProductId, x => x); + + return map; + } + public virtual ProductVariantAttribute GetProductVariantAttributeById(int productVariantAttributeId) { if (productVariantAttributeId == 0) return null; string key = string.Format(PRODUCTVARIANTATTRIBUTES_BY_ID_KEY, productVariantAttributeId); - return _cacheManager.Get(key, () => { + + return _cacheManager.Get(key, () => + { return _productVariantAttributeRepository.GetById(productVariantAttributeId); }); } - // codehint: sm-add - public virtual IEnumerable GetProductVariantAttributesByIds(params int[] ids) - { - if (ids == null || ids.Length == 0) - { - return Enumerable.Empty(); - } + public virtual IList GetProductVariantAttributesByIds(IEnumerable productVariantAttributeIds, IEnumerable attributes = null) + { + if (productVariantAttributeIds != null) + { + if (attributes != null) + { + var ids = new List(); + var result = new List(); - return _productVariantAttributeRepository.GetMany(ids); - } + foreach (var id in productVariantAttributeIds) + { + var pva = attributes.FirstOrDefault(x => x.Id == id); + if (pva == null) + ids.Add(id); + else + result.Add(pva); + } + + var newLoadedMappings = GetSwitchedLoadedAttributeMappings(ids); + + result.AddRange(newLoadedMappings); + + return result; + } + + return GetSwitchedLoadedAttributeMappings(productVariantAttributeIds.ToList()); + } + + return new List(); + } - // codehint: sm-add public virtual IEnumerable GetProductVariantAttributeValuesByIds(params int[] productVariantAttributeValueIds) { if (productVariantAttributeValueIds == null || productVariantAttributeValueIds.Length == 0) @@ -256,10 +276,6 @@ public virtual IEnumerable GetProductVariantAttrib return _productVariantAttributeValueRepository.GetMany(productVariantAttributeValueIds); } - /// - /// Inserts a product variant attribute mapping - /// - /// The product variant attribute mapping public virtual void InsertProductVariantAttribute(ProductVariantAttribute productVariantAttribute) { if (productVariantAttribute == null) @@ -275,10 +291,6 @@ public virtual void InsertProductVariantAttribute(ProductVariantAttribute produc _eventPublisher.EntityInserted(productVariantAttribute); } - /// - /// Updates the product variant attribute mapping - /// - /// The product variant attribute mapping public virtual void UpdateProductVariantAttribute(ProductVariantAttribute productVariantAttribute) { if (productVariantAttribute == null) @@ -298,10 +310,6 @@ public virtual void UpdateProductVariantAttribute(ProductVariantAttribute produc #region Product variant attribute values (ProductVariantAttributeValue) - /// - /// Deletes a product variant attribute value - /// - /// Product variant attribute value public virtual void DeleteProductVariantAttributeValue(ProductVariantAttributeValue productVariantAttributeValue) { if (productVariantAttributeValue == null) @@ -317,11 +325,6 @@ public virtual void DeleteProductVariantAttributeValue(ProductVariantAttributeVa _eventPublisher.EntityDeleted(productVariantAttributeValue); } - /// - /// Gets product variant attribute values by product identifier - /// - /// The product variant attribute mapping identifier - /// Product variant attribute mapping collection public virtual IList GetProductVariantAttributeValues(int productVariantAttributeId) { string key = string.Format(PRODUCTVARIANTATTRIBUTEVALUES_ALL_KEY, productVariantAttributeId); @@ -336,11 +339,6 @@ orderby pvav.DisplayOrder }); } - /// - /// Gets a product variant attribute value - /// - /// Product variant attribute value identifier - /// Product variant attribute value public virtual ProductVariantAttributeValue GetProductVariantAttributeValueById(int productVariantAttributeValueId) { if (productVariantAttributeValueId == 0) @@ -353,10 +351,6 @@ public virtual ProductVariantAttributeValue GetProductVariantAttributeValueById( }); } - /// - /// Inserts a product variant attribute value - /// - /// The product variant attribute value public virtual void InsertProductVariantAttributeValue(ProductVariantAttributeValue productVariantAttributeValue) { if (productVariantAttributeValue == null) @@ -372,10 +366,6 @@ public virtual void InsertProductVariantAttributeValue(ProductVariantAttributeVa _eventPublisher.EntityInserted(productVariantAttributeValue); } - /// - /// Updates the product variant attribute value - /// - /// The product variant attribute value public virtual void UpdateProductVariantAttributeValue(ProductVariantAttributeValue productVariantAttributeValue) { if (productVariantAttributeValue == null) @@ -411,52 +401,95 @@ private void CombineAll(List> toCombine, List } } - /// - /// Deletes a product variant attribute combination - /// - /// Product variant attribute combination public virtual void DeleteProductVariantAttributeCombination(ProductVariantAttributeCombination combination) { if (combination == null) throw new ArgumentNullException("combination"); - _productVariantAttributeCombinationRepository.Delete(combination); + _pvacRepository.Delete(combination); //event notification _eventPublisher.EntityDeleted(combination); } - /// - /// Gets all product variant attribute combinations - /// - /// Product identifier - /// Product variant attribute combination collection - public virtual IList GetAllProductVariantAttributeCombinations(int productId) - { + public virtual IPagedList GetAllProductVariantAttributeCombinations( + int productId, + int pageIndex, + int pageSize, + bool untracked = true) + { + if (productId == 0) + { + return new PagedList(new List(), pageIndex, pageSize); + } + + string key = string.Format(PRODUCTVARIANTATTRIBUTES_COMBINATIONS_BY_ID_KEY, productId, 0, int.MaxValue); + return _cacheManager.Get(key, () => + { + var query = from pvac in (untracked ? _pvacRepository.TableUntracked : _pvacRepository.Table) + orderby pvac.Id + where pvac.ProductId == productId + select pvac; + + var combinations = new PagedList(query, pageIndex, pageSize); + return combinations; + }); + } + + public virtual IList GetAllProductVariantAttributeCombinationPictureIds(int productId) + { + var pictureIds = new List(); + if (productId == 0) - return new List(); + return pictureIds; - var query = from pvac in _productVariantAttributeCombinationRepository.Table - orderby pvac.Id - where pvac.ProductId == productId - select pvac; + var query = from pvac in _pvacRepository.TableUntracked + where + pvac.ProductId == productId + && pvac.IsActive + && !String.IsNullOrEmpty(pvac.AssignedPictureIds) + select pvac.AssignedPictureIds; - var combinations = query.ToList(); - return combinations; - } + var data = query.ToList(); + if (data.Any()) + { + 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)) + pictureIds.Add(id); + } + } + + return pictureIds; + } + + public virtual Multimap GetProductVariantAttributeCombinations(int[] productIds) + { + Guard.ArgumentNotNull(() => productIds); + + var query = + from pvac in _pvacRepository.TableUntracked + where productIds.Contains(pvac.ProductId) + select pvac; + + var map = query + .OrderBy(x => x.ProductId) + .ToList() + .ToMultimap(x => x.ProductId, x => x); + + return map; + } - /// - /// Get the lowest price of all combinations for a product - /// - /// Product identifier - /// Lowest price public virtual decimal? GetLowestCombinationPrice(int productId) { if (productId == 0) return null; var query = - from pvac in _productVariantAttributeCombinationRepository.Table + from pvac in _pvacRepository.Table where pvac.ProductId == productId && pvac.Price != null && pvac.IsActive orderby pvac.Price ascending select pvac.Price; @@ -465,51 +498,45 @@ orderby pvac.Price ascending return price; } - /// - /// Gets a product variant attribute combination - /// - /// Product variant attribute combination identifier - /// Product variant attribute combination public virtual ProductVariantAttributeCombination GetProductVariantAttributeCombinationById(int productVariantAttributeCombinationId) { if (productVariantAttributeCombinationId == 0) return null; - var combination = _productVariantAttributeCombinationRepository.GetById(productVariantAttributeCombinationId); + var combination = _pvacRepository.GetById(productVariantAttributeCombinationId); return combination; } - /// - /// Inserts a product variant attribute combination - /// - /// Product variant attribute combination + public virtual ProductVariantAttributeCombination GetProductVariantAttributeCombinationBySku(string sku) + { + if (sku.IsEmpty()) + return null; + + var combination = _pvacRepository.Table.FirstOrDefault(x => x.Sku == sku); + return combination; + } + public virtual void InsertProductVariantAttributeCombination(ProductVariantAttributeCombination combination) { if (combination == null) throw new ArgumentNullException("combination"); - // codehint: sm-add //if (combination.IsDefaultCombination) //{ // EnsureSingleDefaultVariant(combination); //} - _productVariantAttributeCombinationRepository.Insert(combination); + _pvacRepository.Insert(combination); //event notification _eventPublisher.EntityInserted(combination); } - /// - /// Updates a product variant attribute combination - /// - /// Product variant attribute combination public virtual void UpdateProductVariantAttributeCombination(ProductVariantAttributeCombination combination) { if (combination == null) throw new ArgumentNullException("combination"); - // codehint: sm-add //if (combination.IsDefaultCombination) //{ // EnsureSingleDefaultVariant(combination); @@ -534,23 +561,16 @@ public virtual void UpdateProductVariantAttributeCombination(ProductVariantAttri // } //} - _productVariantAttributeCombinationRepository.Update(combination); + _pvacRepository.Update(combination); //event notification _eventPublisher.EntityUpdated(combination); } - /// - /// Creates all variant attribute combinations - /// - /// The product public virtual void CreateAllProductVariantAttributeCombinations(Product product) { // delete all existing combinations - foreach(var itm in GetAllProductVariantAttributeCombinations(product.Id)) - { - DeleteProductVariantAttributeCombination(itm); - } + _pvacRepository.DeleteAll(x => x.ProductId == product.Id); var attributes = GetProductVariantAttributesByProductId(product.Id); if (attributes == null || attributes.Count <= 0) @@ -571,26 +591,43 @@ public virtual void CreateAllProductVariantAttributeCombinations(Product product { CombineAll(toCombine, resultMatrix, 0, tmp); - foreach (var values in resultMatrix) + using (var scope = new DbContextScope(ctx: _pvacRepository.Context, autoCommit: false, autoDetectChanges: false, validateOnSave: false, hooksEnabled: false)) { - string attrXml = ""; - foreach (var x in values) - { - attrXml = attributes[values.IndexOf(x)].AddProductAttribute(attrXml, x.Id.ToString()); + ProductVariantAttributeCombination combination = null; + + var idx = 0; + foreach (var values in resultMatrix) + { + idx++; + + string attrXml = ""; + for (var i = 0; i < values.Count; ++i) + { + var value = values[i]; + attrXml = attributes[i].AddProductAttribute(attrXml, value.Id.ToString()); + } + + combination = new ProductVariantAttributeCombination + { + ProductId = product.Id, + AttributesXml = attrXml, + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true + }; + + _pvacRepository.Insert(combination); } - var combination = new ProductVariantAttributeCombination() + scope.Commit(); + + if (combination != null) { - ProductId = product.Id, - AttributesXml = attrXml, - StockQuantity = 10000, - AllowOutOfStockOrders = true, - IsActive = true - }; - - _productVariantAttributeCombinationRepository.Insert(combination); - _eventPublisher.EntityInserted(combination); + // Perf: publish event for last one only + _eventPublisher.EntityInserted(combination); + } } + } //foreach (var y in resultMatrix) { @@ -608,7 +645,7 @@ public virtual bool VariantHasAttributeCombinations(int productId) return false; var query = - from c in _productVariantAttributeCombinationRepository.Table + from c in _pvacRepository.Table where c.ProductId == productId select c; @@ -619,10 +656,6 @@ from c in _productVariantAttributeCombinationRepository.Table #region Product bundle item attribute filter - /// - /// Inserts a product bundle item attribute filter - /// - /// Product bundle item attribute filter public virtual void InsertProductBundleItemAttributeFilter(ProductBundleItemAttributeFilter attributeFilter) { if (attributeFilter == null) @@ -636,10 +669,6 @@ public virtual void InsertProductBundleItemAttributeFilter(ProductBundleItemAttr } } - /// - /// Updates the product bundle item attribute filter - /// - /// Product bundle item attribute filter public virtual void UpdateProductBundleItemAttributeFilter(ProductBundleItemAttributeFilter attributeFilter) { if (attributeFilter == null) @@ -650,10 +679,6 @@ public virtual void UpdateProductBundleItemAttributeFilter(ProductBundleItemAttr _eventPublisher.EntityUpdated(attributeFilter); } - /// - /// Deletes a product bundle item attribute filter - /// - /// Product bundle item attribute filter public virtual void DeleteProductBundleItemAttributeFilter(ProductBundleItemAttributeFilter attributeFilter) { if (attributeFilter == null) @@ -664,10 +689,6 @@ public virtual void DeleteProductBundleItemAttributeFilter(ProductBundleItemAttr _eventPublisher.EntityDeleted(attributeFilter); } - /// - /// Deletes all attribute filters of a bundle item - /// - /// Bundle item public virtual void DeleteProductBundleItemAttributeFilter(ProductBundleItem bundleItem) { if (bundleItem != null && bundleItem.Id != 0) @@ -681,11 +702,6 @@ from x in _productBundleItemAttributeFilterRepository.Table } } - /// - /// Deletes product bundle item attribute filters - /// - /// Attribute identifier - /// Attribute value identifier public virtual void DeleteProductBundleItemAttributeFilter(int attributeId, int attributeValueId) { var attributeFilterQuery = @@ -696,10 +712,6 @@ from x in _productBundleItemAttributeFilterRepository.Table attributeFilterQuery.ToList().Each(x => DeleteProductBundleItemAttributeFilter(x)); } - /// - /// Deletes product bundle item attribute filters - /// - /// Attribute identifier public virtual void DeleteProductBundleItemAttributeFilter(int attributeId) { var attributeFilterQuery = diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index 21d749a085..051fc841f2 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs @@ -1,17 +1,20 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Data; +using SmartStore.Core; using SmartStore.Core.Domain.Catalog; +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) { @@ -25,13 +28,10 @@ public static ProductVariantAttributeCombination MergeWithCombination(this Produ if (selectedAttributes.IsEmpty()) return null; - // let's find appropriate record - var combination = product - .ProductVariantAttributeCombinations - .Where(x => x.IsActive == true) - .FirstOrDefault(x => productAttributeParser.AreProductAttributesEqual(x.AttributesXml, selectedAttributes)); + // let's find appropriate record + var combination = productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes); - if (combination != null) + if (combination != null && combination.IsActive) { product.MergeWithCombination(combination); } @@ -87,9 +87,9 @@ public static void MergeWithCombination(this Product product, ProductVariantAttr product.MergedDataValues.Add("BasePriceBaseAmount", combination.BasePriceBaseAmount); } - public static void GetAllCombinationImageIds(this IList combinations, List imageIds) + public static IList GetAllCombinationPictureIds(this IEnumerable combinations) { - Guard.ArgumentNotNull(imageIds, "imageIds"); + var pictureIds = new List(); if (combinations != null) { @@ -105,21 +105,23 @@ public static void GetAllCombinationImageIds(this IList i == id)) - imageIds.Add(id); + if (int.TryParse(str, out id) && !pictureIds.Exists(i => i == id)) + pictureIds.Add(id); } } } + + return pictureIds; } - /// - /// Finds a related product item by specified identifiers - /// - /// Source - /// The first product identifier - /// The second product identifier - /// Related product - public static RelatedProduct FindRelatedProduct(this IList source, + /// + /// Finds a related product item by specified identifiers + /// + /// Source + /// The first product identifier + /// The second product identifier + /// Related product + public static RelatedProduct FindRelatedProduct(this IList source, int productId1, int productId2) { foreach (RelatedProduct relatedProduct in source) @@ -297,41 +299,93 @@ public static int[] ParseRequiredProductIds(this Product product) } /// - /// Gets the base price + /// Gets the base price info /// /// Product /// Localization service /// Price formatter + /// Currency service + /// Tax service + /// Price calculation service + /// Target currency /// Price adjustment - /// Whether the result string should be language independent - /// The base price - public static string GetBasePriceInfo(this Product product, ILocalizationService localizationService, IPriceFormatter priceFormatter, - decimal priceAdjustment = decimal.Zero, bool languageIndependent = false) + /// Whether the result string should be language insensitive + /// The base price info + public static string GetBasePriceInfo(this Product product, + ILocalizationService localizationService, + IPriceFormatter priceFormatter, + ICurrencyService currencyService, + ITaxService taxService, + IPriceCalculationService priceCalculationService, + Currency currency, + decimal priceAdjustment = decimal.Zero, + bool languageInsensitive = false) { - if (product == null) - throw new ArgumentNullException("product"); - - if (localizationService == null && !languageIndependent) - throw new ArgumentNullException("localizationService"); + Guard.ArgumentNotNull(() => product); + Guard.ArgumentNotNull(() => currencyService); + Guard.ArgumentNotNull(() => taxService); + Guard.ArgumentNotNull(() => priceCalculationService); + Guard.ArgumentNotNull(() => currency); if (product.BasePriceHasValue && product.BasePriceAmount != Decimal.Zero) { - decimal price = decimal.Add(product.Price, priceAdjustment); - decimal basePriceValue = Convert.ToDecimal((price / product.BasePriceAmount) * product.BasePriceBaseAmount); + var workContext = EngineContext.Current.Resolve(); - string basePrice = priceFormatter.FormatPrice(basePriceValue, true, false); - string unit = "{0} {1}".FormatWith(product.BasePriceBaseAmount, product.BasePriceMeasureUnit); + var taxrate = decimal.Zero; + var currentPrice = priceCalculationService.GetFinalPrice(product, workContext.CurrentCustomer, true); + var price = taxService.GetProductPrice(product, decimal.Add(currentPrice, priceAdjustment), out taxrate); + + price = currencyService.ConvertFromPrimaryStoreCurrency(price, currency); - if (languageIndependent) - { - return "{0} / {1}".FormatWith(basePrice, unit); - } + return product.GetBasePriceInfo(price, localizationService, priceFormatter, currency, languageInsensitive); + } - return localizationService.GetResource("Products.BasePriceInfo").FormatWith(basePrice, unit); - } return ""; } + /// + /// Gets the base price info + /// + /// Product + /// The calculated product price + /// 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) + { + Guard.ArgumentNotNull(() => product); + Guard.ArgumentNotNull(() => localizationService); + Guard.ArgumentNotNull(() => priceFormatter); + Guard.ArgumentNotNull(() => currency); + + 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 result = infoTemplate.FormatInvariant( + amountFormatted, + product.BasePriceMeasureUnit, + valueFormatted, + product.BasePriceBaseAmount + ); + + return result; + } + + return ""; + } + public static string GetProductTypeLabel(this Product product, ILocalizationService localizationService) { if (product != null && product.ProductType != ProductType.SimpleProduct) diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs b/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs index 2f9f92362e..001e18789b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs @@ -31,17 +31,37 @@ public ProductSearchContext() /// 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 /// @@ -148,5 +168,35 @@ public ProductSearchContext() /// 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 0b027451b1..6dc93b582d 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1,12 +1,17 @@ using System; using System.Collections.Generic; using System.Data; +using System.Globalization; 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; @@ -41,7 +46,7 @@ public partial class ProductService : IProductService private readonly IRepository _storeMappingRepository; private readonly IRepository _productPictureRepository; private readonly IRepository _productSpecificationAttributeRepository; - private readonly IRepository _productVariantAttributeCombinationRepository; // codehint: sm-add + private readonly IRepository _productVariantAttributeCombinationRepository; private readonly IRepository _productBundleItemRepository; private readonly IProductAttributeService _productAttributeService; private readonly IProductAttributeParser _productAttributeParser; @@ -49,12 +54,10 @@ public partial class ProductService : IProductService private readonly IWorkflowMessageService _workflowMessageService; private readonly IDataProvider _dataProvider; private readonly IDbContext _dbContext; - private readonly ICacheManager _cacheManager; - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; + private readonly ICacheManager _cacheManager; private readonly LocalizationSettings _localizationSettings; private readonly CommonSettings _commonSettings; - private readonly IEventPublisher _eventPublisher; + private readonly ICommonServices _services; #endregion @@ -84,7 +87,7 @@ public partial class ProductService : IProductService /// Localization settings /// Common settings /// Event published - public ProductService(ICacheManager cacheManager, + public ProductService( IRepository productRepository, IRepository relatedProductRepository, IRepository crossSellProductRepository, @@ -100,13 +103,13 @@ public ProductService(ICacheManager cacheManager, IProductAttributeParser productAttributeParser, ILanguageService languageService, IWorkflowMessageService workflowMessageService, - IDataProvider dataProvider, IDbContext dbContext, - IWorkContext workContext, - IStoreContext storeContext, - LocalizationSettings localizationSettings, CommonSettings commonSettings, - IEventPublisher eventPublisher) + IDataProvider dataProvider, + IDbContext dbContext, + ICacheManager cacheManager, + LocalizationSettings localizationSettings, + CommonSettings commonSettings, + ICommonServices services) { - this._cacheManager = cacheManager; this._productRepository = productRepository; this._relatedProductRepository = relatedProductRepository; this._crossSellProductRepository = crossSellProductRepository; @@ -124,11 +127,10 @@ public ProductService(ICacheManager cacheManager, this._workflowMessageService = workflowMessageService; this._dataProvider = dataProvider; this._dbContext = dbContext; - this._workContext = workContext; - this._storeContext = storeContext; + this._cacheManager = cacheManager; this._localizationSettings = localizationSettings; this._commonSettings = commonSettings; - this._eventPublisher = eventPublisher; + this._services = services; this.QuerySettings = DbQuerySettings.Default; } @@ -136,12 +138,91 @@ public ProductService(ICacheManager cacheManager, public DbQuerySettings QuerySettings { get; set; } #endregion - - #region Methods - #region Products + #region Utilities - /// + protected virtual int EnsureMutuallyRelatedProducts(List productIds) + { + int count = 0; + + foreach (int id1 in 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 + select rp).ToList(); + + foreach (int id2 in productIds) + { + if (id1 == id2) + continue; + + if (!mutualAssociations.Any(x => x.ProductId1 == id2)) + { + int maxDisplayOrder = _relatedProductRepository.TableUntracked + .Where(x => x.ProductId1 == id2) + .OrderByDescending(x => x.DisplayOrder) + .Select(x => x.DisplayOrder) + .FirstOrDefault(); + + var newRelatedProduct = new RelatedProduct + { + ProductId1 = id2, + ProductId2 = id1, + DisplayOrder = maxDisplayOrder + 1 + }; + + InsertRelatedProduct(newRelatedProduct); + ++count; + } + } + } + + return count; + } + + protected virtual int EnsureMutuallyCrossSellProducts(List productIds) + { + int count = 0; + + foreach (int id1 in 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 + select rp).ToList(); + + foreach (int id2 in productIds) + { + if (id1 == id2) + continue; + + if (!mutualAssociations.Any(x => x.ProductId1 == id2)) + { + var newCrossSellProduct = new CrossSellProduct + { + ProductId1 = id2, + ProductId2 = id1 + }; + + InsertCrossSellProduct(newCrossSellProduct); + ++count; + } + } + } + + return count; + } + + #endregion + + #region Methods + + #region Products + + /// /// Delete a product /// /// Product @@ -155,6 +236,17 @@ public virtual void DeleteProduct(Product product) product.QuantityUnitId = null; UpdateProduct(product); + + if (product.ProductType == ProductType.GroupedProduct) + { + var associatedProducts = _productRepository.Table + .Where(x => x.ParentGroupedProductId == product.Id) + .ToList(); + + associatedProducts.ForEach(x => x.ParentGroupedProductId = 0); + + _dbContext.SaveChanges(); + } } /// @@ -163,12 +255,12 @@ public virtual void DeleteProduct(Product product) /// Product collection public virtual IList GetAllProductsDisplayedOnHomePage() { - var query = from p in _productRepository.Table - orderby p.Name - where p.Published && - !p.Deleted && - p.ShowOnHomePage - select p; + var query = + from p in _productRepository.Table + orderby p.HomePageDisplayOrder + where p.Published && !p.Deleted && p.ShowOnHomePage + select p; + var products = query.ToList(); return products; } @@ -204,17 +296,13 @@ public virtual IList GetProductsByIds(int[] productIds) where productIds.Contains(p.Id) select p; var products = query.ToList(); - - //sort by passed identifiers - var sortedProducts = new List(); - foreach (int id in productIds) - { - var product = products.Find(x => x.Id == id); - if (product != null) - sortedProducts.Add(product); - } - return sortedProducts; + // 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(); } /// @@ -233,7 +321,7 @@ public virtual void InsertProduct(Product product) _cacheManager.RemoveByPattern(PRODUCTS_PATTERN_KEY); //event notification - _eventPublisher.EntityInserted(product); + _services.EventPublisher.EntityInserted(product); } /// @@ -251,16 +339,16 @@ public virtual void UpdateProduct(Product product, bool publishEvent = true) modified = _productRepository.IsModified(product); } - //update + // update _productRepository.Update(product); - //cache + // cache _cacheManager.RemoveByPattern(PRODUCTS_PATTERN_KEY); - //event notification + // event notification if (publishEvent && modified) { - _eventPublisher.EntityUpdated(product); + _services.EventPublisher.EntityUpdated(product); } } @@ -273,18 +361,13 @@ public virtual int CountProducts(ProductSearchContext ctx) return query.Distinct().Count(); } - /// - /// Search products - /// codehint: sm-edit (changed input parameter) - /// public virtual IPagedList SearchProducts(ProductSearchContext ctx) { - // codehint: sm-edit (turn off nc filtering, we have our own) ctx.LoadFilterableSpecificationAttributeOptionIds = false; ctx.FilterableSpecificationAttributeOptionIds = new List(); - _eventPublisher.Publish(new ProductsSearchingEvent(ctx)); + _services.EventPublisher.Publish(new ProductsSearchingEvent(ctx)); //search by keyword bool searchLocalizedValue = false; @@ -307,7 +390,7 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) ctx.CategoryIds.Remove(0); //Access control list. Allowed customer roles - var allowedCustomerRolesIds = _workContext.CurrentCustomer.CustomerRoles + var allowedCustomerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles .Where(cr => cr.Active).Select(cr => cr.Id).ToList(); if (_commonSettings.UseStoredProceduresIfSupported && _dataProvider.StoredProceduresSupported) @@ -319,7 +402,7 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) //pass categry identifiers as comma-delimited string string commaSeparatedCategoryIds = ""; - if (ctx.CategoryIds != null) + if (ctx.CategoryIds != null && !(ctx.WithoutCategories ?? false)) { for (int i = 0; i < ctx.CategoryIds.Count; i++) { @@ -369,7 +452,7 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) var pManufacturerId = _dataProvider.GetParameter(); pManufacturerId.ParameterName = "ManufacturerId"; - pManufacturerId.Value = ctx.ManufacturerId; + pManufacturerId.Value = (ctx.WithoutManufacturers ?? false) ? 0 : ctx.ManufacturerId; pManufacturerId.DbType = DbType.Int32; var pStoreId = _dataProvider.GetParameter(); @@ -482,6 +565,57 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) 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; @@ -520,19 +654,29 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) pPageSize, pShowHidden, pLoadFilterableSpecificationAttributeOptionIds, + pWithoutCategories, + pWithoutManufacturers, + pIsPublished, + pHomePageProducts, + pIdMin, + pIdMax, + pAvailabilityMin, + pAvailabilityMax, + pCreatedFromUtc, + pCreatedToUtc, pFilterableSpecificationAttributeOptionIds, pTotalRecords); - //get filterable specification attribute option identifier + + // get filterable specification attribute option identifier string filterableSpecificationAttributeOptionIdsStr = (pFilterableSpecificationAttributeOptionIds.Value != DBNull.Value) ? (string)pFilterableSpecificationAttributeOptionIds.Value : ""; - if (ctx.LoadFilterableSpecificationAttributeOptionIds && - !string.IsNullOrWhiteSpace(filterableSpecificationAttributeOptionIdsStr)) + if (ctx.LoadFilterableSpecificationAttributeOptionIds && !string.IsNullOrWhiteSpace(filterableSpecificationAttributeOptionIdsStr)) { - // codehint: sm-edit ctx.FilterableSpecificationAttributeOptionIds.AddRange(filterableSpecificationAttributeOptionIdsStr .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => Convert.ToInt32(x.Trim()))); } - //return products + + // return products int totalRecords = (pTotalRecords.Value != DBNull.Value) ? Convert.ToInt32(pTotalRecords.Value) : 0; return new PagedList(products, ctx.PageIndex, ctx.PageSize, totalRecords); @@ -633,27 +777,41 @@ where psa.AllowFiltering } } - /// - /// Builds a product query based on the options in ProductSearchContext parameter. - /// - /// Parameters to build the query. - /// Customer role ids (ACL). - /// Whether to search localized values. - public virtual IQueryable PrepareProductSearchQuery(ProductSearchContext ctx, IEnumerable allowedCustomerRolesIds = null, bool searchLocalizedValue = false) - { - if (allowedCustomerRolesIds == null) - { - allowedCustomerRolesIds = _workContext.CurrentCustomer.CustomerRoles.Where(cr => cr.Active).Select(cr => cr.Id).ToList(); - } - - // products - var query = ctx.Query ?? _productRepository.Table; - query = query.Where(p => !p.Deleted); + public virtual IQueryable PrepareProductSearchQuery( + ProductSearchContext ctx, + IEnumerable allowedCustomerRolesIds = null, + bool searchLocalizedValue = false) + { + return PrepareProductSearchQuery(ctx, x => x, allowedCustomerRolesIds, searchLocalizedValue); + } - if (!ctx.ShowHidden) - { - query = query.Where(p => p.Published); - } + public virtual IQueryable PrepareProductSearchQuery( + ProductSearchContext ctx, + Expression> selector, + IEnumerable allowedCustomerRolesIds = null, + bool searchLocalizedValue = false) + { + Guard.ArgumentNotNull(() => ctx); + Guard.ArgumentNotNull(() => selector); + + if (allowedCustomerRolesIds == null) + { + allowedCustomerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(cr => cr.Active).Select(cr => cr.Id).ToList(); + } + + // 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); + } if (ctx.ParentGroupedProductId > 0) { @@ -665,6 +823,11 @@ public virtual IQueryable PrepareProductSearchQuery(ProductSearchContex query = query.Where(p => p.VisibleIndividually); } + if (ctx.HomePageProducts.HasValue) + { + query = query.Where(p => p.ShowOnHomePage == ctx.HomePageProducts.Value); + } + if (ctx.ProductType.HasValue) { int productTypeId = (int)ctx.ProductType.Value; @@ -675,6 +838,34 @@ public virtual IQueryable PrepareProductSearchQuery(ProductSearchContex { query = query.Where(x => ctx.ProductIds.Contains(x.Id)); } + 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) + { + query = query.Where(x => x.StockQuantity >= ctx.AvailabilityMinimum.Value); + } + + if (ctx.AvailabilityMaximum.HasValue) + { + query = query.Where(x => x.StockQuantity <= ctx.AvailabilityMaximum.Value); + } + + if (ctx.CreatedFromUtc.HasValue) + { + query = query.Where(x => x.CreatedOnUtc >= ctx.CreatedFromUtc.Value); + } + + if (ctx.CreatedToUtc.HasValue) + { + query = query.Where(x => x.CreatedOnUtc <= ctx.CreatedToUtc.Value); + } //The function 'CurrentUtcDateTime' is not supported by SQL Server Compact. //That's why we pass the date value @@ -728,39 +919,39 @@ public virtual IQueryable PrepareProductSearchQuery(ProductSearchContex (!p.AvailableEndDateTimeUtc.HasValue || p.AvailableEndDateTimeUtc.Value > nowUtc)); } - // searching by keyword - if (!String.IsNullOrWhiteSpace(ctx.Keywords)) - { - 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)) || + // searching by keyword + if (!String.IsNullOrWhiteSpace(ctx.Keywords)) + { + 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)) || + (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; - } + (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; + } - if (!ctx.ShowHidden && !QuerySettings.IgnoreAcl) - { - query = + if (!ctx.ShowHidden && !QuerySettings.IgnoreAcl) + { + 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; - } + } if (ctx.StoreId > 0 && !QuerySettings.IgnoreMultiStore) { - query = + query = from p in query join sm in _storeMappingRepository.Table on new { pid = p.Id, pname = "Product" } equals new { pid = sm.EntityId, pname = sm.EntityName } into psm from sm in psm.DefaultIfEmpty() @@ -768,10 +959,10 @@ from sm in psm.DefaultIfEmpty() select p; } - // search by specs - if (ctx.FilteredSpecs != null && ctx.FilteredSpecs.Count > 0) - { - query = + // search by specs + if (ctx.FilteredSpecs != null && ctx.FilteredSpecs.Count > 0) + { + query = from p in query where !ctx.FilteredSpecs.Except ( @@ -780,57 +971,71 @@ from p in query .Select(psa => psa.SpecificationAttributeOptionId) ).Any() select p; - } + } - // category filtering - 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; - } - } + // 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; + } + } - // manufacturer filtering - 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; - } + // 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; + } - // 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; - } + // 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; + } - return query; - } + return query.Select(selector); + } /// /// Update product review totals @@ -873,24 +1078,25 @@ public virtual void UpdateProductReviewTotals(Product product) /// Result public virtual IList GetLowStockProducts() { - //Track inventory for product + // Track inventory for product var query1 = from p in _productRepository.Table orderby p.MinStockQuantity where !p.Deleted && - p.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStock && - p.MinStockQuantity >= p.StockQuantity + p.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStock && + p.MinStockQuantity >= p.StockQuantity select p; var products1 = query1.ToList(); - //Track inventory for product by product attributes + // Track inventory for product by product attributes var query2 = from p in _productRepository.Table from pvac in p.ProductVariantAttributeCombinations where !p.Deleted && - p.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStockByAttributes && - pvac.StockQuantity <= 0 + p.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStockByAttributes && + pvac.StockQuantity <= 0 select p; - //only distinct products (group by ID) - //if we use standard Distinct() method, then all fields will be compared (low performance) + + // only distinct products (group by ID) + // if we use standard Distinct() method, then all fields will be compared (low performance) query2 = from p in query2 group p by p.Id into pGroup orderby pGroup.Key @@ -916,8 +1122,7 @@ public virtual Product GetProductBySku(string sku) var query = from p in _productRepository.Table orderby p.DisplayOrder, p.Id - where !p.Deleted && - p.Sku == sku + where !p.Deleted && p.Sku == sku select p; var product = query.FirstOrDefault(); return product; @@ -926,7 +1131,7 @@ public virtual Product GetProductBySku(string sku) /// /// Gets a product by GTIN /// - /// GTIN + /// GTIN /// Product public virtual Product GetProductByGtin(string gtin) { @@ -944,6 +1149,36 @@ orderby p.Id return product; } + public virtual Product GetProductByManufacturerPartNumber(string manufacturerPartNumber) + { + if (manufacturerPartNumber.IsEmpty()) + return null; + + manufacturerPartNumber = manufacturerPartNumber.Trim(); + + var product = _productRepository.Table + .Where(x => !x.Deleted && x.ManufacturerPartNumber == manufacturerPartNumber) + .OrderBy(x => x.Id) + .FirstOrDefault(); + + return product; + } + + public virtual Product GetProductByName(string name) + { + if (name.IsEmpty()) + return null; + + name = name.Trim(); + + var product = _productRepository.Table + .Where(x => !x.Deleted && x.Name == name) + .OrderBy(x => x.Id) + .FirstOrDefault(); + + return product; + } + /// /// Adjusts inventory /// @@ -1069,7 +1304,7 @@ public virtual AdjustInventoryResult AdjustInventory(Product product, bool decre break; case ManageInventoryMethod.ManageStockByAttributes: { - var combination = _productAttributeParser.FindProductVariantAttributeCombination(product, attributesXml); + var combination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, attributesXml); if (combination != null) { result.StockQuantityOld = combination.StockQuantity; @@ -1149,6 +1384,71 @@ public virtual void UpdateHasDiscountsApplied(Product product) UpdateProduct(product); } + public virtual Multimap GetProductTagsByProductIds(int[] productIds) + { + Guard.ArgumentNotNull(() => productIds); + + var query = _productRepository.TableUntracked + .Expand(x => x.ProductTags) + .Where(x => productIds.Contains(x.Id)) + .Select(x => new + { + ProductId = x.Id, + Tags = x.ProductTags + }); + + var map = new Multimap(); + + foreach (var item in query.ToList()) + { + foreach (var tag in item.Tags) + map.Add(item.ProductId, tag); + } + + return map; + } + + public virtual Multimap GetAppliedDiscountsByProductIds(int[] productIds) + { + Guard.ArgumentNotNull(() => productIds); + + var query = _productRepository.TableUntracked + .Expand(x => x.AppliedDiscounts.Select(y => y.DiscountRequirements)) + .Where(x => productIds.Contains(x.Id)) + .Select(x => new + { + ProductId = x.Id, + Discounts = x.AppliedDiscounts + }); + + var map = new Multimap(); + + foreach (var item in query.ToList()) + { + foreach (var discount in item.Discounts) + map.Add(item.ProductId, discount); + } + + 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 @@ -1165,7 +1465,7 @@ public virtual void DeleteRelatedProduct(RelatedProduct relatedProduct) _relatedProductRepository.Delete(relatedProduct); //event notification - _eventPublisher.EntityDeleted(relatedProduct); + _services.EventPublisher.EntityDeleted(relatedProduct); } /// @@ -1178,13 +1478,11 @@ public virtual IList GetRelatedProductsByProductId1(int productI { var query = from rp in _relatedProductRepository.Table join p in _productRepository.Table on rp.ProductId2 equals p.Id - where rp.ProductId1 == productId1 && - !p.Deleted && - (showHidden || p.Published) + where rp.ProductId1 == productId1 && !p.Deleted && (showHidden || p.Published) orderby rp.DisplayOrder select rp; - var relatedProducts = query.ToList(); + var relatedProducts = query.ToList(); return relatedProducts; } @@ -1214,7 +1512,7 @@ public virtual void InsertRelatedProduct(RelatedProduct relatedProduct) _relatedProductRepository.Insert(relatedProduct); //event notification - _eventPublisher.EntityInserted(relatedProduct); + _services.EventPublisher.EntityInserted(relatedProduct); } /// @@ -1229,9 +1527,26 @@ public virtual void UpdateRelatedProduct(RelatedProduct relatedProduct) _relatedProductRepository.Update(relatedProduct); //event notification - _eventPublisher.EntityUpdated(relatedProduct); + _services.EventPublisher.EntityUpdated(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); + var productIds = relatedProducts.Select(x => x.ProductId2).ToList(); + + if (productIds.Count > 0 && !productIds.Any(x => x == productId1)) + productIds.Add(productId1); + + int count = EnsureMutuallyRelatedProducts(productIds); + return count; + } + #endregion #region Cross-sell products @@ -1248,7 +1563,7 @@ public virtual void DeleteCrossSellProduct(CrossSellProduct crossSellProduct) _crossSellProductRepository.Delete(crossSellProduct); //event notification - _eventPublisher.EntityDeleted(crossSellProduct); + _services.EventPublisher.EntityDeleted(crossSellProduct); } /// @@ -1296,7 +1611,7 @@ public virtual void InsertCrossSellProduct(CrossSellProduct crossSellProduct) _crossSellProductRepository.Insert(crossSellProduct); //event notification - _eventPublisher.EntityInserted(crossSellProduct); + _services.EventPublisher.EntityInserted(crossSellProduct); } /// @@ -1311,7 +1626,7 @@ public virtual void UpdateCrossSellProduct(CrossSellProduct crossSellProduct) _crossSellProductRepository.Update(crossSellProduct); //event notification - _eventPublisher.EntityUpdated(crossSellProduct); + _services.EventPublisher.EntityUpdated(crossSellProduct); } /// @@ -1362,6 +1677,24 @@ public virtual IList GetCrosssellProductsByShoppingCart(IList + /// 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); + var productIds = crossSellProducts.Select(x => x.ProductId2).ToList(); + + if (productIds.Count > 0 && !productIds.Any(x => x == productId1)) + productIds.Add(productId1); + + int count = EnsureMutuallyCrossSellProducts(productIds); + return count; + } + #endregion #region Tier prices @@ -1380,7 +1713,7 @@ public virtual void DeleteTierPrice(TierPrice tierPrice) _cacheManager.RemoveByPattern(PRODUCTS_PATTERN_KEY); //event notification - _eventPublisher.EntityDeleted(tierPrice); + _services.EventPublisher.EntityDeleted(tierPrice); } /// @@ -1397,6 +1730,31 @@ public virtual TierPrice GetTierPriceById(int tierPriceId) return tierPrice; } + public virtual Multimap GetTierPricesByProductIds(int[] productIds, Customer customer = null, int storeId = 0) + { + Guard.ArgumentNotNull(() => productIds); + + var query = + from x in _tierPriceRepository.TableUntracked + where productIds.Contains(x.ProductId) + select x; + + if (storeId != 0) + query = query.Where(x => x.StoreId == 0 || x.StoreId == storeId); + + query = query.OrderBy(x => x.ProductId).ThenBy(x => x.Quantity); + + var list = query.ToList(); + + if (customer != null) + list = list.FilterForCustomer(customer).ToList(); + + var map = list + .ToMultimap(x => x.ProductId, x => x); + + return map; + } + /// /// Inserts a tier price /// @@ -1411,7 +1769,7 @@ public virtual void InsertTierPrice(TierPrice tierPrice) _cacheManager.RemoveByPattern(PRODUCTS_PATTERN_KEY); //event notification - _eventPublisher.EntityInserted(tierPrice); + _services.EventPublisher.EntityInserted(tierPrice); } /// @@ -1428,7 +1786,7 @@ public virtual void UpdateTierPrice(TierPrice tierPrice) _cacheManager.RemoveByPattern(PRODUCTS_PATTERN_KEY); //event notification - _eventPublisher.EntityUpdated(tierPrice); + _services.EventPublisher.EntityUpdated(tierPrice); } #endregion @@ -1444,13 +1802,12 @@ public virtual void DeleteProductPicture(ProductPicture productPicture) if (productPicture == null) throw new ArgumentNullException("productPicture"); - // codehint: sm-add UnassignDeletedPictureFromVariantCombinations(productPicture); _productPictureRepository.Delete(productPicture); //event notification - _eventPublisher.EntityDeleted(productPicture); + _services.EventPublisher.EntityDeleted(productPicture); } private void UnassignDeletedPictureFromVariantCombinations(ProductPicture productPicture) @@ -1498,6 +1855,33 @@ orderby pp.DisplayOrder return productPictures; } + public virtual Multimap GetProductPicturesByProductIds(int[] productIds, bool onlyFirstPicture = false) + { + var query = + from pp in _productPictureRepository.TableUntracked.Expand(x => x.Picture) + where productIds.Contains(pp.ProductId) + orderby pp.ProductId, pp.DisplayOrder + select pp; + + if (onlyFirstPicture) + { + var map = query.GroupBy(x => x.ProductId, x => x) + .Select(x => x.FirstOrDefault()) + .ToList() + .ToMultimap(x => x.ProductId, x => x); + + return map; + } + else + { + var map = query + .ToList() + .ToMultimap(x => x.ProductId, x => x); + + return map; + } + } + /// /// Gets a product picture /// @@ -1524,7 +1908,7 @@ public virtual void InsertProductPicture(ProductPicture productPicture) _productPictureRepository.Insert(productPicture); //event notification - _eventPublisher.EntityInserted(productPicture); + _services.EventPublisher.EntityInserted(productPicture); } /// @@ -1539,7 +1923,7 @@ public virtual void UpdateProductPicture(ProductPicture productPicture) _productPictureRepository.Update(productPicture); //event notification - _eventPublisher.EntityUpdated(productPicture); + _services.EventPublisher.EntityUpdated(productPicture); } #endregion @@ -1567,7 +1951,7 @@ public virtual void InsertBundleItem(ProductBundleItem bundleItem) _productBundleItemRepository.Insert(bundleItem); //event notification - _eventPublisher.EntityInserted(bundleItem); + _services.EventPublisher.EntityInserted(bundleItem); } /// @@ -1582,7 +1966,7 @@ public virtual void UpdateBundleItem(ProductBundleItem bundleItem) _productBundleItemRepository.Update(bundleItem); //event notification - _eventPublisher.EntityUpdated(bundleItem); + _services.EventPublisher.EntityUpdated(bundleItem); } /// @@ -1597,7 +1981,7 @@ public virtual void DeleteBundleItem(ProductBundleItem bundleItem) _productBundleItemRepository.Delete(bundleItem); //event notification - _eventPublisher.EntityDeleted(bundleItem); + _services.EventPublisher.EntityDeleted(bundleItem); } /// @@ -1628,7 +2012,7 @@ join p in _productRepository.Table on pbi.ProductId equals p.Id orderby pbi.DisplayOrder select pbi; - query = _productBundleItemRepository.Expand(query, x => x.Product); + query = query.Expand(x => x.Product); var bundleItemData = new List(); @@ -1637,6 +2021,24 @@ orderby pbi.DisplayOrder return bundleItemData; } + public virtual Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false) + { + Guard.ArgumentNotNull(() => productIds); + + var query = + from pbi in _productBundleItemRepository.TableUntracked + join p in _productRepository.TableUntracked on pbi.ProductId equals p.Id + where productIds.Contains(pbi.BundleProductId) && !p.Deleted && (showHidden || (pbi.Published && p.Published)) + orderby pbi.DisplayOrder + select pbi; + + var map = query + .ToList() + .ToMultimap(x => x.BundleProductId, x => x); + + return map; + } + #endregion #endregion diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductTagService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductTagService.cs index 53cad4b1a5..a80d073138 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductTagService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductTagService.cs @@ -11,10 +11,10 @@ namespace SmartStore.Services.Catalog { - /// - /// Product tag service - /// - public partial class ProductTagService : IProductTagService + /// + /// Product tag service + /// + public partial class ProductTagService : IProductTagService { #region Constants @@ -36,6 +36,7 @@ public partial class ProductTagService : IProductTagService #region Fields private readonly IRepository _productTagRepository; + private readonly IRepository _storeMappingRepository; private readonly IDataProvider _dataProvider; private readonly IDbContext _dbContext; private readonly CommonSettings _commonSettings; @@ -55,7 +56,9 @@ public partial class ProductTagService : IProductTagService /// Common settings /// Cache manager /// Event published - public ProductTagService(IRepository productTagRepository, + public ProductTagService( + IRepository productTagRepository, + IRepository storeMappingRepository, IDataProvider dataProvider, IDbContext dbContext, CommonSettings commonSettings, @@ -63,14 +66,19 @@ public ProductTagService(IRepository productTagRepository, IEventPublisher eventPublisher) { _productTagRepository = productTagRepository; + _storeMappingRepository = storeMappingRepository; _dataProvider = dataProvider; _dbContext = dbContext; _commonSettings = commonSettings; _cacheManager = cacheManager; _eventPublisher = eventPublisher; - } - #endregion + QuerySettings = DbQuerySettings.Default; + } + + public DbQuerySettings QuerySettings { get; set; } + + #endregion #region Nested classes @@ -94,55 +102,38 @@ private Dictionary GetProductCount(int storeId) string key = string.Format(PRODUCTTAG_COUNT_KEY, storeId); return _cacheManager.Get(key, () => { + IEnumerable tagCount = null; if (_commonSettings.UseStoredProceduresIfSupported && _dataProvider.StoredProceduresSupported) { - //stored procedures are enabled and supported by the database. - //It's much faster than the LINQ implementation below + //stored procedures are enabled and supported by the database. It's much faster than the LINQ implementation below - #region Use stored procedure - - //prepare parameters var pStoreId = _dataProvider.GetParameter(); pStoreId.ParameterName = "StoreId"; pStoreId.Value = storeId; pStoreId.DbType = DbType.Int32; - - //invoke stored procedure - var result = _dbContext.SqlQuery( - "Exec ProductTagCountLoadAll @StoreId", - pStoreId); - - var dictionary = new Dictionary(); - foreach (var item in result) - dictionary.Add(item.ProductTagId, item.ProductCount); - return dictionary; - - #endregion + tagCount = _dbContext.SqlQuery("Exec ProductTagCountLoadAll @StoreId", pStoreId); } else { //stored procedures aren't supported. Use LINQ - #region Search products - var query = from pt in _productTagRepository.Table - select new - { - Id = pt.Id, - ProductCount = pt.Products - //published and not deleted product/variants - .Count(p => !p.Deleted && p.Published) - //UNDOEN filter by store identifier if specified ( > 0 ) - }; - - var dictionary = new Dictionary(); - foreach (var item in query) - dictionary.Add(item.Id, item.ProductCount); - return dictionary; - - #endregion + tagCount = _productTagRepository.Table + .Select(pt => new ProductTagWithCount + { + ProductTagId = pt.Id, + ProductCount = (storeId > 0 && !QuerySettings.IgnoreMultiStore) ? + (from p in pt.Products + 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 || storeId == sm.StoreId) && !p.Deleted && p.Published + select p).Count() : + pt.Products.Count(p => !p.Deleted && p.Published) + }); } + + return tagCount.ToDictionary(x => x.ProductTagId, x => x.ProductCount); }); } @@ -265,10 +256,11 @@ public virtual void UpdateProductTag(ProductTag productTag) public virtual int GetProductCount(int productTagId, int storeId) { var dictionary = GetProductCount(storeId); + if (dictionary.ContainsKey(productTagId)) return dictionary[productTagId]; - else - return 0; + + return 0; } #endregion diff --git a/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs index 5a19971440..cc146a0f5b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs @@ -74,7 +74,7 @@ public virtual SpecificationAttribute GetSpecificationAttributeById(int specific /// /// Gets specification attributes /// - /// Specification attributes + /// Specification attribute query public virtual IQueryable GetSpecificationAttributes() { var query = @@ -85,6 +85,25 @@ from sa in _specificationAttributeRepository.Table return query; } + /// + /// Gets specification attributes by identifier + /// + /// Identifiers + /// Specification attribute query + public virtual IQueryable GetSpecificationAttributesByIds(int[] ids) + { + if (ids == null || ids.Length == 0) + return null; + + var query = + from sa in _specificationAttributeRepository.Table + where ids.Contains(sa.Id) + orderby sa.DisplayOrder, sa.Name + select sa; + + return query; + } + /// /// Deletes a specification attribute /// @@ -94,7 +113,7 @@ public virtual void DeleteSpecificationAttribute(SpecificationAttribute specific if (specificationAttribute == null) throw new ArgumentNullException("specificationAttribute"); - // codehint: sm-add (delete localized properties of options) + // (delete localized properties of options) var options = GetSpecificationAttributeOptionsBySpecificationAttribute(specificationAttribute.Id); foreach (var itm in options) { DeleteSpecificationAttributeOption(itm); @@ -337,7 +356,6 @@ public virtual void UpdateProductSpecificationAttribute(ProductSpecificationAttr _eventPublisher.EntityUpdated(productSpecificationAttribute); } - /// codehint: sm-add public virtual void UpdateProductSpecificationMapping(int specificationAttributeId, string field, bool value) { if (specificationAttributeId == 0 || field.IsEmpty()) return; diff --git a/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs b/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs index 9f108f69ed..1055aaa340 100644 --- a/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs +++ b/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Collections.Generic; using System.Text; using SmartStore.Core.Domain.Common; @@ -7,41 +8,79 @@ namespace SmartStore.Services.Common { public static class AddressExtentions { - /// - /// Find an address - /// - /// Source - /// First name - /// Last name - /// Phone number - /// Email - /// Fax number - /// Company - /// Address 1 - /// Address 2 - /// City - /// State/province identifier - /// Zip postal code - /// Country identifier - /// Address - public static Address FindAddress(this List
source, - string firstName, string lastName, string phoneNumber, - string email, string faxNumber, string company, string address1, - string address2, string city, int? stateProvinceId, - string zipPostalCode, int? countryId) + /// + /// Find first occurrence of an address + /// + /// Addresses to be searched + /// Address to find + /// First occurrence of address + public static Address FindAddress(this ICollection
source, Address address) + { + return source.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 + ); + } + + /// + /// Find an address + /// + /// Source + /// First name + /// Last name + /// Phone number + /// Email + /// Fax number + /// Company + /// Address 1 + /// Address 2 + /// City + /// State/province identifier + /// Zip postal code + /// Country identifier + /// Address + public static Address FindAddress( + this ICollection
source, + string firstName, + string lastName, + string phoneNumber, + string email, + string faxNumber, + string company, + string address1, + string address2, + string city, + int? stateProvinceId, + string zipPostalCode, + int? countryId) { - return source.Find((a) => ((String.IsNullOrEmpty(a.FirstName) && String.IsNullOrEmpty(firstName)) || a.FirstName == firstName) && - ((String.IsNullOrEmpty(a.LastName) && String.IsNullOrEmpty(lastName)) || a.LastName == lastName) && - ((String.IsNullOrEmpty(a.PhoneNumber) && String.IsNullOrEmpty(phoneNumber)) || a.PhoneNumber == phoneNumber) && - ((String.IsNullOrEmpty(a.Email) && String.IsNullOrEmpty(email)) || a.Email == email) && - ((String.IsNullOrEmpty(a.FaxNumber) && String.IsNullOrEmpty(faxNumber)) || a.FaxNumber == faxNumber) && - ((String.IsNullOrEmpty(a.Company) && String.IsNullOrEmpty(company)) || a.Company == company) && - ((String.IsNullOrEmpty(a.Address1) && String.IsNullOrEmpty(address1)) || a.Address1 == address1) && - ((String.IsNullOrEmpty(a.Address2) && String.IsNullOrEmpty(address2)) || a.Address2 == address2) && - ((String.IsNullOrEmpty(a.City) && String.IsNullOrEmpty(city)) || a.City == city) && - ((a.StateProvinceId.IsNullOrDefault() && stateProvinceId.IsNullOrDefault()) || a.StateProvinceId == stateProvinceId) && - ((String.IsNullOrEmpty(a.ZipPostalCode) && String.IsNullOrEmpty(zipPostalCode)) || a.ZipPostalCode == zipPostalCode) && - ((a.CountryId.IsNullOrDefault() && countryId.IsNullOrDefault()) || a.CountryId == countryId)); + Func addressMatcher = (x) => + { + return x.Email.IsCaseInsensitiveEqual(email) + && x.LastName.IsCaseInsensitiveEqual(lastName) + && x.FirstName.IsCaseInsensitiveEqual(firstName) + && x.Address1.IsCaseInsensitiveEqual(address1) + && x.Address2.IsCaseInsensitiveEqual(address2) + && x.Company.IsCaseInsensitiveEqual(company) + && x.ZipPostalCode.IsCaseInsensitiveEqual(zipPostalCode) + && x.City.IsCaseInsensitiveEqual(city) + && x.PhoneNumber.IsCaseInsensitiveEqual(phoneNumber) + && x.FaxNumber.IsCaseInsensitiveEqual(faxNumber) + && x.StateProvinceId == stateProvinceId + && x.CountryId == countryId; + }; + + return source.FirstOrDefault(addressMatcher); } /// Returns the full name of the address. diff --git a/src/Libraries/SmartStore.Services/Common/AddressService.cs b/src/Libraries/SmartStore.Services/Common/AddressService.cs index 026b110713..6ba5546ccb 100644 --- a/src/Libraries/SmartStore.Services/Common/AddressService.cs +++ b/src/Libraries/SmartStore.Services/Common/AddressService.cs @@ -4,6 +4,7 @@ using SmartStore.Core.Domain.Common; using SmartStore.Services.Directory; using SmartStore.Core.Events; +using System.Collections.Generic; namespace SmartStore.Services.Common { @@ -62,7 +63,6 @@ public virtual void DeleteAddress(Address address) _eventPublisher.EntityDeleted(address); } - /// codehint: sm-add public virtual void DeleteAddress(int id) { var address = GetAddressById(id); @@ -116,6 +116,18 @@ public virtual Address GetAddressById(int addressId) return address; } + public virtual IList
GetAddressByIds(int[] addressIds) + { + Guard.ArgumentNotNull(() => addressIds); + + var query = + from x in _addressRepository.TableUntracked.Expand(x => x.Country).Expand(x => x.StateProvince) + where addressIds.Contains(x.Id) + select x; + + return query.ToList(); + } + /// /// Inserts an address /// diff --git a/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs b/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs index e3d58978bb..8f23424525 100644 --- a/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; @@ -165,6 +166,20 @@ public virtual IList GetAttributesForEntity(int entityId, stri }); } + public virtual Multimap GetAttributesForEntity(int[] entityIds, string keyGroup) + { + Guard.ArgumentNotNull(() => entityIds); + + var query = _genericAttributeRepository.TableUntracked + .Where(x => entityIds.Contains(x.EntityId) && x.KeyGroup == keyGroup); + + var map = query + .ToList() + .ToMultimap(x => x.EntityId, x => x); + + return map; + } + /// /// Get queryable attributes /// @@ -194,48 +209,54 @@ public virtual void SaveAttribute(BaseEntity entity, string key, TPro if (entity == null) throw new ArgumentNullException("entity"); - string keyGroup = entity.GetUnproxiedEntityType().Name; + SaveAttribute(entity.Id, key, entity.GetUnproxiedEntityType().Name, value, storeId); + } - var props = GetAttributesForEntity(entity.Id, keyGroup) + public virtual void SaveAttribute(int entityId, string key, string keyGroup, TPropType value, int storeId = 0) + { + Guard.ArgumentNotZero(entityId, "entityId"); + + var props = GetAttributesForEntity(entityId, keyGroup) .Where(x => x.StoreId == storeId) .ToList(); - var prop = props.FirstOrDefault(ga => - ga.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); //should be culture invariant - string valueStr = value.Convert(); + var prop = props.FirstOrDefault(ga => + ga.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); // should be culture invariant - if (prop != null) - { - if (string.IsNullOrWhiteSpace(valueStr)) - { - //delete - DeleteAttribute(prop); - } - else - { - //update - prop.Value = valueStr; - UpdateAttribute(prop); - } - } - else - { - if (!string.IsNullOrWhiteSpace(valueStr)) - { - //insert - prop = new GenericAttribute() - { - EntityId = entity.Id, - Key = key, - KeyGroup = keyGroup, - Value = valueStr, + string valueStr = value.Convert(); + + if (prop != null) + { + if (string.IsNullOrWhiteSpace(valueStr)) + { + // delete + DeleteAttribute(prop); + } + else + { + // update + prop.Value = valueStr; + UpdateAttribute(prop); + } + } + else + { + if (!string.IsNullOrWhiteSpace(valueStr)) + { + // insert + prop = new GenericAttribute + { + EntityId = entityId, + Key = key, + KeyGroup = keyGroup, + Value = valueStr, StoreId = storeId - }; - InsertAttribute(prop); - } - } - } + }; + InsertAttribute(prop); + } + } + } - #endregion - } + #endregion + } } \ 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 3aa5c9338c..37faa65bea 100644 --- a/src/Libraries/SmartStore.Services/Common/IAddressService.cs +++ b/src/Libraries/SmartStore.Services/Common/IAddressService.cs @@ -1,5 +1,6 @@ +using System.Collections.Generic; using SmartStore.Core.Domain.Common; namespace SmartStore.Services.Common @@ -15,7 +16,6 @@ public partial interface IAddressService /// Address void DeleteAddress(Address address); - /// codehint: sm-add void DeleteAddress(int id); /// @@ -39,6 +39,13 @@ public partial interface IAddressService /// Address Address GetAddressById(int addressId); + /// + /// Gets addresses by address identifiers + /// + /// Address identifiers + /// Addresses + IList
GetAddressByIds(int[] addressIds); + /// /// Inserts an address /// diff --git a/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs b/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs index 4aabb70b7b..c584c4ad4c 100644 --- a/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Common; @@ -40,9 +41,17 @@ public partial interface IGenericAttributeService ///
/// Entity identifier /// Key group - /// Get attributes + /// Generic attributes IList GetAttributesForEntity(int entityId, string keyGroup); + /// + /// Get attributes + /// + /// Entity identifiers + /// Key group + /// Generic attributes + Multimap GetAttributesForEntity(int[] entityIds, string keyGroup); + /// /// Get queryable attributes /// @@ -60,5 +69,16 @@ public partial interface IGenericAttributeService /// 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); - } + + /// + /// Save attribute value + /// + /// Property type + /// Entity identifier + /// The key + /// The key group + /// 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); + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Common/IUserAgent.cs b/src/Libraries/SmartStore.Services/Common/IUserAgent.cs new file mode 100644 index 0000000000..a232323b07 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Common/IUserAgent.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Services.Common +{ + + public interface IUserAgent + { + string RawValue { get; set; } + + UserAgentInfo UserAgent { get; } + DeviceInfo Device { get; } + OSInfo OS { get; } + + bool IsBot { get; } + bool IsMobileDevice { get; } + bool IsTablet { get; } + bool IsPdfConverter { get; } + } + + public sealed class DeviceInfo + { + public DeviceInfo(string family, bool isBot) + { + this.Family = family; + this.IsBot = isBot; + } + public override string ToString() + { + return this.Family; + } + public string Family { get; private set; } + public bool IsBot { get; private set; } + } + + public sealed class OSInfo + { + public OSInfo(string family, string major, string minor, string patch, string patchMinor) + { + this.Family = family; + this.Major = major; + this.Minor = minor; + this.Patch = patch; + this.PatchMinor = patchMinor; + } + public override string ToString() + { + var str = VersionString.Format(Major, Minor, Patch, PatchMinor); + return (this.Family + (!string.IsNullOrEmpty(str) ? (" " + str) : null)); + } + public string Family { get; private set; } + public string Major { get; private set; } + public string Minor { get; private set; } + public string Patch { get; private set; } + public string PatchMinor { get; private set; } + + private static string FormatVersionString(params string[] parts) + { + return string.Join(".", (from v in parts + where !string.IsNullOrEmpty(v) + select v).ToArray()); + } + } + + public sealed class UserAgentInfo + { + private static readonly HashSet s_Bots = new HashSet(StringComparer.InvariantCultureIgnoreCase) + { + "BingPreview" + }; + + private bool? _isBot; + + public UserAgentInfo(string family, string major, string minor, string patch) + { + this.Family = family; + this.Major = major; + this.Minor = minor; + this.Patch = patch; + } + public override string ToString() + { + var str = VersionString.Format(Major, Minor, Patch); + return (this.Family + (!string.IsNullOrEmpty(str) ? (" " + str) : null)); + } + public string Family { get; private set; } + public string Major { get; private set; } + public string Minor { get; private set; } + public string Patch { get; private set; } + public bool IsBot + { + get + { + if (!_isBot.HasValue) + { + _isBot = s_Bots.Contains(Family); + } + return _isBot.Value; + } + } + } + + internal static class VersionString + { + public static string Format(params string[] parts) + { + return string.Join(".", (from v in parts + where !string.IsNullOrEmpty(v) + select v).ToArray()); + } + } + + +} diff --git a/src/Libraries/SmartStore.Services/Common/KeepAliveTask.cs b/src/Libraries/SmartStore.Services/Common/KeepAliveTask.cs deleted file mode 100644 index cac76727e4..0000000000 --- a/src/Libraries/SmartStore.Services/Common/KeepAliveTask.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Net; -using SmartStore.Core; -using SmartStore.Services.Tasks; - -namespace SmartStore.Services.Common -{ - /// - /// Represents a task for keeping the site alive - /// - public partial class KeepAliveTask : ITask - { - private readonly IStoreContext _storeContext; - - public KeepAliveTask(IStoreContext storeContext) - { - this._storeContext = storeContext; - } - - public void Execute(TaskExecutionContext ctx) - { - var storeUrl = _storeContext.CurrentStore.Url.TrimEnd('\\').EnsureEndsWith("/"); - string url = storeUrl + "keepalive/index"; - - try - { - using (var wc = new WebClient()) - { - //wc.Headers.Add("SmartStore.NET"); // makes problems - if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) - { - url = "http://" + url; - } - wc.DownloadString(url); - } - } - catch (WebException ex) - { - if (ex.Status == WebExceptionStatus.ProtocolError && ex.Response != null) - { - var resp = (HttpWebResponse)ex.Response; - if (resp.StatusCode == HttpStatusCode.NotFound) // HTTP 404 - { - // the page was not found (as it can be expected with some webservers) - return; - } - } - // throw any other exception - this should not occur - throw; - } - } - } -} diff --git a/src/Libraries/SmartStore.Services/Common/MobileDeviceHelper.cs b/src/Libraries/SmartStore.Services/Common/MobileDeviceHelper.cs index ab1736189f..98576528cc 100644 --- a/src/Libraries/SmartStore.Services/Common/MobileDeviceHelper.cs +++ b/src/Libraries/SmartStore.Services/Common/MobileDeviceHelper.cs @@ -15,7 +15,7 @@ public partial class MobileDeviceHelper : IMobileDeviceHelper private readonly ThemeSettings _themeSettings; private readonly IWorkContext _workContext; private readonly IStoreContext _storeContext; - private readonly HttpContextBase _httpConttext; + private readonly IUserAgent _userAgent; #endregion @@ -26,13 +26,16 @@ public partial class MobileDeviceHelper : IMobileDeviceHelper ///
/// Work context /// Store context - public MobileDeviceHelper(ThemeSettings themeSettings, IWorkContext workContext, - IStoreContext storeContext, HttpContextBase httpContext) + public MobileDeviceHelper( + ThemeSettings themeSettings, + IWorkContext workContext, + IStoreContext storeContext, + IUserAgent userAgent) { this._themeSettings = themeSettings; this._workContext = workContext; this._storeContext = storeContext; - this._httpConttext = httpContext; + this._userAgent = userAgent; } #endregion @@ -47,20 +50,7 @@ public MobileDeviceHelper(ThemeSettings themeSettings, IWorkContext workContext, /// Result public virtual bool IsMobileDevice() { - if (_themeSettings.EmulateMobileDevice) - return true; - - //comment the code below if you want tablets to be recognized as mobile devices. - //SmartStore.NET uses the free edition of the 51degrees.mobi library for detecting browser mobile properties. - //by default this property (IsTablet) is always false. you will need the premium edition in order to get it supported. - bool isTablet = false; - if (bool.TryParse(_httpConttext.Request.Browser["IsTablet"], out isTablet) && isTablet) - return false; - - if (_httpConttext.Request.Browser.IsMobileDevice) - return true; - - return false; + return _themeSettings.EmulateMobileDevice || (_userAgent.IsMobileDevice && !_userAgent.IsTablet); } /// diff --git a/src/Libraries/SmartStore.Services/Common/TempFileCleanupTask.cs b/src/Libraries/SmartStore.Services/Common/TempFileCleanupTask.cs new file mode 100644 index 0000000000..a07dfe8cce --- /dev/null +++ b/src/Libraries/SmartStore.Services/Common/TempFileCleanupTask.cs @@ -0,0 +1,16 @@ +using SmartStore.Services.Tasks; +using SmartStore.Utilities; + +namespace SmartStore.Services.Common +{ + /// + /// Task to cleanup temporary files + /// + public partial class TempFileCleanupTask : ITask + { + public void Execute(TaskExecutionContext ctx) + { + FileSystemHelper.TempCleanup(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Common/UAParserUserAgent.cs b/src/Libraries/SmartStore.Services/Common/UAParserUserAgent.cs new file mode 100644 index 0000000000..a6b8e24305 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Common/UAParserUserAgent.cs @@ -0,0 +1,265 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Web; +using SmartStore.Utilities; +using uap = UAParser; + +namespace SmartStore.Services.Common +{ + + public class UAParserUserAgent : IUserAgent + { + private readonly static uap.Parser s_uap; + private static readonly Regex s_pdfConverterPattern = new Regex(@"wkhtmltopdf", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + # region Mobile UAs, OS & Devices + + private static readonly HashSet s_MobileOS = new HashSet(StringComparer.InvariantCultureIgnoreCase) + { + "Android", + "iOS", + "Windows Mobile", + "Windows Phone", + "Windows CE", + "Symbian OS", + "BlackBerry OS", + "BlackBerry Tablet OS", + "Firefox OS", + "Brew MP", + "webOS", + "Bada", + "Kindle", + "Maemo" + }; + + private static readonly HashSet s_MobileBrowsers = new HashSet(StringComparer.InvariantCultureIgnoreCase) + { + "Googlebot-Mobile", + "Baiduspider-mobile", + "Android", + "Firefox Mobile", + "Opera Mobile", + "Opera Mini", + "Mobile Safari", + "Amazon Silk", + "webOS Browser", + "MicroB", + "Ovi Browser", + "NetFront", + "NetFront NX", + "Chrome Mobile", + "Chrome Mobile iOS", + "UC Browser", + "Tizen Browser", + "Baidu Explorer", + "QQ Browser Mini", + "QQ Browser Mobile", + "IE Mobile", + "Polaris", + "ONE Browser", + "iBrowser Mini", + "Nokia Services (WAP) Browser", + "Nokia Browser", + "Nokia OSS Browser", + "BlackBerry WebKit", + "BlackBerry", "Palm", + "Palm Blazer", + "Palm Pre", + "Teleca Browser", + "SEMC-Browser", + "PlayStation Portable", + "Nokia", + "Maemo Browser", + "Obigo", + "Bolt", + "Iris", + "UP.Browser", + "Minimo", + "Bunjaloo", + "Jasmine", + "Dolfin", + "Polaris", + "Skyfire" + }; + + private static readonly HashSet s_MobileDevices = new HashSet(StringComparer.InvariantCultureIgnoreCase) + { + "BlackBerry", + "MI PAD", + "iPhone", + "iPad", + "iPod", + "Kindle", + "Kindle Fire", + "Nokia", + "Lumia", + "Palm", + "DoCoMo", + "HP TouchPad", + "Xoom", + "Motorola", + "Generic Feature Phone", + "Generic Smartphone" + }; + + #endregion + + private readonly HttpContextBase _httpContext; + + private string _rawValue; + private UserAgentInfo _userAgent; + private DeviceInfo _device; + private OSInfo _os; + + private bool? _isBot; + private bool? _isMobileDevice; + private bool? _isTablet; + private bool? _isPdfConverter; + + static UAParserUserAgent() + { + //s_uap = uap.Parser.GetDefault(); + s_uap = uap.Parser.FromYamlFile(CommonHelper.MapPath("~/App_Data/ua-parser.regexes.yaml")); + } + + public UAParserUserAgent(HttpContextBase httpContext) + { + this._httpContext = httpContext; + } + + public string RawValue + { + get + { + if (_rawValue == null) + { + if (_httpContext.Request != null) + { + _rawValue = _httpContext.Request.UserAgent.EmptyNull(); + } + else + { + _rawValue = ""; + } + } + + return _rawValue; + } + // for (unit) test purpose + set + { + _rawValue = value; + _userAgent = null; + _device = null; + _os = null; + _isBot = null; + _isMobileDevice = null; + _isTablet = null; + _isPdfConverter = null; + } + } + + public virtual UserAgentInfo UserAgent + { + get + { + if (_userAgent == null) + { + var tmp = s_uap.ParseUserAgent(this.RawValue); + _userAgent = new UserAgentInfo(tmp.Family, tmp.Major, tmp.Minor, tmp.Patch); + } + return _userAgent; + } + } + + public virtual DeviceInfo Device + { + get + { + if (_device == null) + { + var tmp = s_uap.ParseDevice(this.RawValue); + _device = new DeviceInfo(tmp.Family, tmp.IsSpider()); + } + return _device; + } + } + + public virtual OSInfo OS + { + get + { + if (_os == null) + { + var tmp = s_uap.ParseOS(this.RawValue); + _os = new OSInfo(tmp.Family, tmp.Major, tmp.Minor, tmp.Patch, tmp.PatchMinor); + } + return _os; + } + } + + public virtual bool IsBot + { + get + { + if (!_isBot.HasValue) + { + // empty useragent > bad bot! + _isBot = this.RawValue.IsEmpty() || _httpContext.Request.Browser.Crawler || this.Device.IsBot || this.UserAgent.IsBot; + } + return _isBot.Value; + } + } + + public virtual bool IsMobileDevice + { + get + { + if (!_isMobileDevice.HasValue) + { + _isMobileDevice = + s_MobileOS.Contains(this.OS.Family) || + s_MobileBrowsers.Contains(this.UserAgent.Family) || + s_MobileDevices.Contains(this.Device.Family); + } + return _isMobileDevice.Value; + } + } + + public virtual bool IsTablet + { + get + { + if (!_isTablet.HasValue) + { + _isTablet = + Regex.IsMatch(this.Device.Family, "iPad|Kindle Fire|Nexus 10|Xoom|Transformer|MI PAD|IdeaTab", RegexOptions.CultureInvariant) || + this.OS.Family == "BlackBerry Tablet OS"; + } + return _isTablet.Value; + } + } + + public virtual bool IsPdfConverter + { + get + { + if (!_isPdfConverter.HasValue) + { + _isPdfConverter = s_pdfConverterPattern.IsMatch(this.RawValue); + } + return _isPdfConverter.Value; + } + } + } + + internal static class DeviceExtensions + { + internal static bool IsSpider(this UAParser.Device device) + { + return device.Family.Equals("Spider", StringComparison.InvariantCultureIgnoreCase); + } + } + +} diff --git a/src/Libraries/SmartStore.Services/CommonServices.cs b/src/Libraries/SmartStore.Services/CommonServices.cs index 86742ade49..0d6b989990 100644 --- a/src/Libraries/SmartStore.Services/CommonServices.cs +++ b/src/Libraries/SmartStore.Services/CommonServices.cs @@ -10,11 +10,14 @@ using SmartStore.Services.Security; using SmartStore.Services.Configuration; using SmartStore.Services.Stores; +using Autofac; +using SmartStore.Services.Helpers; namespace SmartStore.Services { public class CommonServices : ICommonServices { + private readonly IComponentContext _container; private readonly Lazy _cache; private readonly Lazy _dbContext; private readonly Lazy _storeContext; @@ -27,9 +30,11 @@ public class CommonServices : ICommonServices private readonly Lazy _permissions; private readonly Lazy _settings; private readonly Lazy _storeService; - + private readonly Lazy _dateTimeHelper; + public CommonServices( - Func> cache, + IComponentContext container, + Func> cache, Lazy dbContext, Lazy storeContext, Lazy webHelper, @@ -40,8 +45,10 @@ public CommonServices( Lazy notifier, Lazy permissions, Lazy settings, - Lazy storeService) + Lazy storeService, + Lazy dateTimeHelper) { + this._container = container; this._cache = cache("static"); this._dbContext = dbContext; this._storeContext = storeContext; @@ -54,8 +61,17 @@ public CommonServices( this._permissions = permissions; this._settings = settings; this._storeService = storeService; + this._dateTimeHelper = dateTimeHelper; + } + + public IComponentContext Container + { + get + { + return _container; + } } - + public ICacheManager Cache { get @@ -152,5 +168,13 @@ public IStoreService StoreService return _storeService.Value; } } + + public IDateTimeHelper DateTimeHelper + { + get + { + return _dateTimeHelper.Value; + } + } } } diff --git a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs index c0c153badb..aa688a4e96 100644 --- a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs +++ b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs @@ -2,19 +2,15 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; -using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Configuration; using SmartStore.Core.Data; using SmartStore.Core.Domain.Configuration; -using SmartStore.Core.Infrastructure; using SmartStore.Core.Events; -using Fasterflect; using System.Linq.Expressions; using System.Reflection; -using SmartStore.Core.Plugins; -using System.ComponentModel; -using SmartStore.Utilities; +using SmartStore.ComponentModel; +using System.Collections; namespace SmartStore.Services.Configuration { @@ -81,7 +77,7 @@ protected virtual IDictionary> GetAllSettingsCa 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 settingName = s.Name.ToLowerInvariant(); @@ -151,7 +147,6 @@ public virtual void UpdateSetting(Setting setting, bool clearCache = true) _eventPublisher.EntityUpdated(setting); } - /// codehint: sm-add private T LoadSettingsJson(int storeId = 0) { Type t = typeof(T); @@ -164,6 +159,7 @@ private T LoadSettingsJson(int storeId = 0) { JsonConvert.PopulateObject(rawSetting, settings); } + return settings; } @@ -240,6 +236,7 @@ public virtual Setting GetSettingById(int settingId) if (setting != null) return setting.Value.Convert(); } + return defaultValue; } @@ -304,52 +301,66 @@ public virtual bool SettingExists(T settings, var settings = Activator.CreateInstance(); - foreach (var prop in typeof(T).GetProperties()) + foreach (var fastProp in FastProperty.GetProperties(typeof(T)).Values) { + var prop = fastProp.Property; + // get properties we can read and write to - if (!prop.CanRead || !prop.CanWrite) + if (!prop.CanWrite) continue; var key = typeof(T).Name + "." + prop.Name; - //load by store + // load by store string setting = GetSettingByKey(key, storeId: storeId, loadSharedValueIfNotFound: true); - if (setting == null) - { - 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) + if (setting == null) + { + if (fastProp.IsSequenceType) + { + if ((fastProp.GetValue(settings) as IEnumerable) != null) { - prop.SetValue(settings, list, 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; + } - continue; - } + } - var converter = CommonHelper.GetTypeConverter(prop.PropertyType); + var converter = TypeConverterFactory.GetConverter(prop.PropertyType); if (converter == null || !converter.CanConvertFrom(typeof(string))) continue; - if (!converter.IsValid(setting)) - continue; - - object value = converter.ConvertFromInvariantString(setting); + object value = converter.ConvertFrom(setting); //set property - prop.SetValue(settings, value, null); + fastProp.SetValue(settings, value); } return settings; @@ -368,7 +379,7 @@ public virtual void SetSetting(string key, T value, int storeId = 0, bool cle Guard.ArgumentNotEmpty(() => key); key = key.Trim().ToLowerInvariant(); - string valueStr = CommonHelper.GetTypeConverter(typeof(T)).ConvertToInvariantString(value); + var str = value.Convert(); var allSettings = GetAllSettingsCached(); var settingForCaching = allSettings.ContainsKey(key) ? @@ -378,16 +389,16 @@ public virtual void SetSetting(string key, T value, int storeId = 0, bool cle { //update var setting = GetSettingById(settingForCaching.Id); - setting.Value = valueStr; + setting.Value = str; UpdateSetting(setting, clearCache); } else { //insert - var setting = new Setting() + var setting = new Setting { Name = key, - Value = valueStr, + Value = str, StoreId = storeId }; InsertSetting(setting, clearCache); @@ -411,18 +422,19 @@ public virtual void SetSetting(string key, T value, int storeId = 0, bool cle /* 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 typeof(T).GetProperties()) + foreach (var prop in FastProperty.GetProperties(typeof(T)).Values) { // get properties we can read and write to - if (!prop.CanRead || !prop.CanWrite) + if (!prop.IsPublicSettable) continue; - if (!CommonHelper.GetTypeConverter(prop.PropertyType).CanConvertFrom(typeof(string))) + var converter = TypeConverterFactory.GetConverter(prop.Property.PropertyType); + if (converter == null || !converter.CanConvertFrom(typeof(string))) continue; string key = typeof(T).Name + "." + prop.Name; - //Duck typing is not supported in C#. That's why we're using dynamic type - dynamic value = settings.TryGetPropertyValue(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); } @@ -460,13 +472,13 @@ public virtual void SaveSetting(T settings, } string key = typeof(T).Name + "." + propInfo.Name; - //Duck typing is not supported in C#. That's why we're using dynamic type - dynamic value = settings.TryGetPropertyValue(propInfo.Name); + // 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); SetSetting(key, value ?? "", storeId, false); } - /// codehint: sm-add public virtual void UpdateSetting(T settings, Expression> keySelector, bool overrideForStore, int storeId = 0) where T : ISettings, new() { if (overrideForStore || storeId == 0) diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs b/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs index 319a99c5fb..afc1acdbe0 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs @@ -21,8 +21,7 @@ public static class CustomerExtentions /// 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) + public static bool IsInCustomerRole(this Customer customer, string customerRoleSystemName, bool onlyActiveCustomerRoles = true) { if (customer == null) throw new ArgumentNullException("customer"); @@ -44,40 +43,54 @@ public static bool IsInCustomerRole(this Customer customer, /// Result public static bool IsBackgroundTaskAccount(this Customer customer) { - if (customer == null) - throw new ArgumentNullException("customer"); + Guard.ArgumentNotNull(() => customer); - if (!customer.IsSystemAccount || String.IsNullOrEmpty(customer.SystemName)) - return false; + 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 + /// Gets a value indicating whether customer is a search engine /// /// Customer /// Result public static bool IsSearchEngineAccount(this Customer customer) { - if (customer == null) - throw new ArgumentNullException("customer"); + Guard.ArgumentNotNull(() => customer); - if (!customer.IsSystemAccount || String.IsNullOrEmpty(customer.SystemName)) - return false; + 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 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) + /// + /// 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); } @@ -274,14 +287,15 @@ public static int CountProductsInCart(this Customer customer, ShoppingCartType c return count; } - public static List GetCartItems(this Customer customer, ShoppingCartType cartType, int? storeId = null, bool orderById = false) + + public static List GetCartItems(this Customer customer, ShoppingCartType cartType, int? storeId = null) { var rawItems = customer.ShoppingCartItems.Filter(cartType, storeId); - if (orderById) - rawItems = rawItems.OrderByDescending(x => x.Id); - - var organizedItems = rawItems.ToList().Organize(); + var organizedItems = rawItems + .OrderByDescending(x => x.Id) + .ToList() + .Organize(); return organizedItems.ToList(); } diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerRegisteredEvent.cs b/src/Libraries/SmartStore.Services/Customers/CustomerRegisteredEvent.cs new file mode 100644 index 0000000000..76bc89b1de --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/CustomerRegisteredEvent.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Services.Customers +{ + /// + /// An event message, which gets published after customer was registered + /// + public class CustomerRegisteredEvent + { + public Customer Customer + { + get; + set; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs index 2ee2141309..49fb4587aa 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs @@ -2,26 +2,27 @@ using System.Linq; using SmartStore.Core; using SmartStore.Core.Domain.Customers; -using SmartStore.Services.Localization; +using SmartStore.Core.Events; +using SmartStore.Core.Localization; using SmartStore.Services.Messages; using SmartStore.Services.Security; namespace SmartStore.Services.Customers { - /// - /// Customer registration service - /// - public partial class CustomerRegistrationService : ICustomerRegistrationService + /// + /// Customer registration service + /// + public partial class CustomerRegistrationService : ICustomerRegistrationService { #region Fields private readonly ICustomerService _customerService; private readonly IEncryptionService _encryptionService; private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; - private readonly ILocalizationService _localizationService; private readonly RewardPointsSettings _rewardPointsSettings; private readonly CustomerSettings _customerSettings; private readonly IStoreContext _storeContext; + private readonly IEventPublisher _eventPublisher; #endregion @@ -33,30 +34,33 @@ public partial class CustomerRegistrationService : ICustomerRegistrationService public CustomerRegistrationService(ICustomerService customerService, IEncryptionService encryptionService, INewsLetterSubscriptionService newsLetterSubscriptionService, - ILocalizationService localizationService, RewardPointsSettings rewardPointsSettings, CustomerSettings customerSettings, - IStoreContext storeContext) + IStoreContext storeContext, IEventPublisher eventPublisher) { this._customerService = customerService; this._encryptionService = encryptionService; this._newsLetterSubscriptionService = newsLetterSubscriptionService; - this._localizationService = localizationService; this._rewardPointsSettings = rewardPointsSettings; this._customerSettings = customerSettings; this._storeContext = storeContext; - } + this._eventPublisher = eventPublisher; - #endregion + T = NullLocalizer.Instance; + } - #region Methods + #endregion - /// - /// Validate customer - /// - /// Username or email - /// Password - /// Result - public virtual bool ValidateCustomer(string usernameOrEmail, string password) + 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) @@ -109,48 +113,46 @@ public virtual bool ValidateCustomer(string usernameOrEmail, string password) /// Result public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationRequest request) { - if (request == null) - throw new ArgumentNullException("request"); - - if (request.Customer == null) - throw new ArgumentException("Can't load current customer"); + Guard.ArgumentNotNull(() => request); + Guard.ArgumentNotNull(() => request.Customer); var result = new CustomerRegistrationResult(); + if (request.Customer.IsSearchEngineAccount()) { - result.AddError("Search engine can't be registered"); + result.AddError(T("Account.Register.Errors.CannotRegisterSearchEngine")); return result; } if (request.Customer.IsBackgroundTaskAccount()) { - result.AddError("Background task account can't be registered"); + result.AddError(T("Account.Register.Errors.CannotRegisterTaskAccount")); return result; } if (request.Customer.IsRegistered()) { - result.AddError("Current customer is already registered"); + result.AddError(T("Account.Register.Errors.AlreadyRegistered")); return result; } if (String.IsNullOrEmpty(request.Email)) { - result.AddError(_localizationService.GetResource("Account.Register.Errors.EmailIsNotProvided")); + result.AddError(T("Account.Register.Errors.EmailIsNotProvided")); return result; } if (!request.Email.IsEmail()) { - result.AddError(_localizationService.GetResource("Common.WrongEmail")); + result.AddError(T("Common.WrongEmail")); return result; } if (String.IsNullOrWhiteSpace(request.Password)) { - result.AddError(_localizationService.GetResource("Account.Register.Errors.PasswordIsNotProvided")); + result.AddError(T("Account.Register.Errors.PasswordIsNotProvided")); return result; } if (_customerSettings.UsernamesEnabled) { if (String.IsNullOrEmpty(request.Username)) { - result.AddError(_localizationService.GetResource("Account.Register.Errors.UsernameIsNotProvided")); + result.AddError(T("Account.Register.Errors.UsernameIsNotProvided")); return result; } } @@ -158,14 +160,14 @@ public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationR //validate unique user if (_customerService.GetCustomerByEmail(request.Email) != null) { - result.AddError(_localizationService.GetResource("Account.Register.Errors.EmailAlreadyExists")); + result.AddError(T("Account.Register.Errors.EmailAlreadyExists")); return result; } if (_customerSettings.UsernamesEnabled) { if (_customerService.GetCustomerByUsername(request.Username) != null) { - result.AddError(_localizationService.GetResource("Account.Register.Errors.UsernameAlreadyExists")); + result.AddError(T("Account.Register.Errors.UsernameAlreadyExists")); return result; } } @@ -177,44 +179,52 @@ public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationR switch (request.PasswordFormat) { - case PasswordFormat.Clear: - { - request.Customer.Password = request.Password; - } - break; - case PasswordFormat.Encrypted: - { - request.Customer.Password = _encryptionService.EncryptText(request.Password); - } - break; - case PasswordFormat.Hashed: - { - string saltKey = _encryptionService.CreateSaltKey(5); - request.Customer.PasswordSalt = saltKey; - request.Customer.Password = _encryptionService.CreatePasswordHash(request.Password, saltKey, _customerSettings.HashedPasswordFormat); - } - break; - default: - break; + case PasswordFormat.Clear: + request.Customer.Password = request.Password; + break; + case PasswordFormat.Encrypted: + request.Customer.Password = _encryptionService.EncryptText(request.Password); + break; + case PasswordFormat.Hashed: + string saltKey = _encryptionService.CreateSaltKey(5); + request.Customer.PasswordSalt = saltKey; + request.Customer.Password = _encryptionService.CreatePasswordHash(request.Password, saltKey, _customerSettings.HashedPasswordFormat); + break; } request.Customer.Active = request.IsApproved; - - //add to 'Registered' role - var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); - if (registeredRole == null) - throw new SmartException("'Registered' role could not be loaded"); + + if (_customerSettings.RegisterCustomerRoleId != 0) + { + var customerRole = _customerService.GetCustomerRoleById(_customerSettings.RegisterCustomerRoleId); + request.Customer.CustomerRoles.Add(customerRole); + } + + //add to 'Registered' role + var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); + if (registeredRole == null) + { + throw new SmartException(T("Admin.Customers.CustomerRoles.CannotFoundRole", "Registered")); + } + request.Customer.CustomerRoles.Add(registeredRole); + //remove from 'Guests' role var guestRole = request.Customer.CustomerRoles.FirstOrDefault(cr => cr.SystemName == SystemCustomerRoleNames.Guests); - if (guestRole != null) - request.Customer.CustomerRoles.Remove(guestRole); + if (guestRole != null) + { + request.Customer.CustomerRoles.Remove(guestRole); + } - //Add reward points for customer registration (if enabled) - if (_rewardPointsSettings.Enabled && _rewardPointsSettings.PointsForRegistration > 0) - request.Customer.AddRewardPointsHistoryEntry(_rewardPointsSettings.PointsForRegistration, _localizationService.GetResource("RewardPoints.Message.RegisteredAsCustomer")); + //Add reward points for customer registration (if enabled) + if (_rewardPointsSettings.Enabled && _rewardPointsSettings.PointsForRegistration > 0) + { + request.Customer.AddRewardPointsHistoryEntry(_rewardPointsSettings.PointsForRegistration, T("RewardPoints.Message.RegisteredAsCustomer")); + } _customerService.UpdateCustomer(request.Customer); + _eventPublisher.Publish(new CustomerRegisteredEvent { Customer = request.Customer }); + return result; } @@ -231,19 +241,19 @@ public virtual PasswordChangeResult ChangePassword(ChangePasswordRequest request var result = new PasswordChangeResult(); if (String.IsNullOrWhiteSpace(request.Email)) { - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.EmailIsNotProvided")); + result.AddError(T("Account.ChangePassword.Errors.EmailIsNotProvided")); return result; } if (String.IsNullOrWhiteSpace(request.NewPassword)) { - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.PasswordIsNotProvided")); + result.AddError(T("Account.ChangePassword.Errors.PasswordIsNotProvided")); return result; } var customer = _customerService.GetCustomerByEmail(request.Email); if (customer == null) { - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.EmailNotFound")); + result.AddError(T("Account.ChangePassword.Errors.EmailNotFound")); return result; } @@ -268,7 +278,7 @@ public virtual PasswordChangeResult ChangePassword(ChangePasswordRequest request bool oldPasswordIsValid = oldPwd == customer.Password; if (!oldPasswordIsValid) - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.OldPasswordDoesntMatch")); + result.AddError(T("Account.ChangePassword.Errors.OldPasswordDoesntMatch")); if (oldPasswordIsValid) requestIsValid = true; @@ -323,14 +333,14 @@ public virtual void SetEmail(Customer customer, string newEmail) string oldEmail = customer.Email; if (!newEmail.IsEmail()) - throw new SmartException(_localizationService.GetResource("Account.EmailUsernameErrors.NewEmailIsNotValid")); + throw new SmartException(T("Account.EmailUsernameErrors.NewEmailIsNotValid")); if (newEmail.Length > 100) - throw new SmartException(_localizationService.GetResource("Account.EmailUsernameErrors.EmailTooLong")); + throw new SmartException(T("Account.EmailUsernameErrors.EmailTooLong")); var customer2 = _customerService.GetCustomerByEmail(newEmail); if (customer2 != null && customer.Id != customer2.Id) - throw new SmartException(_localizationService.GetResource("Account.EmailUsernameErrors.EmailAlreadyExists")); + throw new SmartException(T("Account.EmailUsernameErrors.EmailAlreadyExists")); customer.Email = newEmail; _customerService.UpdateCustomer(customer); @@ -366,11 +376,11 @@ public virtual void SetUsername(Customer customer, string newUsername) newUsername = newUsername.Trim(); if (newUsername.Length > 100) - throw new SmartException(_localizationService.GetResource("Account.EmailUsernameErrors.UsernameTooLong")); + throw new SmartException(T("Account.EmailUsernameErrors.UsernameTooLong")); var user2 = _customerService.GetCustomerByUsername(newUsername); if (user2 != null && customer.Id != user2.Id) - throw new SmartException(_localizationService.GetResource("Account.EmailUsernameErrors.UsernameAlreadyExists")); + throw new SmartException(T("Account.EmailUsernameErrors.UsernameAlreadyExists")); customer.Username = newUsername; _customerService.UpdateCustomer(customer); diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs index ecff924925..d48fe10d6c 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs @@ -4,35 +4,36 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; +using System.Web; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; -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.News; using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Polls; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Events; using SmartStore.Core.Localization; +using SmartStore.Core.Fakes; using SmartStore.Services.Common; using SmartStore.Services.Localization; namespace SmartStore.Services.Customers { - /// - /// Customer service - /// - public partial class CustomerService : ICustomerService + /// + /// Customer service + /// + public partial class CustomerService : ICustomerService { - #region Constants + #region Constants - private const string CUSTOMERROLES_ALL_KEY = "SmartStore.customerrole.all-{0}"; + 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 @@ -40,39 +41,40 @@ public partial class CustomerService : ICustomerService private readonly IRepository _customerRepository; private readonly IRepository _customerRoleRepository; private readonly IRepository _gaRepository; + private readonly IRepository _rewardPointsHistoryRepository; private readonly IGenericAttributeService _genericAttributeService; - private readonly ICacheManager _cacheManager; - private readonly IEventPublisher _eventPublisher; + private readonly ICommonServices _services; + private readonly ICacheManager _cache; private readonly RewardPointsSettings _rewardPointsSettings; + private readonly HttpContextBase _httpContext; + private readonly IUserAgent _userAgent; - #endregion + #endregion - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Customer repository - /// Customer role repository - /// Generic attribute repository - /// Generic attribute service - /// Event published - public CustomerService(ICacheManager cacheManager, + #region Ctor + + public CustomerService( IRepository customerRepository, IRepository customerRoleRepository, IRepository gaRepository, + IRepository rewardPointsHistoryRepository, IGenericAttributeService genericAttributeService, - IEventPublisher eventPublisher, - RewardPointsSettings rewardPointsSettings) + ICommonServices services, + ICacheManager cache, + RewardPointsSettings rewardPointsSettings, + HttpContextBase httpContext, + IUserAgent userAgent) { - this._cacheManager = cacheManager; this._customerRepository = customerRepository; this._customerRoleRepository = customerRoleRepository; this._gaRepository = gaRepository; + this._rewardPointsHistoryRepository = rewardPointsHistoryRepository; this._genericAttributeService = genericAttributeService; - this._eventPublisher = eventPublisher; + this._services = services; + this._cache = cache; this._rewardPointsSettings = rewardPointsSettings; + this._httpContext = httpContext; + this._userAgent = userAgent; T = NullLocalizer.Instance; } @@ -89,26 +91,6 @@ public CustomerService(ICacheManager cacheManager, #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 public virtual IPagedList GetAllCustomers(DateTime? registrationFrom, DateTime? registrationTo, int[] customerRoleIds, string email, string username, string firstName, string lastName, int dayOfBirth, int monthOfBirth, @@ -241,13 +223,6 @@ public virtual IPagedList GetAllCustomers(DateTime? registrationFrom, return customers; } - /// - /// Gets all customers by affiliate identifier - /// - /// Affiliate identifier - /// Page index - /// Page size - /// Customers public virtual IPagedList GetAllCustomers(int affiliateId, int pageIndex, int pageSize) { var query = _customerRepository.Table; @@ -259,11 +234,6 @@ public virtual IPagedList GetAllCustomers(int affiliateId, int pageInd return customers; } - /// - /// Gets all customers by customer format (including deleted ones) - /// - /// Password format - /// Customers public virtual IList GetAllCustomersByPasswordFormat(PasswordFormat passwordFormat) { int passwordFormatId = (int)passwordFormat; @@ -275,14 +245,6 @@ public virtual IList GetAllCustomersByPasswordFormat(PasswordFormat pa return customers; } - /// - /// 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 public virtual IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, int[] customerRoleIds, int pageIndex, int pageSize) { @@ -297,10 +259,6 @@ public virtual IPagedList GetOnlineCustomers(DateTime lastActivityFrom return customers; } - /// - /// Delete a customer - /// - /// Customer public virtual void DeleteCustomer(Customer customer) { if (customer == null) @@ -313,11 +271,6 @@ public virtual void DeleteCustomer(Customer customer) UpdateCustomer(customer); } - /// - /// Gets a customer - /// - /// Customer identifier - /// A customer public virtual Customer GetCustomerById(int customerId) { if (customerId == 0) @@ -327,11 +280,6 @@ public virtual Customer GetCustomerById(int customerId) return customer; } - /// - /// Get customers by identifiers - /// - /// Customer identifiers - /// Customers public virtual IList GetCustomersByIds(int[] customerIds) { if (customerIds == null || customerIds.Length == 0) @@ -352,11 +300,11 @@ where customerIds.Contains(c.Id) return sortedCustomers; } - /// - /// Gets a customer by GUID - /// - /// Customer GUID - /// A customer + public virtual IList GetSystemAccountCustomers() + { + return _customerRepository.Table.Where(x => x.IsSystemAccount).ToList(); + } + public virtual Customer GetCustomerByGuid(Guid customerGuid) { if (customerGuid == Guid.Empty) @@ -370,11 +318,6 @@ orderby c.Id return customer; } - /// - /// Get customer by email - /// - /// Email - /// Customer public virtual Customer GetCustomerByEmail(string email) { if (string.IsNullOrWhiteSpace(email)) @@ -388,29 +331,20 @@ orderby c.Id return customer; } - /// - /// Get customer by system name - /// - /// System name - /// Customer public virtual Customer GetCustomerBySystemName(string systemName) { if (string.IsNullOrWhiteSpace(systemName)) return null; - var query = from c in _customerRepository.Table - orderby c.Id - where c.SystemName == systemName - select c; - var customer = query.FirstOrDefault(); - return customer; + var query = from c in _customerRepository.Table + orderby c.Id + where c.SystemName == systemName + select c; + var customer = query.FirstOrDefault(); + + return customer; } - /// - /// Get customer by username - /// - /// Username - /// Customer public virtual Customer GetCustomerByUsername(string username) { if (string.IsNullOrWhiteSpace(username)) @@ -425,35 +359,63 @@ orderby c.Id return customer; } - /// - /// Insert a guest customer - /// - /// Customer - public virtual Customer InsertGuestCustomer() + public virtual Customer InsertGuestCustomer(Guid? customerGuid = null) { - var customer = new Customer() + 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); - _customerRepository.Insert(customer); + _customerRepository.Insert(customer); + + var clientIdent = _services.WebHelper.GetClientIdent(); + if (clientIdent.HasValue()) + { + //_genericAttributeService.SaveAttribute(customer, "ClientIdent", clientIdent); + } return customer; } + + public virtual Customer FindGuestCustomerByClientIdent(string clientIdent = null, int maxAgeSeconds = 60) + { + if (_httpContext.IsFakeContext() || _userAgent.IsBot || _userAgent.IsPdfConverter) + { + return null; + } + + clientIdent = clientIdent.NullEmpty() ?? _services.WebHelper.GetClientIdent(); + if (clientIdent.IsEmpty()) + { + return null; + } + + var dateFrom = DateTime.UtcNow.AddSeconds(maxAgeSeconds * -1); + + var 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; + + return query.FirstOrDefault(); + } - /// - /// Insert a customer - /// - /// Customer public virtual void InsertCustomer(Customer customer) { if (customer == null) @@ -461,14 +423,9 @@ public virtual void InsertCustomer(Customer customer) _customerRepository.Insert(customer); - //event notification - _eventPublisher.EntityInserted(customer); + _services.EventPublisher.EntityInserted(customer); } - /// - /// Updates the customer - /// - /// Customer public virtual void UpdateCustomer(Customer customer) { if (customer == null) @@ -476,20 +433,9 @@ public virtual void UpdateCustomer(Customer customer) _customerRepository.Update(customer); - //event notification - _eventPublisher.EntityUpdated(customer); + _services.EventPublisher.EntityUpdated(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 public virtual void ResetCheckoutData(Customer customer, int storeId, bool clearCouponCodes = false, bool clearCheckoutAttributes = false, bool clearRewardPoints = true, bool clearShippingMethod = true, @@ -533,18 +479,11 @@ public virtual void ResetCheckoutData(Customer customer, int storeId, UpdateCustomer(customer); } - /// - /// 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 public virtual int DeleteGuestCustomers(DateTime? registrationFrom, DateTime? registrationTo, bool onlyWithoutShoppingCart, int maxItemsToDelete = 5000) { var ctx = _customerRepository.Context; - using (var scope = new DbContextScope(ctx: ctx, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true)) + using (var scope = new DbContextScope(ctx: ctx, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true, autoCommit: false)) { var guestRole = GetCustomerRoleBySystemName(SystemCustomerRoleNames.Guests); if (guestRole == null) @@ -556,6 +495,7 @@ public virtual int DeleteGuestCustomers(DateTime? registrationFrom, DateTime? re query = query.Where(c => registrationFrom.Value <= c.CreatedOnUtc); if (registrationTo.HasValue) query = query.Where(c => registrationTo.Value >= c.CreatedOnUtc); + query = query.Where(c => c.CustomerRoles.Select(cr => cr.Id).Contains(guestRole.Id)); if (onlyWithoutShoppingCart) @@ -573,21 +513,6 @@ public virtual int DeleteGuestCustomers(DateTime? registrationFrom, DateTime? re // no forum topics query = JoinWith(query, x => x.CustomerId); - // no blog comments - query = JoinWith(query, x => x.CustomerId); - - // no news comments - query = JoinWith(query, x => x.CustomerId); - - // no product reviews - query = JoinWith(query, x => x.CustomerId); - - // no product review helpfulness - query = JoinWith(query, x => x.CustomerId); - - // no poll voting - query = JoinWith(query, x => x.CustomerId); - //don't delete system accounts query = query.Where(c => !c.IsSystemAccount); @@ -600,12 +525,7 @@ orderby cGroup.Key query = query.OrderBy(c => c.Id); var customers = query.Take(maxItemsToDelete).ToList(); - - var crAutoCommit = _customerRepository.AutoCommitEnabled; - var gaAutoCommit = _gaRepository.AutoCommitEnabled; - _customerRepository.AutoCommitEnabled = false; - _gaRepository.AutoCommitEnabled = false; - + int numberOfDeletedCustomers = 0; foreach (var c in customers) { @@ -618,10 +538,7 @@ orderby cGroup.Key select ga; var attributes = gaQuery.ToList(); - foreach (var attribute in attributes) - { - _gaRepository.Delete(attribute); - } + _gaRepository.DeleteRange(attributes); // delete customer _customerRepository.Delete(c); @@ -649,9 +566,6 @@ orderby cGroup.Key // save the rest scope.Commit(); - _customerRepository.AutoCommitEnabled = crAutoCommit; - _gaRepository.AutoCommitEnabled = gaAutoCommit; - return numberOfDeletedCustomers; } } @@ -688,10 +602,6 @@ from inner in c_inner.DefaultIfEmpty() #region Customer roles - /// - /// Delete a customer role - /// - /// Customer role public virtual void DeleteCustomerRole(CustomerRole customerRole) { if (customerRole == null) @@ -702,17 +612,11 @@ public virtual void DeleteCustomerRole(CustomerRole customerRole) _customerRoleRepository.Delete(customerRole); - _cacheManager.RemoveByPattern(CUSTOMERROLES_PATTERN_KEY); + _cache.RemoveByPattern(CUSTOMERROLES_PATTERN_KEY); - //event notification - _eventPublisher.EntityDeleted(customerRole); + _services.EventPublisher.EntityDeleted(customerRole); } - /// - /// Gets a customer role - /// - /// Customer role identifier - /// Customer role public virtual CustomerRole GetCustomerRoleById(int customerRoleId) { if (customerRoleId == 0) @@ -721,18 +625,13 @@ public virtual CustomerRole GetCustomerRoleById(int customerRoleId) return _customerRoleRepository.GetById(customerRoleId); } - /// - /// Gets a customer role - /// - /// Customer role system name - /// Customer role public virtual CustomerRole GetCustomerRoleBySystemName(string systemName) { if (String.IsNullOrWhiteSpace(systemName)) return null; string key = string.Format(CUSTOMERROLES_BY_SYSTEMNAME_KEY, systemName); - return _cacheManager.Get(key, () => + return _cache.Get(key, () => { var query = from cr in _customerRoleRepository.Table orderby cr.Id @@ -743,15 +642,10 @@ orderby cr.Id }); } - /// - /// Gets all customer roles - /// - /// A value indicating whether to show hidden records - /// Customer role collection public virtual IList GetAllCustomerRoles(bool showHidden = false) { string key = string.Format(CUSTOMERROLES_ALL_KEY, showHidden); - return _cacheManager.Get(key, () => + return _cache.Get(key, () => { var query = from cr in _customerRoleRepository.Table orderby cr.Name @@ -762,10 +656,6 @@ orderby cr.Name }); } - /// - /// Inserts a customer role - /// - /// Customer role public virtual void InsertCustomerRole(CustomerRole customerRole) { if (customerRole == null) @@ -773,16 +663,11 @@ public virtual void InsertCustomerRole(CustomerRole customerRole) _customerRoleRepository.Insert(customerRole); - _cacheManager.RemoveByPattern(CUSTOMERROLES_PATTERN_KEY); + _cache.RemoveByPattern(CUSTOMERROLES_PATTERN_KEY); - //event notification - _eventPublisher.EntityInserted(customerRole); + _services.EventPublisher.EntityInserted(customerRole); } - /// - /// Updates the customer role - /// - /// Customer role public virtual void UpdateCustomerRole(CustomerRole customerRole) { if (customerRole == null) @@ -790,22 +675,15 @@ public virtual void UpdateCustomerRole(CustomerRole customerRole) _customerRoleRepository.Update(customerRole); - _cacheManager.RemoveByPattern(CUSTOMERROLES_PATTERN_KEY); + _cache.RemoveByPattern(CUSTOMERROLES_PATTERN_KEY); - //event notification - _eventPublisher.EntityUpdated(customerRole); + _services.EventPublisher.EntityUpdated(customerRole); } #endregion #region Reward points - /// - /// Add or remove reward points for a product review - /// - /// The customer - /// The product - /// Whether to add or remove points public virtual void RewardPointsForProductReview(Customer customer, Product product, bool add) { if (_rewardPointsSettings.Enabled && _rewardPointsSettings.PointsForProductReview > 0) @@ -818,6 +696,25 @@ public virtual void RewardPointsForProductReview(Customer customer, Product prod } } + public virtual Multimap GetRewardPointsHistoriesByCustomerIds(int[] customerIds) + { + Guard.ArgumentNotNull(() => customerIds); + + var query = + from x in _rewardPointsHistoryRepository.TableUntracked + where customerIds.Contains(x.CustomerId) + select x; + + var map = query + .OrderBy(x => x.CustomerId) + .ThenByDescending(x => x.CreatedOnUtc) + .ThenByDescending(x => x.Id) + .ToList() + .ToMultimap(x => x.CustomerId, x => x); + + return map; + } + #endregion Reward points #endregion diff --git a/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs b/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs index d5d3ce672c..57a2c6f8c9 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 SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; @@ -87,12 +88,18 @@ IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, /// Customers IList GetCustomersByIds(int[] customerIds); - /// - /// Gets a customer by GUID - /// - /// Customer GUID - /// A customer - Customer GetCustomerByGuid(Guid customerGuid); + /// + /// Get system account customers + /// + /// System account customers + IList GetSystemAccountCustomers(); + + /// + /// Gets a customer by GUID + /// + /// Customer GUID + /// A customer + Customer GetCustomerByGuid(Guid customerGuid); /// /// Get customer by email @@ -118,14 +125,26 @@ IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, /// /// Insert a guest customer /// + /// The customer GUID. Pass null to create a random one. /// Customer - Customer InsertGuestCustomer(); + 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 @@ -212,6 +231,13 @@ void ResetCheckoutData(Customer customer, int storeId, /// Whether to add or remove points void RewardPointsForProductReview(Customer customer, Product product, bool add); + /// + /// Gets reward points histories + /// + /// Customer identifiers + /// Reward points histories + Multimap GetRewardPointsHistoriesByCustomerIds(int[] customerIds); + #endregion Reward points } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs new file mode 100644 index 0000000000..452f281fb8 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -0,0 +1,653 @@ +using System; +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; +using SmartStore.Core.Domain.Customers; +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.Directory; +using SmartStore.Services.Helpers; +using SmartStore.Services.Media; +using SmartStore.Services.Security; +using SmartStore.Utilities; + +namespace SmartStore.Services.Customers.Importer +{ + public class CustomerImporter : EntityImporterBase + { + private const string _attributeKeyGroup = "Customer"; + + private readonly IRepository _customerRepository; + 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; + private readonly IStateProvinceService _stateProvinceService; + private readonly FileDownloadManager _fileDownloadManager; + private readonly CustomerSettings _customerSettings; + private readonly DateTimeSettings _dateTimeSettings; + private readonly ForumSettings _forumSettings; + + public CustomerImporter( + IRepository customerRepository, + IRepository pictureRepository, + ICommonServices services, + IGenericAttributeService genericAttributeService, + ICustomerService customerService, + IPictureService pictureService, + IAffiliateService affiliateService, + ICountryService countryService, + IStateProvinceService stateProvinceService, + FileDownloadManager fileDownloadManager, + CustomerSettings customerSettings, + DateTimeSettings dateTimeSettings, + ForumSettings forumSettings) + { + _customerRepository = customerRepository; + _pictureRepository = pictureRepository; + _services = services; + _genericAttributeService = genericAttributeService; + _customerService = customerService; + _pictureService = pictureService; + _affiliateService = affiliateService; + _countryService = countryService; + _stateProvinceService = stateProvinceService; + _fileDownloadManager = fileDownloadManager; + _customerSettings = customerSettings; + _dateTimeSettings = dateTimeSettings; + _forumSettings = forumSettings; + } + + + protected override void Import(ImportExecuteContext context) + { + var customer = _services.WorkContext.CurrentCustomer; + var allowManagingCustomerRoles = _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomerRoles, customer); + + var allAffiliateIds = _affiliateService.GetAllAffiliates(true) + .Select(x => x.Id) + .ToList(); + + var allCountries = new Dictionary(); + foreach (var country in _countryService.GetAllCountries(true)) + { + if (!allCountries.ContainsKey(country.TwoLetterIsoCode)) + allCountries.Add(country.TwoLetterIsoCode, country.Id); + + if (!allCountries.ContainsKey(country.ThreeLetterIsoCode)) + allCountries.Add(country.ThreeLetterIsoCode, country.Id); + } + + 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), + StringComparer.OrdinalIgnoreCase); + + using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) + { + var segmenter = context.DataSegmenter; + + Initialize(context); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var batch = segmenter.GetCurrentBatch(); + + _customerRepository.Context.DetachAll(false); + + context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + // =========================================================================== + // Process customers + // =========================================================================== + try + { + ProcessCustomers(context, batch, allAffiliateIds); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCustomers"); + } + + // reduce batch to saved (valid) records. + // No need to perform import operations on errored records. + 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); + + // =========================================================================== + // Process generic attributes + // =========================================================================== + try + { + ProcessGenericAttributes(context, batch, allCountries, allStateProvinces, allCustomerNumbers); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessGenericAttributes"); + } + + // =========================================================================== + // Process avatars + // =========================================================================== + if (_customerSettings.AllowCustomersToUploadAvatars) + { + try + { + ProcessAvatars(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAvatars"); + } + } + + // =========================================================================== + // Process addresses + // =========================================================================== + try + { + _services.DbContext.AutoDetectChangesEnabled = true; + ProcessAddresses(context, batch, allCountries, allStateProvinces); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAddresses"); + } + finally + { + _services.DbContext.AutoDetectChangesEnabled = false; + } + } + } + } + + protected virtual int ProcessCustomers( + ImportExecuteContext context, + IEnumerable> batch, + List allAffiliateIds) + { + _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); + + foreach (var row in batch) + { + Customer customer = null; + var id = row.GetDataValue("Id"); + var email = row.GetDataValue("Email"); + + foreach (var keyName in context.KeyFieldNames) + { + switch (keyName) + { + case "Id": + if (id != 0) + { + customer = customerQuery.FirstOrDefault(x => x.Id == id); + } + break; + case "CustomerGuid": + var customerGuid = row.GetDataValue("CustomerGuid"); + if (customerGuid.HasValue()) + { + var guid = new Guid(customerGuid); + customer = customerQuery.FirstOrDefault(x => x.CustomerGuid == guid); + } + break; + case "Email": + if (email.HasValue()) + { + customer = customerQuery.FirstOrDefault(x => x.Email == email); + } + break; + case "Username": + var userName = row.GetDataValue("Username"); + if (userName.HasValue()) + { + customer = customerQuery.FirstOrDefault(x => x.Username == userName); + } + break; + } + + if (customer != null) + break; + } + + if (customer == null) + { + if (context.UpdateOnly) + { + ++context.Result.SkippedRecords; + continue; + } + + customer = new Customer + { + CustomerGuid = new Guid(), + AffiliateId = 0, + Active = true + }; + } + else + { + _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()); + + row.SetProperty(context.Result, (x) => x.CustomerGuid); + row.SetProperty(context.Result, (x) => x.Username); + row.SetProperty(context.Result, (x) => x.Email); + + if (email.HasValue() && currentCustomer.Email.IsCaseInsensitiveEqual(email)) + { + context.Result.AddInfo("Security. Ignored password of current customer (who started this import).", row.GetRowInfo(), "Password"); + } + else + { + row.SetProperty(context.Result, (x) => x.Password); + row.SetProperty(context.Result, (x) => x.PasswordFormatId); + row.SetProperty(context.Result, (x) => x.PasswordSalt); + } + + row.SetProperty(context.Result, (x) => x.AdminComment); + row.SetProperty(context.Result, (x) => x.IsTaxExempt); + row.SetProperty(context.Result, (x) => x.Active); + + row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); + row.SetProperty(context.Result, (x) => x.LastActivityDateUtc, UtcNow); + + if (affiliateId > 0 && allAffiliateIds.Contains(affiliateId)) + { + customer.AffiliateId = affiliateId; + } + + if (isAdmin) + { + context.Result.AddInfo("Security. Ignored administrator role.", row.GetRowInfo(), "IsAdministrator"); + } + + UpsertRole(row, guestRole, isGuest); + UpsertRole(row, registeredRole, isRegistered); + UpsertRole(row, forumModeratorRole, isForumModerator); + + if (row.IsTransient) + { + _customerRepository.Insert(customer); + lastInserted = customer; + } + else + { + _customerRepository.Update(customer); + lastUpdated = customer; + } + } + + var num = _customerRepository.Context.SaveChanges(); + + if (lastInserted != null) + { + _services.EventPublisher.EntityInserted(lastInserted); + } + + if (lastUpdated != null) + { + _services.EventPublisher.EntityUpdated(lastUpdated); + } + + return num; + } + + protected virtual int ProcessAddresses( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary allCountries, + Dictionary, int> allStateProvinces) + { + foreach (var row in batch) + { + ImportAddress("BillingAddress.", row, context, allCountries, allStateProvinces); + ImportAddress("ShippingAddress.", row, context, allCountries, allStateProvinces); + } + + return _services.DbContext.SaveChanges(); + } + + private void ImportAddress( + string fieldPrefix, + ImportRow row, + ImportExecuteContext context, + Dictionary allCountries, + Dictionary, int> allStateProvinces) + { + // last name is mandatory for an address to be imported or updated + if (!row.HasDataValue(fieldPrefix + "LastName")) + return; + + var address = new Address + { + CreatedOnUtc = UtcNow + }; + + var childRow = new ImportRow
(row.Segmenter, row.DataRow, row.Position); + childRow.Initialize(address, row.EntityDisplayName); + + childRow.SetProperty(context.Result, fieldPrefix + "LastName", x => x.LastName); + childRow.SetProperty(context.Result, fieldPrefix + "FirstName", x => x.FirstName); + childRow.SetProperty(context.Result, fieldPrefix + "Email", x => x.Email); + childRow.SetProperty(context.Result, fieldPrefix + "Company", x => x.Company); + childRow.SetProperty(context.Result, fieldPrefix + "City", x => x.City); + childRow.SetProperty(context.Result, fieldPrefix + "Address1", x => x.Address1); + childRow.SetProperty(context.Result, fieldPrefix + "Address2", x => x.Address2); + childRow.SetProperty(context.Result, fieldPrefix + "ZipPostalCode", x => x.ZipPostalCode); + childRow.SetProperty(context.Result, fieldPrefix + "PhoneNumber", x => x.PhoneNumber); + childRow.SetProperty(context.Result, fieldPrefix + "FaxNumber", x => x.FaxNumber); + + childRow.SetProperty(context.Result, fieldPrefix + "CountryId", x => x.CountryId); + if (childRow.Entity.CountryId == null) + { + // try with country code + childRow.SetProperty(context.Result, fieldPrefix + "CountryCode", x => x.CountryId, converter: (val, ci) => CountryCodeToId(allCountries, val.ToString())); + } + + var countryId = childRow.Entity.CountryId; + + if (countryId.HasValue) + { + childRow.SetProperty(context.Result, fieldPrefix + "StateProvinceId", x => x.StateProvinceId); + if (childRow.Entity.StateProvinceId == null) + { + // try with state abbreviation + childRow.SetProperty(context.Result, fieldPrefix + "StateAbbreviation", x => x.StateProvinceId, converter: (val, ci) => StateAbbreviationToId(allStateProvinces, countryId, val.ToString())); + } + } + + if (!childRow.IsDirty) + { + // Not one single property could be set. Get out! + return; + } + + var appliedAddress = row.Entity.Addresses.FindAddress(address); + + if (appliedAddress == null) + { + appliedAddress = address; + row.Entity.Addresses.Add(appliedAddress); + } + + if (fieldPrefix == "BillingAddress.") + { + row.Entity.BillingAddress = appliedAddress; + } + else if (fieldPrefix == "ShippingAddress.") + { + row.Entity.ShippingAddress = appliedAddress; + } + + _customerRepository.Update(row.Entity); + } + + protected virtual int ProcessGenericAttributes( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary allCountries, + Dictionary, int> allStateProvinces, + HashSet allCustomerNumbers) + { + 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); + + if (_customerSettings.StreetAddress2Enabled) + SaveAttribute(row, SystemCustomerAttributeNames.StreetAddress2); + + if (_customerSettings.ZipPostalCodeEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.ZipPostalCode); + + if (_customerSettings.CityEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.City); + + if (_customerSettings.CountryEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.CountryId); + + if (_customerSettings.CountryEnabled && _customerSettings.StateProvinceEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.StateProvinceId); + + if (_customerSettings.PhoneEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.Phone); + + if (_customerSettings.FaxEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.Fax); + + if (_forumSettings.ForumsEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.ForumPostCount); + + if (_forumSettings.SignaturesEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.Signature); + + var countryId = CountryCodeToId(allCountries, row.GetDataValue("CountryCode")); + var stateId = StateAbbreviationToId(allStateProvinces, countryId, row.GetDataValue("StateAbbreviation")); + + if (countryId.HasValue) + { + SaveAttribute(row, SystemCustomerAttributeNames.CountryId, countryId.Value); + } + + if (stateId.HasValue) + { + 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(); + } + + protected virtual int ProcessAvatars( + ImportExecuteContext context, + IEnumerable> batch) + { + foreach (var row in batch) + { + var urlOrPath = row.GetDataValue("AvatarPictureUrl"); + 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); + if (image == null) + continue; + + 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 currentPictureId = row.Entity.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); + if (currentPictureId != 0 && (picture = _pictureRepository.GetById(currentPictureId)) != 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) + { + _pictureRepository.Context.SaveChanges(); + SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId, picture.Id); + } + } + else + { + context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "AvatarPictureUrl"); + } + } + } + else + { + context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "AvatarPictureUrl"); + } + } + + return _services.DbContext.SaveChanges(); + } + + public static string[] SupportedKeyFields + { + get + { + return new string[] { "Id", "CustomerGuid", "Email", "Username" }; + } + } + + public static string[] DefaultKeyFields + { + get + { + return new string[] { "Email" }; + } + } + + private int? CountryCodeToId(Dictionary allCountries, string code) + { + int countryId; + if (code.HasValue() && allCountries.TryGetValue(code, out countryId) && countryId != 0) + { + return countryId; + } + + return null; + } + + private int? StateAbbreviationToId(Dictionary, int> allStateProvinces, int? countryId, string abbreviation) + { + if (countryId.HasValue && abbreviation.HasValue()) + { + var key = Tuple.Create(countryId.Value, abbreviation); + + int stateId; + if (allStateProvinces.TryGetValue(key, out stateId) && stateId != 0) + { + return stateId; + } + } + + return null; + } + + private void SaveAttribute(ImportRow row, string key) + { + SaveAttribute(row, key, row.GetDataValue(key)); + } + + private void SaveAttribute(ImportRow row, string key) + { + + SaveAttribute(row, key, row.GetDataValue(key)); + } + + private void SaveAttribute(ImportRow row, string key, TPropType value) + { + if (row.IsTransient) + return; + + if (row.IsNew || value != null) + { + _genericAttributeService.SaveAttribute(row.Entity.Id, key, _attributeKeyGroup, value); + } + } + + private void UpsertRole(ImportRow row, CustomerRole role, bool value) + { + if (role == null) + return; + + var hasRole = row.Entity.CustomerRoles.Any(x => x.SystemName == role.SystemName); + + if (value && !hasRole) + { + row.Entity.CustomerRoles.Add(role); + } + else if (!value && hasRole) + { + row.Entity.CustomerRoles.Remove(role); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvConfiguration.cs b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvConfiguration.cs new file mode 100644 index 0000000000..1680cf2d55 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvConfiguration.cs @@ -0,0 +1,296 @@ +using System; +using System.Linq; +using Newtonsoft.Json; + +namespace SmartStore.Services.DataExchange.Csv +{ + /// + /// Specifies the action to take when a parsing error has occured. + /// + public enum ParseErrorAction + { + /// + /// Raises the event. + /// + RaiseEvent = 0, + + /// + /// Tries to advance to next line. + /// + AdvanceToNextLine = 1, + + /// + /// Throws an exception. + /// + ThrowException = 2, + } + + /// + /// Specifies the action to take when a field is missing. + /// + public enum MissingFieldAction + { + /// + /// Treat as a parsing error. + /// + ParseError = 0, + + /// + /// Replaces by an empty value. + /// + ReplaceByEmpty = 1, + + /// + /// Replaces by a null value (). + /// + ReplaceByNull = 2, + } + + public class CsvConfiguration + { + private char _delimiter; + private char _escape; + private char _quote; + private string _quoteString; + private char[] _quotableChars; + + public CsvConfiguration() + { + _escape = '"'; + _delimiter = ';'; + _quote = '"'; + _quoteString = new String(new char[] { _escape, _quote }); + + Comment = '#'; + HasHeaders = true; + SkipEmptyLines = true; + SupportsMultiline = true; + DefaultHeaderName = "Column"; + + BuildQuotableChars(); + } + + private void BuildQuotableChars() + { + _quotableChars = new char[] { '\r', '\n', _delimiter, _quote }; + } + + internal char[] QuotableChars + { + get { return _quotableChars; } + } + + /// + /// Gets an Excel friendly configuration where the result can be directly edited by Excel + /// + public static CsvConfiguration ExcelFriendlyConfiguration + { + get + { + return new CsvConfiguration + { + Delimiter = ';', + Quote = '"', + Escape = '"', + SupportsMultiline = false + }; + } + } + + /// + /// Gets an array with preset characters + /// + public static char[] PresetCharacters + { + get { return new char[] { '\n', '\r', '\0' }; } + } + + /// + /// Gets the comment character indicating that a line is commented out (default: #). + /// + /// The comment character indicating that a line is commented out. + public char Comment + { + get; + set; + } + + /// + /// Gets the escape character letting insert quotation characters inside a quoted field (default: "). + /// + /// The escape character letting insert quotation characters inside a quoted field. + public char Escape + { + get { return _escape; } + set + { + if (value == _escape) + return; + + if (PresetCharacters.Contains(value)) + { + throw new SmartException("'{0}' is not a valid escape char.".FormatInvariant(value)); + } + if (value == _delimiter) + { + throw new SmartException("Escape and delimiter chars cannot be equal in CSV files."); + } + + _escape = value; + _quoteString = new String(new char[] { _escape, _quote }); + } + } + + /// + /// Gets the delimiter character separating each field (default: ;). + /// + /// The delimiter character separating each field. + public char Delimiter + { + get { return _delimiter; } + set + { + if (value == _delimiter) + return; + + if (PresetCharacters.Contains(value)) + { + throw new SmartException("'{0}' is not a valid delimiter char.".FormatInvariant(value)); + } + if (value == _quote) + { + throw new SmartException("Quote and delimiter chars cannot be equal in CSV files."); + } + + _delimiter = value; + BuildQuotableChars(); + + } + } + + /// + /// Gets the quotation character wrapping every field (default: "). + /// + /// The quotation character wrapping every field. + public char Quote + { + get { return _quote; } + set + { + if (value == _quote) + return; + + if (PresetCharacters.Contains(value)) + { + throw new SmartException("'{0}' is not a valid quote char.".FormatInvariant(value)); + } + if (value == _delimiter) + { + throw new SmartException("Quote and delimiter chars cannot be equal in CSV files."); + } + + _quote = value; + _quoteString = new String(new char[] { _escape, _quote }); + BuildQuotableChars(); + } + } + + /// + /// Gets the concatenation of escape and quote char + /// + [JsonIgnore] + public string QuoteString + { + get + { + return _quoteString; + } + } + + public bool QuoteAllFields + { + get; + set; + } + + /// + /// Indicates if field names are located on the first non commented line (default: true). + /// + /// if field names are located on the first non commented line, otherwise, . + public bool HasHeaders + { + get; + set; + } + + /// + /// Indicates if spaces at the start and end of a field are trimmed (default: false). + /// + /// if spaces at the start and end of a field are trimmed, otherwise, . + public bool TrimValues + { + get; + set; + } + + /// + /// Contains the value which denotes a DbNull-value. + /// + public string NullValue + { + get; + set; + } + + /// + /// Gets or sets the default action to take when a parsing error has occured. + /// + /// The default action to take when a parsing error has occured. + public ParseErrorAction DefaultParseErrorAction + { + get; + set; + } + + /// + /// Gets or sets the action to take when a field is missing. + /// + /// The action to take when a field is missing. + public MissingFieldAction MissingFieldAction + { + get; + set; + } + + /// + /// Gets or sets a value indicating if the reader supports multiline fields (default: true). + /// + /// A value indicating if the reader supports multiline field. + public bool SupportsMultiline + { + get; + set; + } + + /// + /// Gets or sets a value indicating if the reader will skip empty lines (default: true). + /// + /// A value indicating if the reader will skip empty lines. + public bool SkipEmptyLines + { + get; + set; + } + + /// + /// Gets or sets the default header name when it is an empty string or only whitespaces (default: Column). + /// The header index will be appended to the specified name. + /// + /// The default header name when it is an empty string or only whitespaces. + public string DefaultHeaderName + { + get; + set; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvConfigurationConverter.cs b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvConfigurationConverter.cs new file mode 100644 index 0000000000..4a81792a19 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvConfigurationConverter.cs @@ -0,0 +1,65 @@ +using System; +using System.Globalization; +using Newtonsoft.Json; +using SmartStore.ComponentModel; + +namespace SmartStore.Services.DataExchange.Csv +{ + public class CsvConfigurationConverter : TypeConverterBase + { + public CsvConfigurationConverter() + : 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) + { + return JsonConvert.DeserializeObject((string)value); + } + + return base.ConvertFrom(culture, value); + } + + public T ConvertFrom(string value) + { + if (value.HasValue()) + return (T)ConvertFrom(CultureInfo.InvariantCulture, value); + + return default(T); + } + + public override object ConvertTo(CultureInfo culture, string format, object value, Type to) + { + if (to == typeof(string)) + { + if (value is CsvConfiguration) + { + return JsonConvert.SerializeObject(value); + } + else + { + return string.Empty; + } + } + + return base.ConvertTo(culture, format, value, to); + } + + public string ConvertTo(object value) + { + return (string)ConvertTo(CultureInfo.InvariantCulture, null, value, typeof(string)); + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvDataReader.cs b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvDataReader.cs new file mode 100644 index 0000000000..9fbe870a2b --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvDataReader.cs @@ -0,0 +1,389 @@ +// Wrapper for LumenWorks CsvReader (fork by phatcher) +// ---------------------------------------------------- +// LumenWorks.Framework.IO.CsvReader +// Copyright (c) 2006 Sébastien Lorion +// https://github.com/phatcher/CsvReader/ +// +// MIT license (http://en.wikipedia.org/wiki/MIT_License) + +using System; +using System.Collections; +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 +{ + /// + /// Represents a reader that provides fast, non-cached, forward-only access to CSV data. + /// + public class CsvDataReader : DisposableObject, IDataReader, IEnumerable + { + private readonly CsvReader _csv; + private readonly IDataReader _reader; + + /// + /// Initializes a new instance of the CsvDataReader class. + /// + /// A pointing to the CSV file. + public CsvDataReader(TextReader reader) + : this(reader, new CsvConfiguration()) + { + } + + /// + /// Initializes a new instance of the CsvDataReader class. + /// + /// A pointing to the CSV file. + public CsvDataReader(TextReader reader, CsvConfiguration configuration) + { + Guard.ArgumentNotNull(() => reader); + Guard.ArgumentNotNull(() => configuration); + + this.Configuration = configuration; + + _csv = new CsvReader( + 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); + + _reader = _csv; + } + + public CsvConfiguration Configuration + { + get; + private set; + } + + #region Public wrapper members + + /// + /// Gets the field headers. + /// + /// The field headers or an empty array if headers are not supported. + /// + /// The instance has been disposed of. + /// + public string[] GetFieldHeaders() + { + return _csv.GetFieldHeaders(); + } + + public bool EndOfStream + { + get + { + return _csv.EndOfStream; + } + } + + /// + /// Gets the current row index in the CSV file (0-based). + /// + /// The current row index in the CSV file. + public long CurrentRowIndex + { + get + { + return _csv.CurrentRecordIndex; + } + } + + public void CopyCurrentRowTo(string[] array) + { + _csv.CopyCurrentRecordTo(array); + } + + /// + /// Gets the current row's raw CSV data. + /// + /// The current raw CSV data. + public string GetRawData() + { + return _csv.GetCurrentRawData(); + } + + /// + /// Gets the field with the specified name. must be . + /// + /// + /// The field with the specified name. + /// + /// + /// is or an empty string. + /// + /// + /// The CSV does not have headers ( property is ). + /// + /// + /// not found. + /// + /// + /// The CSV appears to be corrupt at the current position. + /// + /// + /// The instance has been disposed of. + /// + public string this[string field] + { + get + { + return _csv[field]; + } + } + + /// + /// Gets the field at the specified index. + /// + /// The field at the specified index. + /// + /// must be included in [0, [. + /// + /// + /// No record read yet. Call ReadLine() first. + /// + /// + /// The CSV appears to be corrupt at the current position. + /// + /// + /// The instance has been disposed of. + /// + public virtual string this[int index] + { + get + { + return _csv[index]; + } + } + + #endregion + + #region IDataReader (implicit) + + public int FieldCount + { + get + { + return _reader.FieldCount; + } + } + + public bool Read() + { + return _csv.ReadNextRecord(); + } + + public int GetOrdinal(string name) + { + return _reader.GetOrdinal(name); + } + + #endregion + + #region IDataReader (explicit) + + object IDataRecord.this[string name] + { + get + { + return _reader[name]; + } + } + + object IDataRecord.this[int i] + { + get + { + return _reader[i]; + } + } + + int IDataReader.Depth + { + get + { + return _reader.Depth; + } + } + + bool IDataReader.IsClosed + { + get + { + return _reader.IsClosed; + } + } + + int IDataReader.RecordsAffected + { + get + { + return _reader.RecordsAffected; + } + } + + void IDataReader.Close() + { + _reader.Close(); + } + + bool IDataRecord.GetBoolean(int i) + { + return _reader.GetBoolean(i); + } + + byte IDataRecord.GetByte(int i) + { + return _reader.GetByte(i); + } + + long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + { + return _reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); + } + + char IDataRecord.GetChar(int i) + { + return _reader.GetChar(i); + } + + long IDataRecord.GetChars(int i, long fieldOffset, char[] buffer, int bufferoffset, int length) + { + return _reader.GetChars(i, fieldOffset, buffer, bufferoffset, length); + } + + IDataReader IDataRecord.GetData(int i) + { + return _reader.GetData(i); + } + + string IDataRecord.GetDataTypeName(int i) + { + return _reader.GetDataTypeName(i); + } + + DateTime IDataRecord.GetDateTime(int i) + { + return _reader.GetDateTime(i); + } + + decimal IDataRecord.GetDecimal(int i) + { + return _reader.GetDecimal(i); + } + + double IDataRecord.GetDouble(int i) + { + return _reader.GetDouble(i); + } + + Type IDataRecord.GetFieldType(int i) + { + return _reader.GetFieldType(i); + } + + float IDataRecord.GetFloat(int i) + { + return _reader.GetFloat(i); + } + + Guid IDataRecord.GetGuid(int i) + { + return _reader.GetGuid(i); + } + + short IDataRecord.GetInt16(int i) + { + return _reader.GetInt16(i); + } + + int IDataRecord.GetInt32(int i) + { + return _reader.GetInt32(i); + } + + long IDataRecord.GetInt64(int i) + { + return _reader.GetInt64(i); + } + + string IDataRecord.GetName(int i) + { + return _reader.GetName(i); + } + + DataTable IDataReader.GetSchemaTable() + { + return _reader.GetSchemaTable(); + } + + string IDataRecord.GetString(int i) + { + return _reader.GetString(i); + } + + object IDataRecord.GetValue(int i) + { + return _reader.GetGuid(i); + } + + int IDataRecord.GetValues(object[] values) + { + return _reader.GetValues(values); + } + + bool IDataRecord.IsDBNull(int i) + { + return _reader.IsDBNull(i); + } + + bool IDataReader.NextResult() + { + return _reader.NextResult(); + } + + #endregion + + #region IEnumerable + + IEnumerator IEnumerable.GetEnumerator() + { + return _csv.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _csv.GetEnumerator(); + } + + #endregion + + #region Disposable + + protected override void OnDispose(bool disposing) + { + if (disposing) + { + _csv.Dispose(); + } + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs new file mode 100644 index 0000000000..8bb9942052 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs @@ -0,0 +1,194 @@ +// Customized version of CsvHelper from Josh Close: +// ------------------------------------------------ +// Copyright 2009-2015 Josh Close and Contributors +// This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. +// See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. +// http://csvhelper.com + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Services.DataExchange.Csv +{ + /// + /// SImple utility class used to write CSV files. + /// + public class CsvWriter : DisposableObject + { + private TextWriter _writer; + private readonly IList _currentRow = new List(); + private int? _fieldCount; + + public CsvWriter(TextWriter writer) + : this(writer, new CsvConfiguration()) + { + } + + public CsvWriter(TextWriter writer, CsvConfiguration configuration) + { + Guard.ArgumentNotNull(() => writer); + Guard.ArgumentNotNull(() => configuration); + + _writer = writer; + this.Configuration = configuration; + } + + public CsvConfiguration Configuration + { + get; + private set; + } + + /// + /// Writes a sequence of fields to the CSV file. The fields + /// may get quotes added to it. + /// When all fields are written for a row, + /// must be called + /// to complete writing of the current row. + /// + /// The fields to write. + public virtual void WriteFields(IEnumerable fields) + { + Guard.ArgumentNotNull(() => fields); + fields.Each(x => WriteField(x)); + } + + /// + /// Writes a sequence of fields to the CSV file. This will + /// ignore any need to quote and ignore the + /// + /// and just quote based on the shouldQuote + /// parameter. + /// When all fields are written for a row, + /// must be called + /// to complete writing of the current row. + /// + /// The fields to write. + /// True to quote the fields, otherwise false. + public virtual void WriteFields(IEnumerable fields, bool shouldQuote) + { + Guard.ArgumentNotNull(() => fields); + fields.Each(x => WriteField(x, shouldQuote)); + } + + /// + /// Writes the field to the CSV file. The field + /// may get quotes added to it. + /// When all fields are written for a row, + /// must be called + /// to complete writing of the current row. + /// + /// The field to write. + public virtual void WriteField(string field) + { + var shouldQuote = Configuration.QuoteAllFields; + + if (!string.IsNullOrEmpty(field)) + { + if (Configuration.TrimValues) + { + field = field.Trim(); + } + + if (!Configuration.SupportsMultiline) + { + field = field.Replace('\r', ' ').Replace('\n', ' '); + } + + if (shouldQuote + || field[0] == ' ' + || field[field.Length - 1] == ' ' + || field.IndexOfAny(Configuration.QuotableChars) > -1) + { + shouldQuote = true; + } + } + + WriteField(field, shouldQuote); + } + + /// + /// Writes the field to the CSV file. This will + /// ignore any need to quote and ignore the + /// + /// and just quote based on the shouldQuote + /// parameter. + /// When all fields are written for a row, + /// must be called + /// to complete writing of the current row. + /// + /// The field to write. + /// True to quote the field, otherwise false. + public virtual void WriteField(string field, bool shouldQuote) + { + // All quotes must be escaped. + if (shouldQuote && !string.IsNullOrEmpty(field)) + { + field = field.Replace(Configuration.Quote.ToString(), Configuration.QuoteString); + } + + if (shouldQuote) + { + field = Configuration.Quote + (field ?? string.Empty) + Configuration.Quote; + } + + _currentRow.Add(field ?? string.Empty); + } + + /// + /// Ends writing of the current row + /// and starts a new row. + /// + public virtual void NextRow() + { + WriteRow(_currentRow.ToArray()); + _currentRow.Clear(); + } + + public string CurrentRawValue() + { + var row = string.Join(Configuration.Delimiter.ToString(), _currentRow); + return row; + } + + private void WriteRow(string[] fields) + { + CheckDisposed(); + + if (fields.Length == 0) + { + throw new SmartException("Cannot write an empty row to the CSV file."); + } + + if (!_fieldCount.HasValue) + { + _fieldCount = fields.Length; + } + + if (_fieldCount.Value != fields.Length) + { + throw new SmartException("The field count of the current row does not match the previous row's field count."); + } + + var row = string.Join(Configuration.Delimiter.ToString(), fields); + _writer.WriteLine(row); + } + + protected override void OnDispose(bool disposing) + { + if (disposing) + { + if (_writer != null) + { + _writer.Dispose(); + _writer = null; + } + } + } + + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs b/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs new file mode 100644 index 0000000000..d46879193c --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs @@ -0,0 +1,612 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using OfficeOpenXml; + +namespace SmartStore.Services.DataExchange.Excel +{ + public class ExcelDataReader : DisposableObject, IDataReader + { + private ExcelPackage _package; + private ExcelWorksheet _sheet; + private int _totalRows; + private int _totalColumns; + + private bool _initialized; + private string[] _columns; + private Dictionary _columnIndexes; + private int _currentRowIndex; + private bool _eof; + + private readonly object _lock = new object(); + + public ExcelDataReader(Stream source, bool hasHeaders) + { + Guard.ArgumentNotNull(() => source); + + _package = new ExcelPackage(source); + + // get the first worksheet in the workbook + _sheet = _package.Workbook.Worksheets.FirstOrDefault(); + if (_sheet == null) + { + throw Error.InvalidOperation("The excel package does not contain any worksheet."); + } + + if (_sheet.Dimension == null) + { + throw Error.InvalidOperation("The excel worksheet does not contain any data."); + } + + HasHeaders = hasHeaders; + DefaultHeaderName = "Column"; + + _totalColumns = _sheet.Dimension.End.Column; + _totalRows = _sheet.Dimension.End.Row - (hasHeaders ? 1 : 0); + + _currentRowIndex = -1; + } + + #region Configuration + + public bool HasHeaders + { + get; + private set; + } + + public string DefaultHeaderName + { + get; + set; + } + + #endregion + + #region public members + + public bool MoveToStart() + { + return MoveTo(0); + } + + public bool MoveToEnd() + { + return MoveTo(_totalRows - 1); + } + + public bool MoveTo(int row) + { + EnsureInitialize(); + ValidateDataReader(validateInitialized: false); + + if (row < 0 || row >= _totalRows) + return false; + + _currentRowIndex = row; + _eof = false; + + return true; + } + + public bool EndOfStream + { + get { return _eof; } + } + + public int TotalRows + { + get + { + EnsureInitialize(); + return _totalRows; + } + } + + public IReadOnlyCollection GetColumnHeaders() + { + EnsureInitialize(); + return _columns.AsReadOnly(); + } + + public int CurrentRowIndex + { + get + { + return _currentRowIndex; + } + } + + public string GetFormatted(int i) + { + ValidateDataReader(); + return _sheet.Cells[ExcelRowIndex(_currentRowIndex), i + 1].Text; + } + + public int GetColumnIndex(string name) + { + Guard.ArgumentNotEmpty(name, "name"); + + EnsureInitialize(); + + int index; + + if (_columnIndexes != null && _columnIndexes.TryGetValue(name, out index)) + return index; + else + return -1; + } + + #endregion + + #region IDataReader + + bool IDataReader.NextResult() + { + ValidateDataReader(validateInitialized: false); + return false; + } + + public bool Read() + { + ValidateDataReader(validateInitialized: false); + return ReadNextRow(false); + } + + int IDataReader.Depth + { + get + { + ValidateDataReader(validateInitialized: false); + return 0; + } + } + + bool IDataReader.IsClosed + { + get + { + return _eof; + } + } + + int IDataReader.RecordsAffected + { + get + { + return -1; + } + } + + void IDataReader.Close() + { + Dispose(); + } + + + public int FieldCount + { + get + { + EnsureInitialize(); + return _totalColumns; + } + } + + public object this[string name] + { + get + { + int index = GetColumnIndex(name); + + if (index < 0) + Error.Argument("name", "'{0}' column header not found.".FormatInvariant(name)); + + return this[index]; + } + } + + public object this[int i] + { + get + { + ValidateDataReader(); + // Excel indexes start from 1 + return _sheet.GetValue(ExcelRowIndex(_currentRowIndex), i + 1); + } + } + + public bool GetBoolean(int i) + { + object value = this[i]; + + int result; + if (Int32.TryParse(value.ToString(), out result)) + return (result != 0); + else + return Boolean.Parse(value.ToString()); + } + + public byte GetByte(int i) + { + return this[i].Convert(CultureInfo.CurrentCulture); + } + + public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + { + ValidateDataReader(); + return CopyFieldToArray(i, fieldOffset, buffer, bufferoffset, length); + } + + public char GetChar(int i) + { + return this[i].Convert(CultureInfo.CurrentCulture); + } + + public long GetChars(int i, long fieldOffset, char[] buffer, int bufferoffset, int length) + { + ValidateDataReader(); + return CopyFieldToArray(i, fieldOffset, buffer, bufferoffset, length); + } + + IDataReader IDataRecord.GetData(int i) + { + ValidateDataReader(); + + if (i == 0) + return this; + else + return null; + } + + string IDataRecord.GetDataTypeName(int i) + { + return this[i].GetType().FullName; + } + + public DateTime GetDateTime(int i) + { + return this[i].Convert(CultureInfo.CurrentCulture); + } + + public decimal GetDecimal(int i) + { + return this[i].Convert(CultureInfo.CurrentCulture); + } + + public double GetDouble(int i) + { + return this[i].Convert(CultureInfo.CurrentCulture); + } + + public Type GetFieldType(int i) + { + return this[i].GetType(); + } + + public float GetFloat(int i) + { + return this[i].Convert(CultureInfo.CurrentCulture); + } + + public Guid GetGuid(int i) + { + return this[i].Convert(CultureInfo.CurrentCulture); + } + + public short GetInt16(int i) + { + return this[i].Convert(CultureInfo.CurrentCulture); + } + + public int GetInt32(int i) + { + return this[i].Convert(CultureInfo.CurrentCulture); + } + + public long GetInt64(int i) + { + return this[i].Convert(CultureInfo.CurrentCulture); + } + + public string GetName(int i) + { + EnsureInitialize(); + ValidateDataReader(validateInitialized: false); + + if (i < 0 || i >= _columns.Length) + { + throw new ArgumentOutOfRangeException("i", i, + "Column index must be included within [0, {0}], but specified column index was: '{1}'.".FormatInvariant(_columns.Length, i)); + } + + return _columns[i]; + } + + public int GetOrdinal(string name) + { + return GetColumnIndex(name); + } + + DataTable IDataReader.GetSchemaTable() + { + EnsureInitialize(); + ValidateDataReader(validateInitialized: false); + + var schema = new DataTable("SchemaTable") + { + Locale = CultureInfo.InvariantCulture, + MinimumCapacity = _columns.Length + }; + + schema.Columns.Add(SchemaTableColumn.AllowDBNull, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.BaseColumnName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.BaseSchemaName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.BaseTableName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.ColumnName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.ColumnSize, typeof(int)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.DataType, typeof(object)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.IsAliased, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.IsExpression, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.IsKey, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.IsLong, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.IsUnique, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.NumericPrecision, typeof(short)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.NumericScale, typeof(short)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.ProviderType, typeof(int)).ReadOnly = true; + + schema.Columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(bool)).ReadOnly = true; + + // null marks columns that will change for each row + object[] schemaRow = + { + true, // 00- AllowDBNull + null, // 01- BaseColumnName + string.Empty, // 02- BaseSchemaName + string.Empty, // 03- BaseTableName + null, // 04- ColumnName + null, // 05- ColumnOrdinal + int.MaxValue, // 06- ColumnSize + typeof(string), // 07- DataType + false, // 08- IsAliased + false, // 09- IsExpression + false, // 10- IsKey + false, // 11- IsLong + false, // 12- IsUnique + DBNull.Value, // 13- NumericPrecision + DBNull.Value, // 14- NumericScale + (int) DbType.String, // 15- ProviderType + string.Empty, // 16- BaseCatalogName + string.Empty, // 17- BaseServerName + false, // 18- IsAutoIncrement + false, // 19- IsHidden + true, // 20- IsReadOnly + false // 21- IsRowVersion + }; + + int r = ExcelRowIndex(0); + + for (int i = 0; i < _columns.Length; i++) + { + schemaRow[1] = _columns[i]; // Base column name + schemaRow[4] = _columns[i]; // Column name + schemaRow[5] = i; // Column ordinal + + // get data type from 1st row only + var firstValue = _sheet.Cells[r, i + 1].Value; + schemaRow[7] = firstValue != null ? firstValue.GetType() : typeof(string); + + schema.Rows.Add(schemaRow); + } + + return schema; + } + + public string GetString(int i) + { + return this[i].Convert(CultureInfo.CurrentCulture); + } + + public object GetValue(int i) + { + ValidateDataReader(); + return ((IDataRecord)this).IsDBNull(i) ? DBNull.Value : this[i]; + } + + int IDataRecord.GetValues(object[] values) + { + var record = (IDataRecord)this; + + for (int i = 0; i < _totalColumns; i++) + { + values[i] = record.GetValue(i); + } + + return _totalColumns; + } + + bool IDataRecord.IsDBNull(int i) + { + return this[i] == null; + } + + #endregion + + #region Helper + + /// + /// Reads the next record. + /// + /// + /// Indicates if the reader will proceed to the next record after having read headers. + /// if it stops after having read headers; otherwise, . + /// + /// if a record has been successfully reads; otherwise, . + /// + /// The instance has been disposed of. + /// + protected virtual bool ReadNextRow(bool onlyReadHeaders) + { + if (_eof) + return false; + + CheckDisposed(); + + if (!_initialized) + { + _columns = new string[_totalColumns]; + _columnIndexes = new Dictionary(_totalColumns, StringComparer.OrdinalIgnoreCase); + + for (int i = 1; i <= _totalColumns; i++) + { + // Excel indexes start from 1 + string name = null; + if (HasHeaders) + { + name = _sheet.GetValue(1, i).NullEmpty(); + } + + name = name ?? (DefaultHeaderName ?? "Column") + i; + + _columns[i - 1] = name; + _columnIndexes[name] = i - 1; + } + + if (_columns.Select(x => x.ToLower()).Distinct().ToArray().Length != _columns.Length) + { + _columns = null; + _columnIndexes = null; + throw Error.InvalidOperation("The first row cannot contain duplicate column names."); + } + + _initialized = true; + + if (!onlyReadHeaders) + { + return ReadNextRow(false); + } + } + else + { + _currentRowIndex++; + if (_currentRowIndex >= _totalRows) + { + _eof = true; + return false; + } + } + + return true; + } + + private int ExcelRowIndex(int i) + { + // Excel indexes start from 1 + return i + (HasHeaders ? 2 : 1); + } + + /// + /// Ensures that the reader is initialized. + /// + private void EnsureInitialize() + { + if (!_initialized) + { + ReadNextRow(true); + } + + Debug.Assert(_columns != null); + Debug.Assert(_columns.Length > 0 || (_columns.Length == 0 && _columnIndexes == null)); + } + + private void ValidateDataReader(bool validateInitialized = true, bool validateNotClosed = true) + { + if (validateInitialized && (!_initialized || _currentRowIndex < 0)) + throw new InvalidOperationException("No current record. Call Read() to initialize the reader."); + + if (validateNotClosed && IsDisposed) + throw new InvalidOperationException("This operation is invalid when the reader is closed."); + } + + private long CopyFieldToArray(int column, long columnOffset, Array destinationArray, int destinationOffset, int length) + { + EnsureInitialize(); + + if (column < 0 || column >= _totalColumns) + { + throw new ArgumentOutOfRangeException("column", column, + "Column index must be included within [0, {0}], but specified column index was: '{1}'.".FormatInvariant(_totalColumns, column)); + } + + if (columnOffset < 0 || columnOffset >= int.MaxValue) + throw new ArgumentOutOfRangeException("fieldOffset"); + + // Array.Copy(...) will do the remaining argument checks + + if (length == 0) + return 0; + + string value = this[column].ToString(); + + if (value == null) + value = string.Empty; + + if (destinationArray.GetType() == typeof(char[])) + Array.Copy(value.ToCharArray((int)columnOffset, length), 0, destinationArray, destinationOffset, length); + else + { + char[] chars = value.ToCharArray((int)columnOffset, length); + byte[] source = new byte[chars.Length]; + ; + + for (int i = 0; i < chars.Length; i++) + source[i] = Convert.ToByte(chars[i]); + + Array.Copy(source, 0, destinationArray, destinationOffset, length); + } + + return length; + } + + #endregion + + #region IDisposable Support + + protected override void OnDispose(bool disposing) + { + if (disposing) + { + try + { + _sheet = null; + if (_package != null) + { + lock (_lock) + { + if (_package != null) + { + _package.Dispose(); + _package = null; + _eof = true; + } + } + } + } + catch { } + } + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs new file mode 100644 index 0000000000..c9d3fa26f2 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace SmartStore.Services.DataExchange.Export +{ + [Serializable] + public class DataExportResult + { + public DataExportResult() + { + Files = new List(); + } + + /// + /// Whether the export succeeded + /// + public bool Succeeded + { + get { return LastError.IsEmpty(); } + } + + /// + /// Last error + /// + [XmlIgnore] + public string LastError { get; set; } + + /// + /// Files created by last export + /// + public List Files { get; set; } + + /// + /// The path of the folder with the export files + /// + [XmlIgnore] + public string FileFolder { get; set; } + + [Serializable] + public class ExportFileInfo + { + /// + /// Store identifier, can be 0. + /// + public int StoreId { get; set; } + + /// + /// Name of file + /// + public string FileName { get; set; } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportTask.cs new file mode 100644 index 0000000000..f32f06c625 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportTask.cs @@ -0,0 +1,53 @@ +using System.Linq; +using SmartStore.Core.Localization; +using SmartStore.Services.Tasks; + +namespace SmartStore.Services.DataExchange.Export +{ + // note: namespace persisted in ScheduleTask.Type + public partial class DataExportTask : ITask + { + private readonly IDataExporter _exporter; + private readonly IExportProfileService _exportProfileService; + + public DataExportTask( + IDataExporter exporter, + IExportProfileService exportProfileService) + { + _exporter = exporter; + _exportProfileService = exportProfileService; + } + + public Localizer T { get; set; } + + public void Execute(TaskExecutionContext ctx) + { + var profileId = ctx.ScheduleTask.Alias.ToInt(); + var profile = _exportProfileService.GetExportProfileById(profileId); + + // load provider + var provider = _exportProfileService.LoadProvider(profile.ProviderSystemName); + if (provider == null) + throw new SmartException(T("Admin.Common.ProviderNotLoaded", profile.ProviderSystemName.NaIfEmpty())); + + // build export request + var request = new DataExportRequest(profile, provider); + + request.ProgressValueSetter = delegate (int val, int max, string msg) + { + ctx.SetProgress(val, max, msg, true); + }; + + if (ctx.Parameters.ContainsKey("SelectedIds")) + { + request.EntitiesToExport = ctx.Parameters["SelectedIds"] + .SplitSafe(",") + .Select(x => x.ToInt()) + .ToList(); + } + + // 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 new file mode 100644 index 0000000000..e6b182c270 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -0,0 +1,1440 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading; +using System.Web; +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.Media; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Email; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.Services.Catalog; +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.Security; +using SmartStore.Services.Seo; +using SmartStore.Services.Shipping; +using SmartStore.Services.Tax; +using SmartStore.Utilities; +using SmartStore.Utilities.Threading; + +namespace SmartStore.Services.DataExchange.Export +{ + public partial class DataExporter : IDataExporter + { + private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); + + #region Dependencies + + private readonly ICommonServices _services; + private readonly IDbContext _dbContext; + private readonly Lazy _priceFormatter; + private readonly Lazy _dateTimeHelper; + private readonly Lazy _exportProfileService; + private readonly Lazy _localizedEntityService; + private readonly Lazy _languageService; + private readonly Lazy _urlRecordService; + private readonly Lazy _pictureService; + private readonly Lazy _priceCalculationService; + private readonly Lazy _currencyService; + private readonly Lazy _taxService; + private readonly Lazy _categoryService; + private readonly Lazy _productAttributeParser; + private readonly Lazy _productAttributeService; + private readonly Lazy _productTemplateService; + private readonly Lazy _categoryTemplateService; + private readonly Lazy _productService; + private readonly Lazy _orderService; + private readonly Lazy _manufacturerService; + private readonly ICustomerService _customerService; + private readonly Lazy _addressService; + private readonly Lazy _countryService; + private readonly Lazy _shipmentService; + private readonly Lazy _genericAttributeService; + private readonly Lazy _emailAccountService; + private readonly Lazy _queuedEmailService; + private readonly Lazy _emailSender; + private readonly Lazy _deliveryTimeService; + private readonly Lazy _quantityUnitService; + + private readonly Lazy>_customerRepository; + private readonly Lazy> _subscriptionRepository; + private readonly Lazy> _orderRepository; + + private readonly Lazy _mediaSettings; + private readonly Lazy _contactDataSettings; + private readonly Lazy _customerSettings; + private readonly Lazy _catalogSettings; + + public DataExporter( + ICommonServices services, + IDbContext dbContext, + Lazy priceFormatter, + Lazy dateTimeHelper, + Lazy exportProfileService, + Lazy localizedEntityService, + Lazy languageService, + Lazy urlRecordService, + Lazy pictureService, + Lazy priceCalculationService, + Lazy currencyService, + Lazy taxService, + Lazy categoryService, + Lazy productAttributeParser, + Lazy productAttributeService, + Lazy productTemplateService, + Lazy categoryTemplateService, + Lazy productService, + Lazy orderService, + Lazy manufacturerService, + ICustomerService customerService, + Lazy addressService, + Lazy countryService, + Lazy shipmentService, + Lazy genericAttributeService, + Lazy emailAccountService, + Lazy queuedEmailService, + Lazy emailSender, + Lazy deliveryTimeService, + Lazy quantityUnitService, + Lazy> customerRepository, + Lazy> subscriptionRepository, + Lazy> orderRepository, + Lazy mediaSettings, + Lazy contactDataSettings, + Lazy customerSettings, + Lazy catalogSettings) + { + _services = services; + _dbContext = dbContext; + _priceFormatter = priceFormatter; + _dateTimeHelper = dateTimeHelper; + _exportProfileService = exportProfileService; + _localizedEntityService = localizedEntityService; + _languageService = languageService; + _urlRecordService = urlRecordService; + _pictureService = pictureService; + _priceCalculationService = priceCalculationService; + _currencyService = currencyService; + _taxService = taxService; + _categoryService = categoryService; + _productAttributeParser = productAttributeParser; + _productAttributeService = productAttributeService; + _productTemplateService = productTemplateService; + _categoryTemplateService = categoryTemplateService; + _productService = productService; + _orderService = orderService; + _manufacturerService = manufacturerService; + _customerService = customerService; + _addressService = addressService; + _countryService = countryService; + _shipmentService = shipmentService; + _genericAttributeService = genericAttributeService; + _emailAccountService = emailAccountService; + _queuedEmailService = queuedEmailService; + _emailSender = emailSender; + _deliveryTimeService = deliveryTimeService; + _quantityUnitService = quantityUnitService; + + _customerRepository = customerRepository; + _subscriptionRepository = subscriptionRepository; + _orderRepository = orderRepository; + + _mediaSettings = mediaSettings; + _contactDataSettings = contactDataSettings; + _customerSettings = customerSettings; + _catalogSettings = catalogSettings; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + #endregion + + #region Utilities + + private void SetProgress(DataExporterContext ctx, int loadedRecords) + { + try + { + if (!ctx.IsPreview && loadedRecords > 0) + { + int totalRecords = ctx.RecordsPerStore.Sum(x => x.Value); + + 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); + ctx.Request.ProgressValueSetter.Invoke(ctx.RecordCount, totalRecords, msg); + } + } + catch { } + } + + private void SetProgress(DataExporterContext ctx, string message) + { + try + { + if (!ctx.IsPreview && message.HasValue()) + { + ctx.Request.ProgressValueSetter.Invoke(0, 0, message); + } + } + catch { } + } + + 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); + + if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Customer) + return _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomers, customer); + + if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Order) + return _services.Permissions.Authorize(StandardPermissionProvider.ManageOrders, customer); + + if (ctx.Request.Provider.Value.EntityType == 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); + } + + try + { + // now again attach what is globally required + _dbContext.Attach(ctx.Request.Profile); + _dbContext.AttachRange(ctx.Stores.Values); + } + catch (Exception exception) + { + ctx.Log.Warning("Re-attaching entities failed.", exception); + } + + try + { + if (ctx.ProductExportContext != null) + ctx.ProductExportContext.Clear(); + + if (ctx.OrderExportContext != null) + ctx.OrderExportContext.Clear(); + + if (ctx.ManufacturerExportContext != null) + ctx.ManufacturerExportContext.Clear(); + + if (ctx.CategoryExportContext != null) + ctx.CategoryExportContext.Clear(); + + if (ctx.CustomerExportContext != null) + ctx.CustomerExportContext.Clear(); + } + catch { } + } + + private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, int pageIndex = 0) + { + 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; + + switch (ctx.Request.Provider.Value.EntityType) + { + case ExportEntityType.Product: + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter + ( + skip => GetProducts(ctx, skip), + 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, null, true), + 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) + ); + }, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + case ExportEntityType.Order: + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter + ( + skip => GetOrders(ctx, skip), + entities => + { + ctx.OrderExportContext = new OrderExportContext(entities, + x => _customerService.GetCustomersByIds(x), + x => _customerService.GetRewardPointsHistoriesByCustomerIds(x), + x => _addressService.Value.GetAddressByIds(x), + x => _orderService.Value.GetOrderItemsByOrderIds(x), + x => _shipmentService.Value.GetShipmentsByOrderIds(x) + ); + }, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + case ExportEntityType.Manufacturer: + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter + ( + skip => GetManufacturers(ctx, skip), + entities => + { + ctx.ManufacturerExportContext = new ManufacturerExportContext(entities, + x => _manufacturerService.Value.GetProductManufacturersByManufacturerIds(x), + x => _pictureService.Value.GetPicturesByIds(x) + ); + }, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + case ExportEntityType.Category: + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter + ( + skip => GetCategories(ctx, skip), + entities => + { + ctx.CategoryExportContext = new CategoryExportContext(entities, + x => _categoryService.Value.GetProductCategoriesByCategoryIds(x), + x => _pictureService.Value.GetPicturesByIds(x) + ); + }, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + case ExportEntityType.Customer: + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter + ( + skip => GetCustomers(ctx, skip), + entities => + { + ctx.CustomerExportContext = new CustomerExportContext(entities, + x => _genericAttributeService.Value.GetAttributesForEntity(x, "Customer") + ); + }, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + case ExportEntityType.NewsLetterSubscription: + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter + ( + skip => GetNewsLetterSubscriptions(ctx, skip), + null, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + default: + ctx.ExecuteContext.DataSegmenter = null; + break; + } + + 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())); + + try + { + ctx.ExecuteContext.DataStreamId = streamId; + + using (ctx.ExecuteContext.DataStream = new MemoryStream()) + { + if (method == "Execute") + { + ctx.Request.Provider.Value.Execute(ctx.ExecuteContext); + } + else if (method == "OnExecuted") + { + ctx.Request.Provider.Value.OnExecuted(ctx.ExecuteContext); + } + + if (ctx.IsFileBasedExport && path.HasValue() && ctx.ExecuteContext.DataStream.Length > 0) + { + if (!ctx.ExecuteContext.DataStream.CanSeek) + { + ctx.Log.Warning("Data stream seems to be closed!"); + } + + ctx.ExecuteContext.DataStream.Seek(0, SeekOrigin.Begin); + + using (_rwLock.GetWriteLock()) + using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + { + ctx.Log.Information("Creating file {0}.".FormatInvariant(path)); + ctx.ExecuteContext.DataStream.CopyTo(fileStream); + } + } + } + } + catch (Exception exception) + { + 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(); + } + finally + { + if (ctx.ExecuteContext.DataStream != null) + { + ctx.ExecuteContext.DataStream.Dispose(); + ctx.ExecuteContext.DataStream = null; + } + + if (ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard && ctx.IsFileBasedExport && path.HasValue()) + { + FileSystemHelper.Delete(path); + } + } + + return (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard); + } + + private bool Deploy(DataExporterContext ctx, string zipPath) + { + var allSucceeded = true; + var deployments = ctx.Request.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled); + + if (deployments.Count() == 0) + return false; + + var context = new ExportDeploymentContext + { + T = T, + Log = ctx.Log, + FolderContent = ctx.FolderContent, + ZipPath = zipPath, + CreateZipArchive = ctx.Request.Profile.CreateZipArchive + }; + + foreach (var deployment in deployments) + { + IFilePublisher publisher = null; + + context.Result = new DataDeploymentResult + { + LastExecutionUtc = DateTime.UtcNow + }; + + try + { + switch (deployment.DeploymentType) + { + case ExportDeploymentType.Email: + publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); + break; + case ExportDeploymentType.FileSystem: + publisher = new FileSystemFilePublisher(); + break; + case ExportDeploymentType.Ftp: + publisher = new FtpFilePublisher(); + break; + case ExportDeploymentType.Http: + publisher = new HttpFilePublisher(); + break; + case ExportDeploymentType.PublicFolder: + publisher = new PublicFolderPublisher(); + break; + } + + if (publisher != null) + { + publisher.Publish(context, deployment); + + if (!context.Result.Succeeded) + allSucceeded = false; + } + } + catch (Exception exception) + { + allSucceeded = false; + + if (context.Result != null) + { + context.Result.LastError = exception.ToAllMessages(); + } + + ctx.Log.Error("Deployment \"{0}\" of type {1} failed: {2}".FormatInvariant( + deployment.Name, deployment.DeploymentType.ToString(), exception.Message), exception); + } + + deployment.ResultInfo = XmlHelper.Serialize(context.Result); + + _exportProfileService.Value.UpdateExportDeployment(deployment); + } + + return allSucceeded; + } + + private void SendCompletionEmail(DataExporterContext ctx, string zipPath) + { + var emailAccount = _emailAccountService.Value.GetEmailAccountById(ctx.Request.Profile.EmailAccountId); + + if (emailAccount == null) + emailAccount = _emailAccountService.Value.GetDefaultEmailAccount(); + + 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(); + + var storeInfo = "{0} ({1})".FormatInvariant(ctx.Store.Name, ctx.Store.Url); + var intro =_services.Localization.GetResource("Admin.DataExchange.Export.CompletedEmail.Body", languageId).FormatInvariant(storeInfo); + var body = new StringBuilder(intro); + + if (ctx.Result.LastError.HasValue()) + { + body.AppendFormat("

{0}

", ctx.Result.LastError); + } + + if (ctx.IsFileBasedExport && File.Exists(zipPath)) + { + var fileName = Path.GetFileName(zipPath); + body.AppendFormat("

{2}

", downloadUrl, HttpUtility.UrlEncode(fileName), fileName); + } + + if (ctx.IsFileBasedExport && ctx.Result.Files.Any()) + { + body.Append("

"); + foreach (var file in ctx.Result.Files) + { + body.AppendFormat("

", downloadUrl, HttpUtility.UrlEncode(file.FileName), file.FileName); + } + body.Append("

"); + } + + message.From = new EmailAddress(emailAccount.Email, emailAccount.DisplayName); + + if (ctx.Request.Profile.CompletedEmailAddresses.HasValue()) + message.To.AddRange(ctx.Request.Profile.CompletedEmailAddresses.SplitSafe(",").Where(x => x.IsEmail()).Select(x => new EmailAddress(x))); + + if (message.To.Count == 0 && _contactDataSettings.Value.CompanyEmailAddress.HasValue()) + message.To.Add(new EmailAddress(_contactDataSettings.Value.CompanyEmailAddress)); + + if (message.To.Count == 0) + message.To.Add(new EmailAddress(emailAccount.Email, emailAccount.DisplayName)); + + message.Subject = _services.Localization.GetResource("Admin.DataExchange.Export.CompletedEmail.Subject", languageId) + .FormatInvariant(ctx.Request.Profile.Name); + + message.Body = body.ToString(); + + _emailSender.Value.SendEmail(smtpContext, message); + + //_queuedEmailService.Value.InsertQueuedEmail(new QueuedEmail + //{ + // From = emailAccount.Email, + // FromName = emailAccount.DisplayName, + // To = message.To.First().Address, + // Subject = message.Subject, + // Body = message.Body, + // CreatedOnUtc = DateTime.UtcNow, + // EmailAccountId = emailAccount.Id, + // SendManually = true + //}); + //_dbContext.SaveChanges(); + } + + #endregion + + #region Getting data + + 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); + + query = query.OrderByDescending(x => x.CreatedOnUtc); + } + else + { + query = ctx.Request.ProductQuery; + } + + if (skip > 0) + query = query.Skip(skip); + + if (take != int.MaxValue) + query = query.Take(take); + + return query; + } + + private List GetProducts(DataExporterContext ctx, int skip) + { + // we use ctx.EntityIdsPerSegment to avoid exporting products multiple times per segment\file (cause of associated products). + + var result = new List(); + + var products = GetProductQuery(ctx, skip, PageSize).ToList(); + + 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); + } + } + else if (product.ProductType == ProductType.GroupedProduct) + { + if (ctx.Projection.NoGroupedProducts && !ctx.IsPreview) + { + var associatedSearchContext = new ProductSearchContext + { + OrderBy = ProductSortingEnum.Position, + PageSize = int.MaxValue, + StoreId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), + VisibleIndividuallyOnly = true, + 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); + } + } + } + else + { + if (!ctx.EntityIdsPerSegment.Contains(product.Id)) + { + result.Add(product); + ctx.EntityIdsPerSegment.Add(product.Id); + } + } + } + } + + SetProgress(ctx, products.Count); + + return result; + } + + 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.OrderStatusIds, + ctx.Filter.PaymentStatusIds, + ctx.Filter.ShippingStatusIds, + null, + null, + null); + + if (ctx.Request.EntitiesToExport.Any()) + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + + query = query.OrderByDescending(x => x.CreatedOnUtc); + + if (skip > 0) + query = query.Skip(skip); + + if (take != int.MaxValue) + query = query.Take(take); + + return query; + } + + private List GetOrders(DataExporterContext ctx, int skip) + { + var orders = GetOrderQuery(ctx, skip, PageSize).ToList(); + + if (ctx.Projection.OrderStatusChange != ExportOrderStatusChange.None) + { + ctx.SetLoadedEntityIds(orders.Select(x => x.Id)); + } + + SetProgress(ctx, orders.Count); + + return orders; + } + + 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); + + if (ctx.Request.EntitiesToExport.Any()) + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + + query = query.OrderBy(x => x.DisplayOrder); + + if (skip > 0) + query = query.Skip(skip); + + if (take != int.MaxValue) + query = query.Take(take); + + return query; + } + + private List GetManufacturers(DataExporterContext ctx, int skip) + { + var manus = GetManufacturerQuery(ctx, skip, PageSize).ToList(); + + SetProgress(ctx, manus.Count); + + return manus; + } + + 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); + + 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); + + if (skip > 0) + query = query.Skip(skip); + + if (take != int.MaxValue) + query = query.Take(take); + + return query; + } + + private List GetCategories(DataExporterContext ctx, int skip) + { + var categories = GetCategoryQuery(ctx, skip, PageSize).ToList(); + + SetProgress(ctx, categories.Count); + + return categories; + } + + private IQueryable GetCustomerQuery(DataExporterContext ctx, int skip, int take) + { + var query = _customerRepository.Value.TableUntracked + .Expand(x => x.BillingAddress) + .Expand(x => x.ShippingAddress) + .Expand(x => x.Addresses.Select(y => y.Country)) + .Expand(x => x.Addresses.Select(y => y.StateProvince)) + .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.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.BillingCountryIds != null && ctx.Filter.BillingCountryIds.Length > 0) + 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.LastActivityFrom.HasValue) + { + var activityFrom = _dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.LastActivityFrom.Value, _dateTimeHelper.Value.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); + query = query.Where(x => activityTo >= x.LastActivityDateUtc); + } + + if (ctx.Filter.HasSpentAtLeastAmount.HasValue) + { + query = query + .Join(_orderRepository.Value.Table, x => x.Id, y => y.CustomerId, (x, y) => new { Customer = x, Order = y }) + .GroupBy(x => x.Customer.Id) + .Select(x => new + { + Customer = x.FirstOrDefault().Customer, + OrderTotal = x.Sum(y => y.Order.OrderTotal) + }) + .Where(x => x.OrderTotal >= ctx.Filter.HasSpentAtLeastAmount.Value) + .Select(x => x.Customer); + } + + if (ctx.Filter.HasPlacedAtLeastOrders.HasValue) + { + query = query + .Join(_orderRepository.Value.Table, x => x.Id, y => y.CustomerId, (x, y) => new { Customer = x, Order = y }) + .GroupBy(x => x.Customer.Id) + .Select(x => new + { + Customer = x.FirstOrDefault().Customer, + OrderCount = x.Count() + }) + .Where(x => x.OrderCount >= ctx.Filter.HasPlacedAtLeastOrders.Value) + .Select(x => x.Customer); + } + + if (ctx.Request.EntitiesToExport.Any()) + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + + query = query.OrderByDescending(x => x.CreatedOnUtc); + + if (skip > 0) + query = query.Skip(skip); + + if (take != int.MaxValue) + query = query.Take(take); + + return query; + } + + private List GetCustomers(DataExporterContext ctx, int skip) + { + var customers = GetCustomerQuery(ctx, skip, PageSize).ToList(); + + SetProgress(ctx, customers.Count); + + return customers; + } + + private IQueryable GetNewsLetterSubscriptionQuery(DataExporterContext ctx, int skip, int take) + { + var storeId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); + + var query = _subscriptionRepository.Value.TableUntracked; + + if (storeId > 0) + query = query.Where(x => x.StoreId == storeId); + + if (ctx.Filter.IsActiveSubscriber.HasValue) + query = query.Where(x => x.Active == ctx.Filter.IsActiveSubscriber.Value); + + if (ctx.Filter.CreatedFrom.HasValue) + { + var createdFrom = _dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.Value.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); + query = query.Where(x => createdTo >= x.CreatedOnUtc); + } + + 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); + + if (skip > 0) + query = query.Skip(skip); + + if (take != int.MaxValue) + query = query.Take(take); + + return query; + } + + private List GetNewsLetterSubscriptions(DataExporterContext ctx, int skip) + { + var subscriptions = GetNewsLetterSubscriptionQuery(ctx, skip, PageSize).ToList(); + + SetProgress(ctx, subscriptions.Count); + + return subscriptions; + } + + #endregion + + private List Init(DataExporterContext ctx, int? totalRecords = null) + { + // Init base things that are even required for preview. Init all other things (regular export) in ExportCoreOuter. + List result = null; + + if (ctx.Projection.CurrencyId.HasValue) + ctx.ContextCurrency = _currencyService.Value.GetCurrencyById(ctx.Projection.CurrencyId.Value); + else + ctx.ContextCurrency = _services.WorkContext.WorkingCurrency; + + if (ctx.Projection.CustomerId.HasValue) + ctx.ContextCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); + else + ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; + + if (ctx.Projection.LanguageId.HasValue) + ctx.ContextLanguage = _languageService.Value.GetLanguageById(ctx.Projection.LanguageId.Value); + else + ctx.ContextLanguage = _services.WorkContext.WorkingLanguage; + + ctx.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) + { + 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); + + ctx.Store = ctx.Stores.Values.FirstOrDefault(x => x.Id == (storeId ?? _services.StoreContext.CurrentStore.Id)); + + result = new List { ctx.Store }; + } + + // get total records for progress + 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); + } + + 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); + + ctx.Store = store; + + { + 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()); + + try + { + var uri = new Uri(store.Url); + logHead.AppendLine("Store:\t\t\t{0} (Id {1})".FormatInvariant(uri.DnsSafeHost.NaIfEmpty(), ctx.Store.Id)); + } + catch { } + + var customer = _services.WorkContext.CurrentCustomer; + logHead.Append("Executed by:\t\t" + (customer.Email.HasValue() ? customer.Email : customer.SystemName)); + + ctx.Log.Information(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 fileExtension = (ctx.Request.Provider.Value.FileExtension.HasValue() ? ctx.Request.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())); + } + + if (segmenter.TotalRecords <= 0) + { + ctx.Log.Information("There are no records to export."); + } + + while (ctx.ExecuteContext.Abort == DataExchangeAbortion.None && segmenter.HasData) + { + segmenter.RecordPerSegmentCount = 0; + ctx.ExecuteContext.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); + } + + if (CallProvider(ctx, null, "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 + }); + } + } + + 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); + } + + if (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard) + { + // always call OnExecuted + if (ctx.ExecuteContext.ExtraDataUnits.Count == 0) + ctx.ExecuteContext.ExtraDataUnits.Add(new ExportDataUnit()); + + ctx.ExecuteContext.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(); + } + } + } + + private void ExportCoreOuter(DataExporterContext ctx) + { + if (ctx.Request.Profile == null || !ctx.Request.Profile.Enabled) + return; + + var logPath = ctx.Request.Profile.GetExportLogPath(); + var zipPath = ctx.Request.Profile.GetExportZipPath(); + + FileSystemHelper.Delete(logPath); + FileSystemHelper.Delete(zipPath); + FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + + using (var logger = new TraceLogger(logPath)) + { + try + { + ctx.Log = logger; + ctx.ExecuteContext.Log = logger; + ctx.ProgressInfo = T("Admin.DataExchange.Export.ProgressInfo"); + + if (!ctx.Request.Provider.IsValid()) + throw new SmartException("Export aborted because the export provider is not valid."); + + if (!HasPermission(ctx)) + throw new SmartException("You do not have permission to perform the selected export."); + + foreach (var item in ctx.Request.CustomData) + { + ctx.ExecuteContext.CustomProperties.Add(item.Key, item.Value); + } + + if (ctx.Request.Profile.ProviderConfigData.HasValue()) + { + var configInfo = ctx.Request.Provider.Value.ConfigurationInfo; + if (configInfo != null) + { + ctx.ExecuteContext.ConfigurationData = XmlHelper.Deserialize(ctx.Request.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)) + { + 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) + { + ctx.Countries = _countryService.Value.GetAllCountries(true).ToDictionary(x => x.Id, x => x); + } + + if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Customer) + { + var subscriptionEmails = _subscriptionRepository.Value.TableUntracked + .Where(x => x.Active) + .Select(x => x.Email) + .Distinct() + .ToList(); + + ctx.NewsletterSubscriptions = new HashSet(subscriptionEmails, StringComparer.OrdinalIgnoreCase); + } + + var stores = Init(ctx); + + ctx.ExecuteContext.Language = ToDynamic(ctx, ctx.ContextLanguage); + ctx.ExecuteContext.Customer = ToDynamic(ctx, ctx.ContextCustomer); + ctx.ExecuteContext.Currency = ToDynamic(ctx, ctx.ContextCurrency); + + stores.ForEach(x => ExportCoreInner(ctx, x)); + } + + if (!ctx.IsPreview && ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard) + { + if (ctx.IsFileBasedExport) + { + if (ctx.Request.Profile.CreateZipArchive) + { + ZipFile.CreateFromDirectory(ctx.FolderContent, zipPath, CompressionLevel.Fastest, false); + } + + if (ctx.Request.Profile.Deployments.Any(x => x.Enabled)) + { + SetProgress(ctx, T("Common.Publishing")); + + var allDeploymentsSucceeded = Deploy(ctx, zipPath); + + if (allDeploymentsSucceeded && ctx.Request.Profile.Cleanup) + { + logger.Information("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)) + { + SendCompletionEmail(ctx, zipPath); + } + } + } + catch (Exception exception) + { + logger.ErrorsAll(exception); + ctx.Result.LastError = exception.ToString(); + } + finally + { + try + { + if (!ctx.IsPreview && ctx.Request.Profile.Id != 0) + { + ctx.Request.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); + + _exportProfileService.Value.UpdateExportProfile(ctx.Request.Profile); + } + } + catch (Exception exception) + { + logger.ErrorsAll(exception); + } + + DetachAllEntitiesAndClear(ctx); + + try + { + ctx.NewsletterSubscriptions.Clear(); + ctx.ProductTemplates.Clear(); + ctx.CategoryTemplates.Clear(); + ctx.Countries.Clear(); + ctx.Languages.Clear(); + ctx.QuantityUnits.Clear(); + ctx.DeliveryTimes.Clear(); + ctx.CategoryPathes.Clear(); + ctx.Categories.Clear(); + ctx.Stores.Clear(); + + ctx.Request.CustomData.Clear(); + + ctx.ExecuteContext.CustomProperties.Clear(); + ctx.ExecuteContext.Log = null; + ctx.Log = null; + } + catch (Exception exception) + { + logger.ErrorsAll(exception); + } + } + } + + if (ctx.IsPreview || ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard) + return; + + // post process order entities + if (ctx.EntityIdsLoaded.Any() && ctx.Request.Provider.Value.EntityType == ExportEntityType.Order && ctx.Projection.OrderStatusChange != ExportOrderStatusChange.None) + { + using (var logger = new TraceLogger(logPath)) + { + try + { + int? orderStatusId = null; + + 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()) + { + var entities = _orderRepository.Value.Table.Where(x => chunk.Contains(x.Id)).ToList(); + + entities.ForEach(x => x.OrderStatusId = (orderStatusId ?? x.OrderStatusId)); + + _dbContext.SaveChanges(); + } + } + + logger.Information("Updated order status for {0} order(s).".FormatInvariant(ctx.EntityIdsLoaded.Count())); + } + catch (Exception exception) + { + logger.ErrorsAll(exception); + ctx.Result.LastError = exception.ToString(); + } + } + } + } + + /// + /// The name of the public export folder + /// + public static string PublicFolder + { + get { return "Exchange"; } + } + + public static int PageSize + { + get { return 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) + { + var resultData = new List(); + var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); + + var ctx = new DataExporterContext(request, cancellation.Token, true); + + var unused = Init(ctx, totalRecords); + + 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())); + } + + while (segmenter.HasData) + { + segmenter.RecordPerSegmentCount = 0; + + while (segmenter.ReadNextSegment()) + { + resultData.AddRange(segmenter.CurrentSegment); + } + } + + DetachAllEntitiesAndClear(ctx); + } + + if (ctx.Result.LastError.HasValue()) + { + _services.Notifier.Error(ctx.Result.LastError); + } + + 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; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/DataDeploymentResult.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/DataDeploymentResult.cs new file mode 100644 index 0000000000..7fff7a46e5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/DataDeploymentResult.cs @@ -0,0 +1,23 @@ +using System; + +namespace SmartStore.Services.DataExchange.Export.Deployment +{ + [Serializable] + public class DataDeploymentResult + { + /// + /// Whether the deployment succeeded + /// + public bool Succeeded + { + get { return LastError.IsEmpty(); } + } + + public string LastError { get; set; } + + /// + /// Last execution date + /// + public DateTime LastExecutionUtc { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs new file mode 100644 index 0000000000..298d476c41 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; +using System.Linq; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Email; +using SmartStore.Core.IO; +using SmartStore.Core.Logging; +using SmartStore.Services.Messages; + +namespace SmartStore.Services.DataExchange.Export.Deployment +{ + public class EmailFilePublisher : IFilePublisher + { + private IEmailAccountService _emailAccountService; + private IQueuedEmailService _queuedEmailService; + + public EmailFilePublisher( + IEmailAccountService emailAccountService, + IQueuedEmailService queuedEmailService) + { + _emailAccountService = emailAccountService; + _queuedEmailService = queuedEmailService; + } + + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + { + var emailAccount = _emailAccountService.GetEmailAccountById(deployment.EmailAccountId); + var smtpContext = new SmtpContext(emailAccount); + var count = 0; + + foreach (var email in deployment.EmailAddresses.SplitSafe(",").Where(x => x.IsEmail())) + { + var queuedEmail = new QueuedEmail + { + From = emailAccount.Email, + FromName = emailAccount.DisplayName, + SendManually = false, + To = email, + Subject = deployment.EmailSubject.NaIfEmpty(), + CreatedOnUtc = DateTime.UtcNow, + EmailAccountId = deployment.EmailAccountId + }; + + foreach (var path in context.GetDeploymentFiles()) + { + var name = Path.GetFileName(path); + + queuedEmail.Attachments.Add(new QueuedEmailAttachment + { + StorageLocation = EmailAttachmentStorageLocation.Blob, + Data = File.ReadAllBytes(path), + Name = name, + MimeType = MimeTypes.MapNameToMimeType(name) + }); + } + + _queuedEmailService.InsertQueuedEmail(queuedEmail); + ++count; + } + + context.Log.Information("{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 new file mode 100644 index 0000000000..6bb56682e1 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs @@ -0,0 +1,22 @@ +using System.IO; +using SmartStore.Core.Domain; +using SmartStore.Core.Logging; +using SmartStore.Utilities; + +namespace SmartStore.Services.DataExchange.Export.Deployment +{ + public class FileSystemFilePublisher : IFilePublisher + { + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + { + var targetFolder = deployment.GetDeploymentFolder(true); + + 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); + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs new file mode 100644 index 0000000000..7a0954a1c7 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using SmartStore.Core.Domain; +using SmartStore.Core.Logging; + +namespace SmartStore.Services.DataExchange.Export.Deployment +{ + public class FtpFilePublisher : IFilePublisher + { + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + { + 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; + + foreach (var path in deploymentFiles) + { + var fileUrl = url.EnsureEndsWith("/") + Path.GetFileName(path); + + 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 (deployment.Username.HasValue()) + request.Credentials = new NetworkCredential(deployment.Username, deployment.Password); + + request.ContentLength = (new FileInfo(path)).Length; + + 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(); + + using (var response = (FtpWebResponse)request.GetResponse()) + { + var statusCode = (int)response.StatusCode; + + if (statusCode >= 200 && statusCode <= 299) + { + ++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)); + } + } + } + + context.Log.Information("{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 new file mode 100644 index 0000000000..4f0d9df8d8 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; + +namespace SmartStore.Services.DataExchange.Export.Deployment +{ + public class HttpFilePublisher : IFilePublisher + { + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + { + var succeededFiles = 0; + var url = deployment.Url; + + if (!url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) + url = "http://" + url; + + if (deployment.HttpTransmissionType == ExportHttpTransmissionType.MultipartFormDataPost) + { + var countFiles = 0; + ICredentials credentials = null; + + if (deployment.Username.HasValue()) + credentials = new NetworkCredential(deployment.Username, deployment.Password); + + using (var handler = new HttpClientHandler { Credentials = credentials }) + using (var client = new HttpClient(handler)) + using (var formData = new MultipartFormDataContent()) + { + foreach (var path in context.GetDeploymentFiles()) + { + byte[] fileData = File.ReadAllBytes(path); + formData.Add(new ByteArrayContent(fileData), "file {0}".FormatInvariant(++countFiles), Path.GetFileName(path)); + } + + var response = client.PostAsync(url, formData).Result; + + if (response.IsSuccessStatusCode) + { + succeededFiles = countFiles; + } + else if (response.Content != null) + { + context.Result.LastError = context.T("Admin.Common.HttpStatus", (int)response.StatusCode, response.StatusCode.ToString()); + + var content = response.Content.ReadAsStringAsync().Result; + + var msg = "Multipart form data upload failed. HTTP status {0} ({1}). Response: {2}".FormatInvariant( + (int)response.StatusCode, response.StatusCode.ToString(), content.NaIfEmpty().Truncate(2000, "...")); + + context.Log.Error(msg); + } + } + } + else + { + using (var webClient = new WebClient()) + { + if (deployment.Username.HasValue()) + webClient.Credentials = new NetworkCredential(deployment.Username, deployment.Password); + + foreach (var path in context.GetDeploymentFiles()) + { + webClient.UploadFile(url, path); + + ++succeededFiles; + } + } + } + + context.Log.Information("{0} file(s) successfully uploaded via HTTP ({1}).".FormatInvariant(succeededFiles, deployment.HttpTransmissionType.ToString())); + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs new file mode 100644 index 0000000000..1ae3301ec4 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.IO; +using SmartStore.Core.Domain; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; + +namespace SmartStore.Services.DataExchange.Export.Deployment +{ + public interface IFilePublisher + { + void Publish(ExportDeploymentContext context, ExportDeployment deployment); + } + + + public class ExportDeploymentContext + { + public Localizer T { get; set; } + public ILogger Log { get; set; } + + public string FolderContent { get; set; } + + public string ZipPath { get; set; } + public bool CreateZipArchive { get; set; } + + public DataDeploymentResult Result { get; set; } + + public IEnumerable GetDeploymentFiles() + { + if (!CreateZipArchive) + { + return System.IO.Directory.EnumerateFiles(FolderContent, "*", SearchOption.AllDirectories); + } + + if (File.Exists(ZipPath)) + { + return new string[] { ZipPath }; + } + + return new string[0]; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs new file mode 100644 index 0000000000..d438e808a2 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs @@ -0,0 +1,44 @@ +using System.IO; +using SmartStore.Core.Domain; +using SmartStore.Core.Logging; +using SmartStore.Utilities; + +namespace SmartStore.Services.DataExchange.Export.Deployment +{ + public class PublicFolderPublisher : IFilePublisher + { + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + { + var destinationFolder = deployment.GetDeploymentFolder(true); + + if (destinationFolder.IsEmpty()) + return; + + if (!System.IO.Directory.Exists(destinationFolder)) + { + System.IO.Directory.CreateDirectory(destinationFolder); + } + + if (context.CreateZipArchive) + { + if (File.Exists(context.ZipPath)) + { + var destinationFile = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); + + File.Copy(context.ZipPath, destinationFile, true); + + context.Log.Information("Copied zipped export data to " + destinationFile); + } + } + else + { + if (!FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder))) + { + context.Result.LastError = context.T("Admin.DataExchange.Export.Deployment.CopyFileFailed"); + } + + context.Log.Information("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 new file mode 100644 index 0000000000..1917084af2 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -0,0 +1,1327 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Web; +using SmartStore.ComponentModel; +using SmartStore.Core; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Common; +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.Messages; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Html; +using SmartStore.Services.Catalog; +using SmartStore.Services.DataExchange.Export.Events; +using SmartStore.Services.DataExchange.Export.Internal; +using SmartStore.Services.Localization; +using SmartStore.Services.Seo; + +namespace SmartStore.Services.DataExchange.Export +{ + public partial class DataExporter + { + private void PrepareProductDescription(DataExporterContext ctx, dynamic dynObject, Product product) + { + try + { + var languageId = (ctx.Projection.LanguageId ?? 0); + string description = ""; + + // description merging + if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.None) + { + // export empty description + } + else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ShortDescriptionOrNameIfEmpty) + { + description = dynObject.FullDescription; + + if (description.IsEmpty()) + description = dynObject.ShortDescription; + if (description.IsEmpty()) + description = dynObject.Name; + } + else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ShortDescription) + { + description = dynObject.ShortDescription; + } + else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.Description) + { + description = dynObject.FullDescription; + } + else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.NameAndShortDescription) + { + description = ((string)dynObject.Name).Grow((string)dynObject.ShortDescription, " "); + } + else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.NameAndDescription) + { + description = ((string)dynObject.Name).Grow((string)dynObject.FullDescription, " "); + } + else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription || + ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndDescription) + { + var productManus = ctx.ProductExportContext.ProductManufacturers.Load(product.Id); + + if (productManus != null && productManus.Any()) + description = productManus.First().Manufacturer.GetLocalized(x => x.Name, languageId, true, false); + + 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, " "); + } + + // 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); + + description = description.Grow(appendText.SafeGet(rnd), " "); + } + } + + // 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 + if (description.HasValue() && ctx.Projection.DescriptionToPlainText) + { + //Regex reg = new Regex("<[^>]+>", RegexOptions.IgnoreCase); + //description = HttpUtility.HtmlDecode(reg.Replace(description, "")); + + description = HtmlUtils.ConvertHtmlToPlainText(description); + description = HtmlUtils.StripTags(HttpUtility.HtmlDecode(description)); + } + + dynObject.FullDescription = description; + } + catch { } + } + + private decimal? ConvertPrice(DataExporterContext ctx, Product product, decimal? price) + { + if (price.HasValue) + { + if (ctx.Projection.ConvertNetToGrossPrices) + { + decimal taxRate; + price = _taxService.Value.GetProductPrice(product, price.Value, true, ctx.ContextCustomer, out taxRate); + } + + if (price != decimal.Zero) + { + price = _currencyService.Value.ConvertFromPrimaryStoreCurrency(price.Value, ctx.ContextCurrency, ctx.Store); + } + } + return price; + } + + private decimal CalculatePrice( + DataExporterContext ctx, + Product product, + ProductVariantAttributeCombination combination, + IList attributeValues) + { + var price = product.Price; + var priceCalculationContext = ctx.ProductExportContext as PriceCalculationContext; + + if (combination != null) + { + // price for attribute combination + var attributesTotalPriceBase = decimal.Zero; + + if (attributeValues != null) + { + attributeValues.Each(x => attributesTotalPriceBase += _priceCalculationService.Value.GetProductVariantAttributeValuePriceAdjustment(x)); + } + + price = _priceCalculationService.Value.GetFinalPrice(product, null, ctx.ContextCustomer, attributesTotalPriceBase, true, 1, null, priceCalculationContext); + } + else if (ctx.Projection.PriceType.HasValue) + { + // price for product + if (ctx.Projection.PriceType.Value == PriceDisplayType.LowestPrice) + { + 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); + } + } + + return ConvertPrice(ctx, product, price) ?? price; + } + + private List GetLocalized(DataExporterContext ctx, T entity, params Expression>[] keySelectors) + where T : BaseEntity, ILocalizedEntity + { + if (ctx.Languages.Count <= 1) + return null; + + var localized = new List(); + + var localeKeyGroup = typeof(T).Name; + var isSlugSupported = typeof(ISlugSupported).IsAssignableFrom(typeof(T)); + + foreach (var language in ctx.Languages) + { + var languageCulture = language.Value.LanguageCulture.EmptyNull().ToLower(); + + // add SeName + if (isSlugSupported) + { + var value = _urlRecordService.Value.GetActiveSlug(entity.Id, localeKeyGroup, language.Value.Id); + if (value.HasValue()) + { + dynamic exp = new HybridExpando(); + exp.Culture = languageCulture; + exp.LocaleKey = "SeName"; + exp.LocaleValue = value; + + 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); + } + + private dynamic ToDynamic(DataExporterContext ctx, Currency currency) + { + if (currency == null) + return null; + + dynamic result = new DynamicEntity(currency); + + result.Name = currency.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + result._Localized = GetLocalized(ctx, currency, x => x.Name); + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, Language language) + { + if (language == null) + return null; + + dynamic result = new DynamicEntity(language); + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, Country country) + { + if (country == null) + return null; + + dynamic result = new DynamicEntity(country); + + result.Name = country.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + result._Localized = GetLocalized(ctx, country, x => x.Name); + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, Address address) + { + if (address == null) + return null; + + dynamic result = new DynamicEntity(address); + + result.Country = ToDynamic(ctx, address.Country); + + if (address.StateProvinceId.GetValueOrDefault() > 0) + { + dynamic sp = new DynamicEntity(address.StateProvince); + + sp.Name = address.StateProvince.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + sp._Localized = GetLocalized(ctx, address.StateProvince, x => x.Name); + + result.StateProvince = sp; + } + else + { + result.StateProvince = null; + } + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, RewardPointsHistory points) + { + if (points == null) + return null; + + dynamic result = new DynamicEntity(points); + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, Customer customer) + { + 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; + + result._GenericAttributes = null; + result._HasNewsletterSubscription = false; + + result._FullName = null; + result._AvatarPictureUrl = null; + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, Store store) + { + if (store == null) + return null; + + dynamic result = new DynamicEntity(store); + + result.PrimaryStoreCurrency = ToDynamic(ctx, store.PrimaryStoreCurrency); + result.PrimaryExchangeRateCurrency = ToDynamic(ctx, store.PrimaryExchangeRateCurrency); + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, DeliveryTime deliveryTime) + { + if (deliveryTime == null) + return null; + + dynamic result = new DynamicEntity(deliveryTime); + + result.Name = deliveryTime.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + result._Localized = GetLocalized(ctx, deliveryTime, x => x.Name); + + return result; + } + + private void ToDeliveryTime(DataExporterContext ctx, dynamic parent, int? deliveryTimeId) + { + if (ctx.DeliveryTimes != null) + { + if (deliveryTimeId.HasValue && ctx.DeliveryTimes.ContainsKey(deliveryTimeId.Value)) + parent.DeliveryTime = ToDynamic(ctx, ctx.DeliveryTimes[deliveryTimeId.Value]); + else + parent.DeliveryTime = null; + } + } + + private dynamic ToDynamic(DataExporterContext ctx, QuantityUnit quantityUnit) + { + if (quantityUnit == null) + return null; + + dynamic result = new DynamicEntity(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._Localized = GetLocalized(ctx, quantityUnit, + x => x.Name, + x => x.Description); + + return result; + } + + private void ToQuantityUnit(DataExporterContext ctx, dynamic parent, int? quantityUnitId) + { + if (ctx.QuantityUnits != null) + { + if (quantityUnitId.HasValue && ctx.QuantityUnits.ContainsKey(quantityUnitId.Value)) + parent.QuantityUnit = ToDynamic(ctx, ctx.QuantityUnits[quantityUnitId.Value]); + else + parent.QuantityUnit = null; + } + } + + private dynamic ToDynamic(DataExporterContext ctx, Picture picture, int thumbPictureSize, int detailsPictureSize) + { + if (picture == null) + return null; + + 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); + + result._ThumbLocalPath = _pictureService.Value.GetThumbLocalPath(picture); + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, ProductVariantAttribute pva) + { + if (pva == null) + return null; + + dynamic result = new DynamicEntity(pva); + + dynamic attribute = new DynamicEntity(pva.ProductAttribute); + + 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); + + attribute.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); + + return dyn; + }) + .ToList(); + + attribute._Localized = GetLocalized(ctx, pva.ProductAttribute, + x => x.Name, + x => x.Description); + + result.Attribute = attribute; + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, ProductVariantAttributeCombination pvac) + { + if (pvac == null) + return null; + + dynamic result = new DynamicEntity(pvac); + + ToDeliveryTime(ctx, result, pvac.DeliveryTimeId); + ToQuantityUnit(ctx, result, pvac.QuantityUnitId); + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, Manufacturer manufacturer) + { + if (manufacturer == null) + return null; + + dynamic result = new DynamicEntity(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._Localized = GetLocalized(ctx, 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; + + dynamic result = new DynamicEntity(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._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); + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, Product product) + { + if (product == null) + return null; + + dynamic result = new DynamicEntity(product); + + 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.TierPrices = null; + result.ProductAttributes = null; + result.ProductAttributeCombinations = null; + result.ProductPictures = null; + result.ProductCategories = null; + result.ProductManufacturers = null; + result.ProductTags = null; + result.ProductSpecificationAttributes = null; + result.ProductBundleItems = null; + + 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); + + return result; + } + + private dynamic ToDynamic( + DataExporterContext ctx, + Product product, + ICollection combinations, + ProductVariantAttributeCombination combination) + { + 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); + + var variantAttributes = (combination != null ? _productAttributeParser.Value.DeserializeProductVariantAttributes(combination.AttributesXml) : null); + var variantAttributeValues = (combination != null ? _productAttributeParser.Value.ParseProductVariantAttributeValues(variantAttributes, productAttributes) : null); + + if (pictureIds.Length > 0) + 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; + + if (combination == null) + dynObject._UniqueId = product.Id.ToString(); + else + dynObject._UniqueId = string.Concat(product.Id, "-", combination.Id); + + dynObject.Price = CalculatePrice(ctx, product, combination, variantAttributeValues); + + 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)) + { + dynObject._AttributeCombination = variantAttributes; + dynObject._AttributeCombinationValues = variantAttributeValues; + } + + if (ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + { + var valueNames = variantAttributeValues + .Select(x => x.GetLocalized(y => y.Name, languageId, true, false)) + .ToList(); + + dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", valueNames), " "); + } + + 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); + } + } + + if (ctx.Categories.Count > 0) + { + 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() + ); + } + + 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); + + return dyn; + }) + .ToList(); + + dynObject.ProductManufacturers = productManufacturers + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + dynamic dyn = new DynamicEntity(x); + + 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; + + return dyn; + }) + .ToList(); + + dynObject.ProductCategories = productCategories + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + dynamic dyn = new DynamicEntity(x); + + dyn.Category = ToDynamic(ctx, x.Category); + + if (x.Category != null && x.Category.PictureId.HasValue) + dyn.Category.Picture = ToDynamic(ctx, x.Category.Picture, _mediaSettings.Value.CategoryThumbPictureSize, _mediaSettings.Value.CategoryThumbPictureSize); + + if (dynObject._CategoryName == null) + dynObject._CategoryName = (string)dyn.Category.Name; + + return dyn; + }) + .ToList(); + + dynObject.ProductAttributes = productAttributes + .OrderBy(x => x.DisplayOrder) + .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)) + { + var assignedPicture = productPictures.FirstOrDefault(y => y.PictureId == pictureId); + if (assignedPicture != null && assignedPicture.Picture != null) + { + assignedPictures.Add(ToDynamic(ctx, assignedPicture.Picture, _mediaSettings.Value.ProductThumbPictureSize, pictureSize)); + } + } + + dyn.Pictures = assignedPictures; + + return dyn; + }) + .ToList(); + + if (product.HasTierPrices) + { + var tierPrices = ctx.ProductExportContext.TierPrices.Load(product.Id) + .RemoveDuplicatedQuantities(); + + dynObject.TierPrices = tierPrices + .Select(x => + { + dynamic dyn = new DynamicEntity(x); + + return dyn; + }) + .ToList(); + } + + if (product.HasDiscountsApplied) + { + var appliedDiscounts = ctx.ProductExportContext.AppliedDiscounts.Load(product.Id); + + dynObject.AppliedDiscounts = appliedDiscounts + .Select(x => ToDynamic(ctx, x)) + .ToList(); + } + + dynObject.ProductTags = productTags + .Select(x => + { + dynamic dyn = new DynamicEntity(x); + + dyn.Name = x.GetLocalized(y => y.Name, languageId, true, false); + dyn.SeName = x.GetSeName(languageId); + dyn._Localized = GetLocalized(ctx, x, y => y.Name); + + return dyn; + }) + .ToList(); + + dynObject.ProductSpecificationAttributes = specificationAttributes + .Select(x => ToDynamic(ctx, x)) + .ToList(); + + if (product.ProductType == ProductType.BundledProduct) + { + var bundleItems = ctx.ProductExportContext.ProductBundleItems.Load(perfLoadId); + + dynObject.ProductBundleItems = bundleItems + .Select(x => + { + dynamic dyn = new DynamicEntity(x); + + 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); + + return dyn; + }) + .ToList(); + } + + #endregion + + #region more attribute controlled data + + if (ctx.Supports(ExportFeatures.CanProjectDescription)) + { + PrepareProductDescription(ctx, dynObject, product); + } + + 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; + + 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); + } + 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); + } + else + { + dynObject._MainPictureUrl = null; + dynObject._MainPictureRelativeUrl = null; + } + } + + if (ctx.Supports(ExportFeatures.UsesSkuAsMpnFallback) && product.ManufacturerPartNumber.IsEmpty()) + { + dynObject.ManufacturerPartNumber = product.Sku; + } + + if (ctx.Supports(ExportFeatures.OffersShippingTimeFallback)) + { + dynamic deliveryTime = dynObject.DeliveryTime; + dynObject._ShippingTime = (deliveryTime == null ? ctx.Projection.ShippingTime : deliveryTime.Name); + } + + if (ctx.Supports(ExportFeatures.OffersShippingCostsFallback)) + { + dynObject._FreeShippingThreshold = ctx.Projection.FreeShippingThreshold; + + if (product.IsFreeShipping || (ctx.Projection.FreeShippingThreshold.HasValue && (decimal)dynObject.Price >= ctx.Projection.FreeShippingThreshold.Value)) + dynObject._ShippingCosts = decimal.Zero; + else + dynObject._ShippingCosts = ctx.Projection.ShippingCosts; + } + + if (ctx.Supports(ExportFeatures.UsesOldPrice)) + { + if (product.OldPrice != decimal.Zero && product.OldPrice != (decimal)dynObject.Price && !(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) + { + if (ctx.Projection.ConvertNetToGrossPrices) + { + decimal taxRate; + dynObject._OldPrice = _taxService.Value.GetProductPrice(product, product.OldPrice, true, ctx.ContextCustomer, out taxRate); + } + else + { + dynObject._OldPrice = product.OldPrice; + } + } + else + { + dynObject._OldPrice = null; + } + } + + if (ctx.Supports(ExportFeatures.UsesSpecialPrice)) + { + dynObject._SpecialPrice = null; // special price which is valid now + dynObject._FutureSpecialPrice = null; // special price which is valid now and in future + dynObject._RegularPrice = null; // price as if a special price would not exist + + if (!(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) + { + if (product.SpecialPrice.HasValue && product.SpecialPriceEndDateTimeUtc.HasValue) + { + var endDate = DateTime.SpecifyKind(product.SpecialPriceEndDateTimeUtc.Value, DateTimeKind.Utc); + if (endDate > DateTime.UtcNow) + { + dynObject._FutureSpecialPrice = ConvertPrice(ctx, product, product.SpecialPrice.Value); + } + } + + var specialPrice = _priceCalculationService.Value.GetSpecialPrice(product); + + dynObject._SpecialPrice = ConvertPrice(ctx, product, specialPrice); + + if (specialPrice.HasValue || dynObject._FutureSpecialPrice != null) + { + decimal tmpSpecialPrice = product.SpecialPrice.Value; + product.SpecialPrice = null; + + dynObject._RegularPrice = CalculatePrice(ctx, product, combination, variantAttributeValues); + + product.SpecialPrice = tmpSpecialPrice; + } + } + } + + #endregion + + return dynObject; + } + + private dynamic ToDynamic(DataExporterContext ctx, Order order) + { + 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.Customer = null; + result.BillingAddress = null; + result.ShippingAddress = null; + result.Store = null; + result.Shipments = null; + + result.RedeemedRewardPointsEntry = ToDynamic(ctx, order.RedeemedRewardPointsEntry); + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, OrderItem orderItem) + { + 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; + } + + private dynamic ToDynamic(DataExporterContext ctx, Shipment shipment) + { + if (shipment == null) + return null; + + dynamic result = new DynamicEntity(shipment); + + result.ShipmentItems = shipment.ShipmentItems + .Select(x => + { + dynamic exp = new DynamicEntity(x); + + return exp; + }) + .ToList(); + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, Discount discount) + { + if (discount == null) + return null; + + dynamic result = new DynamicEntity(discount); + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, ProductSpecificationAttribute psa) + { + if (psa == null) + return null; + + var option = psa.SpecificationAttributeOption; + + dynamic result = new DynamicEntity(psa); + + dynamic dynAttribute = new DynamicEntity(option.SpecificationAttribute); + + dynAttribute.Name = option.SpecificationAttribute.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + dynAttribute._Localized = GetLocalized(ctx, option.SpecificationAttribute, x => x.Name); + + dynamic 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.SpecificationAttribute = dynAttribute; + + result.SpecificationAttributeOption = dynOption; + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, GenericAttribute genericAttribute) + { + if (genericAttribute == null) + return null; + + dynamic result = new DynamicEntity(genericAttribute); + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, NewsLetterSubscription subscription) + { + if (subscription == null) + return null; + + dynamic result = new DynamicEntity(subscription); + + return result; + } + + + private List Convert(DataExporterContext ctx, Product product) + { + var result = new List(); + + var combinations = ctx.ProductExportContext.AttributeCombinations.Load(product.Id); + + if (!ctx.IsPreview && ctx.Projection.AttributeCombinationAsProduct && combinations.Where(x => x.IsActive).Count() > 0) + { + //var productType = typeof(Product); + //var productValues = new Dictionary(); + var dbContext = _dbContext as DbContext; + + foreach (var combination in 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; + _dbContext.DetachEntity(product); + + var dynObject = ToDynamic(ctx, productClone, combinations, combination); + result.Add(dynObject); + } + } + else + { + var dynObject = ToDynamic(ctx, product, combinations, null); + result.Add(dynObject); + } + + if (result.Any()) + { + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = result.First(), + EntityType = ExportEntityType.Product, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + } + + return result; + } + + private List Convert(DataExporterContext ctx, Order order) + { + var result = new List(); + + if (!ctx.IsPreview) + { + ctx.OrderExportContext.Addresses.Collect(order.ShippingAddressId.HasValue ? order.ShippingAddressId.Value : 0); + ctx.OrderExportContext.Addresses.Load(order.BillingAddressId); + } + + var perfLoadId = (ctx.IsPreview ? 0 : order.Id); + 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); + + 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)); + + dynObject.Customer.RewardPointsHistory = rewardPointsHistories + .Select(x => ToDynamic(ctx, x)) + .ToList(); + + if (rewardPointsHistories.Count > 0) + { + dynObject.Customer._RewardPointsBalance = rewardPointsHistories + .OrderByDescending(x => x.CreatedOnUtc) + .ThenByDescending(x => x.Id) + .FirstOrDefault() + .PointsBalance; + } + + if (ctx.OrderExportContext.Addresses.ContainsKey(order.BillingAddressId)) + { + dynObject.BillingAddress = ToDynamic(ctx, ctx.OrderExportContext.Addresses[order.BillingAddressId].FirstOrDefault()); + } + + if (order.ShippingAddressId.HasValue && ctx.OrderExportContext.Addresses.ContainsKey(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; + }) + .ToList(); + + dynObject.Shipments = shipments + .Select(x => ToDynamic(ctx, x)) + .ToList(); + + result.Add(dynObject); + + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.Order, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + + return result; + } + + private List Convert(DataExporterContext ctx, Manufacturer manufacturer) + { + var result = new List(); + + var productManufacturers = ctx.ManufacturerExportContext.ProductManufacturers.Load(manufacturer.Id); + + dynamic dynObject = ToDynamic(ctx, manufacturer); + + if (!ctx.IsPreview && manufacturer.PictureId.HasValue) + { + var numberOfPictures = (ctx.Projection.NumberOfPictures ?? int.MaxValue); + var pictures = ctx.ManufacturerExportContext.Pictures.Load(manufacturer.PictureId.Value).Take(numberOfPictures); + + if (pictures.Any()) + dynObject.Picture = ToDynamic(ctx, pictures.First(), _mediaSettings.Value.ManufacturerThumbPictureSize, _mediaSettings.Value.ManufacturerThumbPictureSize); + } + + dynObject.ProductManufacturers = productManufacturers + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + dynamic dyn = new DynamicEntity(x); + + return dyn; + }) + .ToList(); + + result.Add(dynObject); + + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.Manufacturer, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + + return result; + } + + private List Convert(DataExporterContext ctx, Category category) + { + var result = new List(); + + var productCategories = ctx.CategoryExportContext.ProductCategories.Load(category.Id); + + dynamic dynObject = ToDynamic(ctx, category); + + if (!ctx.IsPreview && category.PictureId.HasValue) + { + var numberOfPictures = (ctx.Projection.NumberOfPictures ?? int.MaxValue); + var pictures = ctx.CategoryExportContext.Pictures.Load(category.PictureId.Value).Take(numberOfPictures); + + if (pictures.Any()) + dynObject.Picture = ToDynamic(ctx, pictures.First(), _mediaSettings.Value.CategoryThumbPictureSize, _mediaSettings.Value.CategoryThumbPictureSize); + } + + dynObject.ProductCategories = productCategories + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + dynamic dyn = new DynamicEntity(x); + + return dyn; + }) + .ToList(); + + result.Add(dynObject); + + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.Category, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + + return result; + } + + 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); + + dynamic dynObject = ToDynamic(ctx, customer); + + dynObject.BillingAddress = ToDynamic(ctx, customer.BillingAddress); + dynObject.ShippingAddress = ToDynamic(ctx, customer.ShippingAddress); + + dynObject.Addresses = customer.Addresses + .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, " "); + + if (_customerSettings.Value.AllowCustomersToUploadAvatars) + { + var pictureId = genericAttributes.FirstOrDefault(x => x.Key == SystemCustomerAttributeNames.AvatarPictureId); + 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); + } + } + + result.Add(dynObject); + + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.Customer, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + + return result; + } + + private List Convert(DataExporterContext ctx, NewsLetterSubscription subscription) + { + var result = new List(); + dynamic dynObject = ToDynamic(ctx, subscription); + result.Add(dynObject); + + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.NewsLetterSubscription, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + + + return result; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Events/RowExportingEvent.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Events/RowExportingEvent.cs new file mode 100644 index 0000000000..0b96526e00 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Events/RowExportingEvent.cs @@ -0,0 +1,35 @@ +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Services.DataExchange.Export.Events +{ + // TODO: Another event message must be implemented, say 'ColumnsBuildingEvent' + // The consumer of this event (most likely a plugin) could push a list of specific column headers + // into the global export definition. + + public class RowExportingEvent + { + public dynamic Row + { + get; + internal set; + } + + public ExportEntityType EntityType + { + get; + internal set; + } + + public DataExportRequest ExportRequest + { + get; + internal set; + } + + public ExportExecuteContext ExecuteContext + { + get; + internal set; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportConfigurationInfo.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportConfigurationInfo.cs new file mode 100644 index 0000000000..2cfb17796d --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportConfigurationInfo.cs @@ -0,0 +1,25 @@ +using System; + +namespace SmartStore.Services.DataExchange.Export +{ + /// + /// Serves information about export provider specific configuration + /// + public class ExportConfigurationInfo + { + /// + /// The partial view name for the configuration + /// + public string PartialViewName { get; set; } + + /// + /// Type of the view model + /// + public Type ModelType { get; set; } + + /// + /// Callback to initialize the view model. Can be null. + /// + public Action Initialize { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportDataSegmenter.cs new file mode 100644 index 0000000000..c84182c6e0 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportDataSegmenter.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core; + +namespace SmartStore.Services.DataExchange.Export +{ + public interface IExportDataSegmenterConsumer + { + /// + /// Total number of records + /// + int TotalRecords { get; } + + /// + /// Gets current data segment + /// + IReadOnlyCollection CurrentSegment { get; } + + /// + /// Reads the next segment + /// + /// + bool ReadNextSegment(); + } + + internal interface IExportDataSegmenterProvider : IExportDataSegmenterConsumer, IDisposable + { + /// + /// Whether there is data available + /// + bool HasData { get; } + + /// + /// Record per segment counter + /// + int RecordPerSegmentCount { get; set; } + } + + public class ExportDataSegmenter : IExportDataSegmenterProvider where T : BaseEntity + { + private Func> _load; + private Action> _loaded; + private Func> _convert; + + private int _offset; + private int _take; + private int _limit; + private int _recordsPerSegment; + private int _totalRecords; + + private int _skip; + private int _countRecords; + + private Queue _data; + + public ExportDataSegmenter( + Func> load, + Action> loaded, + Func> convert, + int offset, + int take, + int limit, + int recordsPerSegment, + int totalRecords) + { + _load = load; + _loaded = loaded; + _convert = convert; + _offset = offset; + _take = take; + _limit = limit; + _recordsPerSegment = recordsPerSegment; + _totalRecords = totalRecords; + + _skip = _offset; + } + + /// + /// Total number of records + /// + public int TotalRecords + { + get + { + var total = Math.Max(_totalRecords - _offset, 0); + + if (_limit > 0 && _limit < total) + return _limit; + + return total; + } + } + + /// + /// Number of processed records + /// + public int RecordCount + { + get { return _countRecords; } + } + + /// + /// Record per segment counter + /// + public int RecordPerSegmentCount { get; set; } + + /// + /// Whether there is data available + /// + public bool HasData + { + get + { + if (_limit > 0 && _countRecords >= _limit) + return false; + + if (_data != null && _data.Count > 0) + return true; + + if (_skip >= _totalRecords) + return false; + + return true; + } + } + + /// + /// Gets current data segment + /// + public IReadOnlyCollection CurrentSegment + { + get + { + T entity; + var records = new List(); + + while (_data.Count > 0 && (entity = _data.Dequeue()) != null) + { + _convert(entity).Each(x => records.Add(x)); + + if (++_countRecords >= _limit && _limit > 0) + return records; + + if (++RecordPerSegmentCount >= _recordsPerSegment && _recordsPerSegment > 0) + return records; + } + + return records; + } + } + + /// + /// Reads the next segment + /// + /// + 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); + } + + /// + /// Dispose and reset segmenter instance + /// + public void Dispose() + { + if (_data != null) + _data.Clear(); + + _skip = _offset; + _countRecords = 0; + RecordPerSegmentCount = 0; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs new file mode 100644 index 0000000000..d5f2860ff8 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; + +namespace SmartStore.Services.DataExchange.Export +{ + public class ExportExecuteContext + { + private DataExportResult _result; + private CancellationToken _cancellation; + private DataExchangeAbortion _providerAbort; + + internal ExportExecuteContext(DataExportResult result, CancellationToken cancellation, string folder) + { + _result = result; + _cancellation = cancellation; + Folder = folder; + ExtraDataUnits = new List(); + CustomProperties = new Dictionary(); + } + + /// + /// Provides the data to be exported + /// + public IExportDataSegmenterConsumer DataSegmenter { get; set; } + + /// + /// The store context to be used for the export + /// + public dynamic Store { get; internal set; } + + /// + /// The customer context to be used for the export + /// + public dynamic Customer { get; internal set; } + + /// + /// The currency context to be used for the export + /// + public dynamic Currency { get; internal set; } + + /// + /// The language context to be used for the export + /// + public dynamic Language { get; internal set; } + + /// + /// Projection data + /// + public ExportProjection Projection { get; internal set; } + + /// + /// To log information into the export log file + /// + public ILogger Log { get; internal set; } + + /// + /// Indicates whether and how to abort the export + /// + public DataExchangeAbortion Abort + { + get + { + if (_cancellation.IsCancellationRequested || IsMaxFailures) + return DataExchangeAbortion.Hard; + + return _providerAbort; + } + set + { + _providerAbort = value; + } + } + + public bool IsMaxFailures + { + get { return RecordsFailed > 11; } + } + + /// + /// Identifier of current data stream. Can be null. + /// + public string DataStreamId { get; set; } + + /// + /// Stream used to write data to + /// + public Stream DataStream { get; internal set; } + + /// + /// List with extra data units/streams required by provider + /// + public List ExtraDataUnits { get; private set; } + + /// + /// The maximum allowed file name length + /// + public int MaxFileNameLength { get; internal set; } + + /// + /// The name of the current export file + /// + public string FileName { get; internal set; } + + /// + /// The path of the export content folder + /// + public string Folder { get; private set; } + + /// + /// Whether the profile has a public deployment into "Exchange" folder + /// + public bool HasPublicDeployment { get; internal set; } + + /// + /// The local path to the public export folder "Exchange". null if the profile has no public deployment. + /// + public string PublicFolderPath { get; internal set; } + + /// + /// Provider specific configuration data + /// + public object ConfigurationData { get; internal set; } + + /// + /// Use this dictionary for any custom data required along the export + /// + public Dictionary CustomProperties { get; set; } + + /// + /// Number of successful processed records + /// + public int RecordsSucceeded { get; set; } + + /// + /// Number of failed records + /// + public int RecordsFailed { get; set; } + + /// + /// Processes an exception that occurred while exporting a record + /// + /// Exception + public void RecordException(Exception exception, int entityId) + { + ++RecordsFailed; + + Log.Error("Error while processing record with id {0}: {1}".FormatInvariant(entityId, exception.ToAllMessages()), exception); + + if (IsMaxFailures) + _result.LastError = exception.ToString(); + } + + public ProgressValueSetter ProgressValueSetter { get; internal set; } + + /// + /// Allows to set a progress message + /// + /// Output message + public void SetProgress(string message) + { + if (ProgressValueSetter != null && message.HasValue()) + { + try + { + ProgressValueSetter.Invoke(0, 0, message); + } + catch { } + } + } + } + + public class ExportDataUnit + { + /// + /// Your Id to identify this stream within a list of streams + /// + public string Id { get; set; } + + /// + /// Stream used to write data to + /// + public Stream DataStream { get; internal set; } + + /// + /// The name of the file to be created + /// + public string FileName { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs new file mode 100644 index 0000000000..0371702eec --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Web; +using SmartStore.Core; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Plugins; +using SmartStore.Services.Localization; +using SmartStore.Utilities; + +namespace SmartStore.Services.DataExchange.Export +{ + public static class ExportExtensions + { + /// + /// Returns a value indicating whether the export provider is valid + /// + /// Export provider + /// true provider is valid, false provider is invalid. + public static bool IsValid(this Provider provider) + { + return provider != null && provider.Value != null; + } + + /// + /// Gets the localized friendly name or the system name as fallback + /// + /// Export provider + /// Localization service + /// Provider name + public static string GetName(this Provider provider, ILocalizationService localizationService) + { + var systemName = provider.Metadata.SystemName; + var resourceName = provider.Metadata.ResourceKeyPattern.FormatInvariant(systemName, "FriendlyName"); + var name = localizationService.GetResource(resourceName, 0, false, systemName, true); + + return (name.IsEmpty() ? systemName : name); + } + + /// + /// Get temporary folder for an export profile + /// + /// Export profile + /// 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" : "")); + + if (create && !System.IO.Directory.Exists(path)) + System.IO.Directory.CreateDirectory(path); + + 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"); + } + + /// + /// Gets the ZIP path for a profile + /// + /// Export profile + /// ZIP file path + public static string GetExportZipPath(this ExportProfile profile) + { + var name = (new DirectoryInfo(profile.FolderName)).Name; + if (name.IsEmpty()) + name = "ExportData"; + + return Path.Combine(profile.GetExportFolder(), name.ToValidFileName() + ".zip"); + } + + /// + /// Gets existing export files for an export profile + /// + /// Export profile + /// Export provider + /// List of file names + public static IEnumerable GetExportFiles(this ExportProfile profile, Provider provider) + { + var exportFolder = profile.GetExportFolder(true); + + if (System.IO.Directory.Exists(exportFolder) && provider.Value.FileExtension.HasValue()) + { + var filter = "*.{0}".FormatInvariant(provider.Value.FileExtension.ToLower()); + + return System.IO.Directory.EnumerateFiles(exportFolder, filter, SearchOption.AllDirectories).OrderBy(x => x); + } + + return Enumerable.Empty(); + } + + /// + /// Get number of existing export files + /// + /// Export profile + /// Export provider + /// Number of export files + public static int GetExportFileCount(this ExportProfile profile, Provider provider) + { + var result = profile.GetExportFiles(provider).Count(); + + if (File.Exists(profile.GetExportZipPath())) + ++result; + + return result; + } + + /// + /// Resolves the file name pattern for an export profile + /// + /// Export profile + /// Store + /// One based file index + /// The maximum length of the file name + /// Resolved file name pattern + public static string ResolveFileNamePattern(this ExportProfile profile, Store store, int fileIndex, int maxFileNameLength) + { + var sb = new StringBuilder(profile.FileNamePattern); + + sb.Replace("%Profile.Id%", profile.Id.ToString()); + sb.Replace("%Profile.FolderName%", profile.FolderName); + 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)); + + var result = sb.ToString() + .ToValidFileName("") + .Truncate(maxFileNameLength); + + return result; + } + + /// + /// Get path of the deployment folder + /// + /// Export deployment + /// Deployment folder path + public static string GetDeploymentFolder(this ExportDeployment deployment, bool create = false) + { + if (deployment == null) + return null; + + string path = null; + + if (deployment.DeploymentType == ExportDeploymentType.PublicFolder) + { + if (deployment.SubFolder.HasValue()) + path = Path.Combine(HttpRuntime.AppDomainAppPath, DataExporter.PublicFolder, deployment.SubFolder); + else + path = Path.Combine(HttpRuntime.AppDomainAppPath, DataExporter.PublicFolder); + } + else if (deployment.DeploymentType == ExportDeploymentType.FileSystem) + { + if (deployment.FileSystemPath.IsEmpty()) + return null; + + if (Path.IsPathRooted(deployment.FileSystemPath)) + { + path = deployment.FileSystemPath; + } + else + { + path = FileSystemHelper.ValidateRootPath(deployment.FileSystemPath); + path = CommonHelper.MapPath(path); + } + } + + if (create && !System.IO.Directory.Exists(path)) + System.IO.Directory.CreateDirectory(path); + + return path; + } + + /// + /// Get url of the public folder and take filtering and projection into consideration + /// + /// Export deployment + /// Common services + /// Store entity + /// Absolute URL of the public folder (always ends with /) or null + public static string GetPublicFolderUrl(this ExportDeployment deployment, ICommonServices services, Store store = null) + { + if (deployment != null && deployment.DeploymentType == ExportDeploymentType.PublicFolder) + { + if (store == null) + { + var filter = XmlHelper.Deserialize(deployment.Profile.Filtering); + var storeId = filter.StoreId; + + if (storeId == 0) + { + var projection = XmlHelper.Deserialize(deployment.Profile.Projection); + storeId = (projection.StoreId ?? 0); + } + + store = (storeId == 0 ? services.StoreContext.CurrentStore : services.StoreService.GetStoreById(storeId)); + } + + var url = string.Concat( + store.Url.EnsureEndsWith("/"), + DataExporter.PublicFolder.EnsureEndsWith("/"), + deployment.SubFolder.HasValue() ? deployment.SubFolder.EnsureEndsWith("/") : "" + ); + + return url; + } + + return null; + } + + /// + /// Get icon class for a deployment type + /// + /// Deployment type + /// Icon class + public static string GetIconClass(this ExportDeploymentType type) + { + switch (type) + { + case ExportDeploymentType.FileSystem: + return "fa-folder-open-o"; + case ExportDeploymentType.Email: + return "fa-envelope-o"; + case ExportDeploymentType.Http: + return "fa-globe"; + case ExportDeploymentType.Ftp: + return "fa-files-o"; + case ExportDeploymentType.PublicFolder: + return "fa-unlock"; + default: + return "fa-question"; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportFeaturesAttribute.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportFeaturesAttribute.cs new file mode 100644 index 0000000000..eee6f1554e --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportFeaturesAttribute.cs @@ -0,0 +1,15 @@ +using System; +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Services.DataExchange.Export +{ + /// + /// Declares data processing types supported by an export provider. + /// Projection type controls whether to display corresponding projection fields while editing an export profile. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class ExportFeaturesAttribute : Attribute + { + public ExportFeatures Features { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs new file mode 100644 index 0000000000..1814ae56ae --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs @@ -0,0 +1,365 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Tasks; +using SmartStore.Core.Events; +using SmartStore.Core.Plugins; +using SmartStore.Services.Localization; +using SmartStore.Services.Tasks; +using SmartStore.Utilities; + +namespace SmartStore.Services.DataExchange.Export +{ + public partial class ExportProfileService : IExportProfileService + { + private const string _defaultFileNamePattern = "%Store.Id%-%Profile.Id%-%File.Index%-%Profile.SeoName%"; + + private readonly IRepository _exportProfileRepository; + private readonly IRepository _exportDeploymentRepository; + private readonly IEventPublisher _eventPublisher; + private readonly IScheduleTaskService _scheduleTaskService; + private readonly IProviderManager _providerManager; + private readonly DataExchangeSettings _dataExchangeSettings; + private readonly ILocalizationService _localizationService; + + public ExportProfileService( + IRepository exportProfileRepository, + IRepository exportDeploymentRepository, + IEventPublisher eventPublisher, + IScheduleTaskService scheduleTaskService, + IProviderManager providerManager, + DataExchangeSettings dataExchangeSettings, + ILocalizationService localizationService) + { + _exportProfileRepository = exportProfileRepository; + _exportDeploymentRepository = exportDeploymentRepository; + _eventPublisher = eventPublisher; + _scheduleTaskService = scheduleTaskService; + _providerManager = providerManager; + _dataExchangeSettings = dataExchangeSettings; + _localizationService = localizationService; + } + + public virtual ExportProfile InsertExportProfile( + string providerSystemName, + string name, + string fileExtension, + ExportFeatures features, + bool isSystemProfile = false, + string profileSystemName = null, + int cloneFromProfileId = 0) + { + Guard.ArgumentNotEmpty(() => providerSystemName); + + var profileCount = _exportProfileRepository.Table.Count(x => x.ProviderSystemName == providerSystemName); + + if (name.IsEmpty()) + name = providerSystemName; + + if (!isSystemProfile) + name = string.Concat(_localizationService.GetResource("Common.My"), " ", name); + + name = string.Concat(name, " ", profileCount + 1); + + var cloneProfile = GetExportProfileById(cloneFromProfileId); + + ScheduleTask task = null; + ExportProfile profile = null; + + if (cloneProfile == null) + { + task = new ScheduleTask + { + CronExpression = "0 */6 * * *", // every six hours + Type = typeof(DataExportTask).AssemblyQualifiedNameWithoutVersion(), + Enabled = false, + StopOnError = false, + IsHidden = true + }; + } + else + { + task = cloneProfile.ScheduleTask.Clone(); + task.LastEndUtc = task.LastStartUtc = task.LastSuccessUtc = null; + } + + task.Name = string.Concat(name, " Task"); + + _scheduleTaskService.InsertTask(task); + + if (cloneProfile == null) + { + profile = new ExportProfile + { + FileNamePattern = _defaultFileNamePattern + }; + + if (isSystemProfile) + { + profile.Enabled = true; + profile.PerStore = false; + profile.CreateZipArchive = false; + profile.Cleanup = false; + } + 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 + var projection = new ExportProjection + { + RemoveCriticalCharacters = true, + CriticalCharacters = "¼,½,¾", + PriceType = PriceDisplayType.PreSelectedPrice, + NoGroupedProducts = (features.HasFlag(ExportFeatures.CanOmitGroupedProducts) ? true : false), + DescriptionMerging = ExportDescriptionMerging.Description + }; + + var filter = new ExportFilter + { + IsPublished = true + }; + + profile.Projection = XmlHelper.Serialize(projection); + profile.Filtering = XmlHelper.Serialize(filter); + } + } + else + { + profile = cloneProfile.Clone(); + } + + profile.IsSystemProfile = isSystemProfile; + profile.Name = name; + profile.ProviderSystemName = providerSystemName; + profile.SchedulingTaskId = task.Id; + + var cleanedSystemName = providerSystemName + .Replace("Exports.", "") + .Replace("Feeds.", "") + .Replace("/", "") + .Replace("-", ""); + + var folderName = SeoHelper.GetSeName(cleanedSystemName, true, false) + .ToValidPath() + .Truncate(_dataExchangeSettings.MaxFileNameLength); + + profile.FolderName = "~/App_Data/ExportProfiles/" + FileSystemHelper.CreateNonExistingDirectoryName(CommonHelper.MapPath("~/App_Data/ExportProfiles"), folderName); + + if (profileSystemName.IsEmpty() && isSystemProfile) + profile.SystemName = cleanedSystemName; + else + profile.SystemName = profileSystemName; + + _exportProfileRepository.Insert(profile); + + + task.Alias = profile.Id.ToString(); + _scheduleTaskService.UpdateTask(task); + + if (fileExtension.HasValue() && !isSystemProfile) + { + if (cloneProfile == null) + { + if (features.HasFlag(ExportFeatures.CreatesInitialPublicDeployment)) + { + var subFolder = FileSystemHelper.CreateNonExistingDirectoryName(CommonHelper.MapPath("~/" + DataExporter.PublicFolder), folderName); + + profile.Deployments.Add(new ExportDeployment + { + ProfileId = profile.Id, + Enabled = true, + DeploymentType = ExportDeploymentType.PublicFolder, + Name = profile.Name, + SubFolder = subFolder + }); + + UpdateExportProfile(profile); + } + } + else + { + foreach (var deployment in cloneProfile.Deployments) + { + profile.Deployments.Add(deployment.Clone()); + } + + UpdateExportProfile(profile); + } + } + + _eventPublisher.EntityInserted(profile); + + return profile; + } + + public virtual ExportProfile InsertExportProfile( + Provider provider, + bool isSystemProfile = false, + string profileSystemName = null, + int cloneFromProfileId = 0) + { + Guard.ArgumentNotNull(() => provider); + + var profile = InsertExportProfile( + provider.Metadata.SystemName, + provider.GetName(_localizationService), + provider.Value.FileExtension, + provider.Metadata.ExportFeatures, + isSystemProfile, + profileSystemName, + cloneFromProfileId); + + return profile; + } + + public virtual void UpdateExportProfile(ExportProfile profile) + { + if (profile == null) + throw new ArgumentNullException("profile"); + + profile.FolderName = FileSystemHelper.ValidateRootPath(profile.FolderName); + + _exportProfileRepository.Update(profile); + + _eventPublisher.EntityUpdated(profile); + } + + public virtual void DeleteExportProfile(ExportProfile profile, bool force = false) + { + if (profile == null) + throw new ArgumentNullException("profile"); + + if (!force && profile.IsSystemProfile) + throw new SmartException(_localizationService.GetResource("Admin.DataExchange.Export.CannotDeleteSystemProfile")); + + int scheduleTaskId = profile.SchedulingTaskId; + var folder = profile.GetExportFolder(); + + _exportProfileRepository.Delete(profile); + + var scheduleTask = _scheduleTaskService.GetTaskById(scheduleTaskId); + _scheduleTaskService.DeleteTask(scheduleTask); + + _eventPublisher.EntityDeleted(profile); + + if (System.IO.Directory.Exists(folder)) + { + FileSystemHelper.ClearDirectory(folder, true); + } + } + + public virtual IQueryable GetExportProfiles(bool? enabled = null) + { + var query = _exportProfileRepository.Table + .Expand(x => x.ScheduleTask) + .Expand(x => x.Deployments); + + if (enabled.HasValue) + { + query = query.Where(x => x.Enabled == enabled.Value); + } + + query = query + .OrderBy(x => x.IsSystemProfile) + .ThenBy(x => x.Name); + + return query; + } + + public virtual ExportProfile GetExportProfileById(int id) + { + if (id == 0) + return null; + + var profile = _exportProfileRepository.Table + .Expand(x => x.ScheduleTask) + .Expand(x => x.Deployments) + .FirstOrDefault(x => x.Id == id); + + return profile; + } + + public virtual ExportProfile GetSystemExportProfile(string providerSystemName) + { + if (providerSystemName.IsEmpty()) + return null; + + var query = GetExportProfiles(true); + + var profile = query + .Where(x => x.IsSystemProfile && x.ProviderSystemName == providerSystemName) + .FirstOrDefault(); + + return profile; + } + + public virtual IList GetExportProfilesBySystemName(string providerSystemName) + { + if (providerSystemName.IsEmpty()) + return new List(); + + var profiles = _exportProfileRepository.Table + .Expand(x => x.ScheduleTask) + .Expand(x => x.Deployments) + .Where(x => x.ProviderSystemName == providerSystemName) + .ToList(); + + return profiles; + } + + + public virtual IEnumerable> LoadAllExportProviders(int storeId = 0, bool showHidden = true) + { + var allProviders = _providerManager.GetAllProviders(storeId) + .Where(x => x.IsValid() && (showHidden || !x.Metadata.IsHidden)) + //.OrderBy(x => x.Metadata.SystemName) + .OrderBy(x => x.Metadata.FriendlyName); + + return allProviders; + } + + public virtual Provider LoadProvider(string systemName, int storeId = 0) + { + var provider = _providerManager.GetProvider(systemName, storeId); + + return (provider.IsValid() ? provider : null); + } + + public virtual ExportDeployment GetExportDeploymentById(int id) + { + if (id == 0) + return null; + + var deployment = _exportDeploymentRepository.Table + .Expand(x => x.Profile) + .FirstOrDefault(x => x.Id == id); + + return deployment; + } + + public virtual void UpdateExportDeployment(ExportDeployment deployment) + { + if (deployment == null) + throw new ArgumentNullException("deployment"); + + _exportDeploymentRepository.Update(deployment); + + _eventPublisher.EntityUpdated(deployment); + } + + public virtual void DeleteExportDeployment(ExportDeployment deployment) + { + if (deployment == null) + throw new ArgumentNullException("deployment"); + + _exportDeploymentRepository.Delete(deployment); + + _eventPublisher.EntityDeleted(deployment); + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs new file mode 100644 index 0000000000..c77638f60b --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs @@ -0,0 +1,50 @@ +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Services.DataExchange.Export +{ + public abstract class ExportProviderBase : IExportProvider + { + /// + /// The exported entity type + /// + public virtual ExportEntityType EntityType + { + get { return ExportEntityType.Product; } + } + + /// + /// File extension of the export files (without dot). Return null for a non file based, on-the-fly export. + /// + public virtual string FileExtension + { + get { return null; } + } + + /// + /// Get provider specific configuration information. Return null when no provider specific configuration is required. + /// + public virtual ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + public void Execute(ExportExecuteContext context) + { + Export(context); + } + + /// + /// Export data to a file + /// + /// Export execution context + protected abstract void Export(ExportExecuteContext context); + + /// + /// Called once per store when export execution ended + /// + /// Export execution context + public virtual void OnExecuted(ExportExecuteContext context) + { + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs new file mode 100644 index 0000000000..68c8b78d55 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs @@ -0,0 +1,918 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Xml; +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.Media; + +namespace SmartStore.Services.DataExchange.Export +{ + public class ExportXmlHelper : IDisposable + { + private XmlWriter _writer; + private CultureInfo _culture; + private bool _doNotDispose; + + public ExportXmlHelper(XmlWriter writer, bool doNotDispose = false, CultureInfo culture = null) + { + _writer = writer; + _doNotDispose = doNotDispose; + _culture = (culture == null ? CultureInfo.InvariantCulture : culture); + } + public ExportXmlHelper(Stream stream, XmlWriterSettings settings = null, CultureInfo culture = null) + { + if (settings == null) + { + settings = DefaultSettings; + } + + _writer = XmlWriter.Create(stream, settings); + _culture = (culture == null ? CultureInfo.InvariantCulture : culture); + } + + public static XmlWriterSettings DefaultSettings + { + get + { + return new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CheckCharacters = false, + Indent = true, + IndentChars = "\t" + }; + } + } + + public ExportXmlExclude Exclude { get; set; } + + public XmlWriter Writer + { + get { return _writer; } + } + + public void Dispose() + { + if (_writer != null && !_doNotDispose) + { + _writer.Dispose(); + } + } + + public void WriteLocalized(dynamic parentNode) + { + if (parentNode == null || parentNode._Localized == null) + return; + + _writer.WriteStartElement("Localized"); + foreach (dynamic item in parentNode._Localized) + { + _writer.WriteStartElement((string)item.LocaleKey); + _writer.WriteAttributeString("culture", (string)item.Culture); + _writer.WriteString(((string)item.LocaleValue).RemoveInvalidXmlChars()); + _writer.WriteEndElement(); // item.LocaleKey + } + _writer.WriteEndElement(); // Localized + } + + public void WriteGenericAttributes(dynamic parentNode) + { + if (parentNode == null || parentNode._GenericAttributes == null) + return; + + _writer.WriteStartElement("GenericAttributes"); + foreach (dynamic genericAttribute in parentNode._GenericAttributes) + { + GenericAttribute entity = genericAttribute.Entity; + + _writer.WriteStartElement("GenericAttribute"); + _writer.Write("Id", entity.ToString()); + _writer.Write("EntityId", entity.EntityId.ToString()); + _writer.Write("KeyGroup", entity.KeyGroup); + _writer.Write("Key", entity.Key); + _writer.Write("Value", (string)genericAttribute.Value); + _writer.Write("StoreId", entity.StoreId.ToString()); + _writer.WriteEndElement(); // GenericAttribute + } + _writer.WriteEndElement(); // GenericAttributes + } + + public void WriteAddress(dynamic address, string node) + { + if (address == null) + return; + + Address entity = address.Entity; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", entity.Id.ToString()); + _writer.Write("FirstName", entity.FirstName); + _writer.Write("LastName", entity.LastName); + _writer.Write("Email", entity.Email); + _writer.Write("Company", entity.Company); + _writer.Write("CountryId", entity.CountryId.HasValue ? entity.CountryId.Value.ToString() : ""); + _writer.Write("StateProvinceId", entity.StateProvinceId.HasValue ? entity.StateProvinceId.Value.ToString() : ""); + _writer.Write("City", entity.City); + _writer.Write("Address1", entity.Address1); + _writer.Write("Address2", entity.Address2); + _writer.Write("ZipPostalCode", entity.ZipPostalCode); + _writer.Write("PhoneNumber", entity.PhoneNumber); + _writer.Write("FaxNumber", entity.FaxNumber); + _writer.Write("CreatedOnUtc", entity.CreatedOnUtc.ToString(_culture)); + + if (address.Country != null) + { + dynamic country = address.Country; + Country entityCountry = address.Country.Entity; + + _writer.WriteStartElement("Country"); + _writer.Write("Id", entityCountry.Id.ToString()); + _writer.Write("Name", (string)country.Name); + _writer.Write("AllowsBilling", entityCountry.AllowsBilling.ToString()); + _writer.Write("AllowsShipping", entityCountry.AllowsShipping.ToString()); + _writer.Write("TwoLetterIsoCode", entityCountry.TwoLetterIsoCode); + _writer.Write("ThreeLetterIsoCode", entityCountry.ThreeLetterIsoCode); + _writer.Write("NumericIsoCode", entityCountry.NumericIsoCode.ToString()); + _writer.Write("SubjectToVat", entityCountry.SubjectToVat.ToString()); + _writer.Write("Published", entityCountry.Published.ToString()); + _writer.Write("DisplayOrder", entityCountry.DisplayOrder.ToString()); + _writer.Write("LimitedToStores", entityCountry.LimitedToStores.ToString()); + + WriteLocalized(country); + + _writer.WriteEndElement(); // Country + } + + if (address.StateProvince != null) + { + dynamic stateProvince = address.StateProvince; + StateProvince entityStateProvince = address.StateProvince.Entity; + + _writer.WriteStartElement("StateProvince"); + _writer.Write("Id", entityStateProvince.Id.ToString()); + _writer.Write("CountryId", entityStateProvince.CountryId.ToString()); + _writer.Write("Name", (string)stateProvince.Name); + _writer.Write("Abbreviation", (string)stateProvince.Abbreviation); + _writer.Write("Published", entityStateProvince.Published.ToString()); + _writer.Write("DisplayOrder", entityStateProvince.DisplayOrder.ToString()); + + WriteLocalized(stateProvince); + + _writer.WriteEndElement(); // StateProvince + } + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteCurrency(dynamic currency, string node) + { + if (currency == null) + return; + + Currency entity = currency.Entity; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", entity.Id.ToString()); + _writer.Write("Name", (string)currency.Name); + _writer.Write("CurrencyCode", entity.CurrencyCode); + _writer.Write("Rate", entity.Rate.ToString(_culture)); + _writer.Write("DisplayLocale", entity.DisplayLocale); + _writer.Write("CustomFormatting", entity.CustomFormatting); + _writer.Write("LimitedToStores", entity.LimitedToStores.ToString()); + _writer.Write("Published", entity.Published.ToString()); + _writer.Write("DisplayOrder", entity.DisplayOrder.ToString()); + _writer.Write("CreatedOnUtc", entity.CreatedOnUtc.ToString(_culture)); + _writer.Write("UpdatedOnUtc", entity.UpdatedOnUtc.ToString(_culture)); + _writer.Write("DomainEndings", entity.DomainEndings); + + WriteLocalized(currency); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteRewardPointsHistory(dynamic rewardPoints, string node) + { + if (rewardPoints == null) + return; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + foreach (dynamic rewardPoint in rewardPoints) + { + RewardPointsHistory entity = rewardPoint.Entity; + + _writer.WriteStartElement("RewardPointsHistory"); + _writer.Write("Id", entity.ToString()); + _writer.Write("CustomerId", entity.ToString()); + _writer.Write("Points", entity.Points.ToString()); + _writer.Write("PointsBalance", entity.PointsBalance.ToString()); + _writer.Write("UsedAmount", entity.UsedAmount.ToString(_culture)); + _writer.Write("Message", (string)rewardPoint.Message); + _writer.Write("CreatedOnUtc", entity.CreatedOnUtc.ToString(_culture)); + _writer.WriteEndElement(); // RewardPointsHistory + } + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteDeliveryTime(dynamic deliveryTime, string node) + { + if (deliveryTime == null) + return; + + DeliveryTime entity = deliveryTime.Entity; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", entity.Id.ToString()); + _writer.Write("Name", (string)deliveryTime.Name); + _writer.Write("DisplayLocale", entity.DisplayLocale); + _writer.Write("ColorHexValue", entity.ColorHexValue); + _writer.Write("DisplayOrder", entity.DisplayOrder.ToString()); + + WriteLocalized(deliveryTime); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteQuantityUnit(dynamic quantityUnit, string node) + { + if (quantityUnit == null) + return; + + QuantityUnit entity = quantityUnit.Entity; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", entity.Id.ToString()); + _writer.Write("Name", (string)quantityUnit.Name); + _writer.Write("Description", (string)quantityUnit.Description); + _writer.Write("DisplayLocale", entity.DisplayLocale); + _writer.Write("DisplayOrder", entity.DisplayOrder.ToString()); + _writer.Write("IsDefault", entity.IsDefault.ToString()); + + WriteLocalized(quantityUnit); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WritePicture(dynamic picture, string node) + { + if (picture == null) + return; + + Picture entity = picture.Entity; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", entity.Id.ToString()); + _writer.Write("SeoFilename", (string)picture.SeoFilename); + _writer.Write("MimeType", (string)picture.MimeType); + _writer.Write("ThumbImageUrl", (string)picture._ThumbImageUrl); + _writer.Write("ImageUrl", (string)picture._ImageUrl); + _writer.Write("FullSizeImageUrl", (string)picture._FullSizeImageUrl); + _writer.Write("FileName", (string)picture._FileName); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteCategory(dynamic category, string node) + { + if (category == null) + return; + + Category entity = category.Entity; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", entity.Id.ToString()); + + if (!Exclude.HasFlag(ExportXmlExclude.Category)) + { + _writer.Write("Name", (string)category.Name); + _writer.Write("FullName", (string)category.FullName); + _writer.Write("Description", (string)category.Description); + _writer.Write("BottomDescription", (string)category.BottomDescription); + _writer.Write("CategoryTemplateId", entity.CategoryTemplateId.ToString()); + _writer.Write("CategoryTemplateViewPath", (string)category._CategoryTemplateViewPath); + _writer.Write("MetaKeywords", (string)category.MetaKeywords); + _writer.Write("MetaDescription", (string)category.MetaDescription); + _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("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()); + _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("SubjectToAcl", entity.SubjectToAcl.ToString()); + _writer.Write("LimitedToStores", entity.LimitedToStores.ToString()); + _writer.Write("Alias", (string)category.Alias); + _writer.Write("DefaultViewMode", entity.DefaultViewMode); + + WritePicture(category.Picture, "Picture"); + + WriteLocalized(category); + } + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteManufacturer(dynamic manufacturer, string node) + { + if (manufacturer == null) + return; + + Manufacturer entity = manufacturer.Entity; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", entity.Id.ToString()); + _writer.Write("Name", (string)manufacturer.Name); + _writer.Write("SeName", (string)manufacturer.SeName); + _writer.Write("Description", (string)manufacturer.Description); + _writer.Write("ManufacturerTemplateId", entity.ManufacturerTemplateId.ToString()); + _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("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)); + + WritePicture(manufacturer.Picture, "Picture"); + + WriteLocalized(manufacturer); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteProduct(dynamic product, string node) + { + if (product == null) + return; + + Product entity = product.Entity; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + decimal? basePriceAmount = product.BasePriceAmount; + int? basePriceBaseAmount = product.BasePriceBaseAmount; + decimal? lowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; + + _writer.Write("Id", entity.Id.ToString()); + _writer.Write("Name", (string)product.Name); + _writer.Write("SeName", (string)product.SeName); + _writer.Write("ShortDescription", (string)product.ShortDescription); + _writer.Write("FullDescription", (string)product.FullDescription); + _writer.Write("AdminComment", (string)product.AdminComment); + _writer.Write("ProductTemplateId", entity.ProductTemplateId.ToString()); + _writer.Write("ProductTemplateViewPath", (string)product._ProductTemplateViewPath); + _writer.Write("ShowOnHomePage", entity.ShowOnHomePage.ToString()); + _writer.Write("HomePageDisplayOrder", entity.HomePageDisplayOrder.ToString()); + _writer.Write("MetaKeywords", (string)product.MetaKeywords); + _writer.Write("MetaDescription", (string)product.MetaDescription); + _writer.Write("MetaTitle", (string)product.MetaTitle); + _writer.Write("AllowCustomerReviews", entity.AllowCustomerReviews.ToString()); + _writer.Write("ApprovedRatingSum", entity.ApprovedRatingSum.ToString()); + _writer.Write("NotApprovedRatingSum", entity.NotApprovedRatingSum.ToString()); + _writer.Write("ApprovedTotalReviews", entity.ApprovedTotalReviews.ToString()); + _writer.Write("NotApprovedTotalReviews", entity.NotApprovedTotalReviews.ToString()); + _writer.Write("Published", entity.Published.ToString()); + _writer.Write("CreatedOnUtc", entity.CreatedOnUtc.ToString(_culture)); + _writer.Write("UpdatedOnUtc", entity.UpdatedOnUtc.ToString(_culture)); + _writer.Write("SubjectToAcl", entity.SubjectToAcl.ToString()); + _writer.Write("LimitedToStores", entity.LimitedToStores.ToString()); + _writer.Write("ProductTypeId", entity.ProductTypeId.ToString()); + _writer.Write("ParentGroupedProductId", entity.ParentGroupedProductId.ToString()); + _writer.Write("Sku", (string)product.Sku); + _writer.Write("ManufacturerPartNumber", (string)product.ManufacturerPartNumber); + _writer.Write("Gtin", (string)product.Gtin); + _writer.Write("IsGiftCard", entity.IsGiftCard.ToString()); + _writer.Write("GiftCardTypeId", entity.GiftCardTypeId.ToString()); + _writer.Write("RequireOtherProducts", entity.RequireOtherProducts.ToString()); + _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() : ""); + _writer.Write("DownloadActivationTypeId", entity.DownloadActivationTypeId.ToString()); + _writer.Write("HasSampleDownload", entity.HasSampleDownload.ToString()); + _writer.Write("SampleDownloadId", entity.SampleDownloadId.HasValue ? entity.SampleDownloadId.Value.ToString() : ""); + _writer.Write("HasUserAgreement", entity.HasUserAgreement.ToString()); + _writer.Write("UserAgreementText", entity.UserAgreementText); + _writer.Write("IsRecurring", entity.IsRecurring.ToString()); + _writer.Write("RecurringCycleLength", entity.RecurringCycleLength.ToString()); + _writer.Write("RecurringCyclePeriodId", entity.RecurringCyclePeriodId.ToString()); + _writer.Write("RecurringTotalCycles", entity.RecurringTotalCycles.ToString()); + _writer.Write("IsShipEnabled", entity.IsShipEnabled.ToString()); + _writer.Write("IsFreeShipping", entity.IsFreeShipping.ToString()); + _writer.Write("AdditionalShippingCharge", entity.AdditionalShippingCharge.ToString(_culture)); + _writer.Write("IsTaxExempt", entity.IsTaxExempt.ToString()); + _writer.Write("TaxCategoryId", entity.TaxCategoryId.ToString()); + _writer.Write("ManageInventoryMethodId", entity.ManageInventoryMethodId.ToString()); + _writer.Write("StockQuantity", entity.StockQuantity.ToString()); + _writer.Write("DisplayStockAvailability", entity.DisplayStockAvailability.ToString()); + _writer.Write("DisplayStockQuantity", entity.DisplayStockQuantity.ToString()); + _writer.Write("MinStockQuantity", entity.MinStockQuantity.ToString()); + _writer.Write("LowStockActivityId", entity.LowStockActivityId.ToString()); + _writer.Write("NotifyAdminForQuantityBelow", entity.NotifyAdminForQuantityBelow.ToString()); + _writer.Write("BackorderModeId", entity.BackorderModeId.ToString()); + _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("DisableBuyButton", entity.DisableBuyButton.ToString()); + _writer.Write("DisableWishlistButton", entity.DisableWishlistButton.ToString()); + _writer.Write("AvailableForPreOrder", entity.AvailableForPreOrder.ToString()); + _writer.Write("CallForPrice", entity.CallForPrice.ToString()); + _writer.Write("Price", entity.Price.ToString(_culture)); + _writer.Write("OldPrice", entity.OldPrice.ToString(_culture)); + _writer.Write("ProductCost", entity.ProductCost.ToString(_culture)); + _writer.Write("SpecialPrice", entity.SpecialPrice.HasValue ? entity.SpecialPrice.Value.ToString(_culture) : ""); + _writer.Write("SpecialPriceStartDateTimeUtc", entity.SpecialPriceStartDateTimeUtc.HasValue ? entity.SpecialPriceStartDateTimeUtc.Value.ToString(_culture) : ""); + _writer.Write("SpecialPriceEndDateTimeUtc", entity.SpecialPriceEndDateTimeUtc.HasValue ? entity.SpecialPriceEndDateTimeUtc.Value.ToString(_culture) : ""); + _writer.Write("CustomerEntersPrice", entity.CustomerEntersPrice.ToString()); + _writer.Write("MinimumCustomerEnteredPrice", entity.MinimumCustomerEnteredPrice.ToString(_culture)); + _writer.Write("MaximumCustomerEnteredPrice", entity.MaximumCustomerEnteredPrice.ToString(_culture)); + _writer.Write("HasTierPrices", entity.HasTierPrices.ToString()); + _writer.Write("HasDiscountsApplied", entity.HasDiscountsApplied.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)); + _writer.Write("Height", ((decimal)product.Height).ToString(_culture)); + _writer.Write("AvailableStartDateTimeUtc", entity.AvailableStartDateTimeUtc.HasValue ? entity.AvailableStartDateTimeUtc.Value.ToString(_culture) : ""); + _writer.Write("AvailableEndDateTimeUtc", entity.AvailableEndDateTimeUtc.HasValue ? entity.AvailableEndDateTimeUtc.Value.ToString(_culture) : ""); + _writer.Write("BasePriceEnabled", ((bool)product.BasePriceEnabled).ToString()); + _writer.Write("BasePriceMeasureUnit", (string)product.BasePriceMeasureUnit); + _writer.Write("BasePriceAmount", basePriceAmount.HasValue ? basePriceAmount.Value.ToString(_culture) : ""); + _writer.Write("BasePriceBaseAmount", basePriceBaseAmount.HasValue ? basePriceBaseAmount.Value.ToString() : ""); + _writer.Write("BasePriceHasValue", ((bool)product.BasePriceHasValue).ToString()); + _writer.Write("BasePriceInfo", (string)product._BasePriceInfo); + _writer.Write("VisibleIndividually", entity.VisibleIndividually.ToString()); + _writer.Write("DisplayOrder", entity.DisplayOrder.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()); + + WriteLocalized(product); + + WriteDeliveryTime(product.DeliveryTime, "DeliveryTime"); + + WriteQuantityUnit(product.QuantityUnit, "QuantityUnit"); + + if (product.AppliedDiscounts != null) + { + _writer.WriteStartElement("AppliedDiscounts"); + foreach (dynamic discount in product.AppliedDiscounts) + { + Discount entityDiscount = discount.Entity; + + _writer.WriteStartElement("AppliedDiscount"); + _writer.Write("Id", entityDiscount.Id.ToString()); + _writer.Write("Name", (string)discount.Name); + _writer.Write("DiscountTypeId", entityDiscount.DiscountTypeId.ToString()); + _writer.Write("UsePercentage", entityDiscount.UsePercentage.ToString()); + _writer.Write("DiscountPercentage", entityDiscount.DiscountPercentage.ToString(_culture)); + _writer.Write("DiscountAmount", entityDiscount.DiscountAmount.ToString(_culture)); + _writer.Write("StartDateUtc", entityDiscount.StartDateUtc.HasValue ? entityDiscount.StartDateUtc.Value.ToString(_culture) : ""); + _writer.Write("EndDateUtc", entityDiscount.EndDateUtc.HasValue ? entityDiscount.EndDateUtc.Value.ToString(_culture) : ""); + _writer.Write("RequiresCouponCode", entityDiscount.RequiresCouponCode.ToString()); + _writer.Write("CouponCode", entityDiscount.CouponCode); + _writer.Write("DiscountLimitationId", entityDiscount.DiscountLimitationId.ToString()); + _writer.Write("LimitationTimes", entityDiscount.LimitationTimes.ToString()); + _writer.WriteEndElement(); // AppliedDiscount + } + _writer.WriteEndElement(); // AppliedDiscounts + } + + if (product.TierPrices != null) + { + _writer.WriteStartElement("TierPrices"); + foreach (dynamic tierPrice in product.TierPrices) + { + TierPrice entityTierPrice = tierPrice.Entity; + + _writer.WriteStartElement("TierPrice"); + _writer.Write("Id", entityTierPrice.Id.ToString()); + _writer.Write("ProductId", entityTierPrice.ProductId.ToString()); + _writer.Write("StoreId", entityTierPrice.StoreId.ToString()); + _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.WriteEndElement(); // TierPrices + } + + if (product.ProductTags != null) + { + _writer.WriteStartElement("ProductTags"); + foreach (dynamic tag in product.ProductTags) + { + _writer.WriteStartElement("ProductTag"); + _writer.Write("Id", ((int)tag.Id).ToString()); + _writer.Write("Name", (string)tag.Name); + _writer.Write("SeName", (string)tag.SeName); + + WriteLocalized(tag); + + _writer.WriteEndElement(); // ProductTag + } + _writer.WriteEndElement(); // ProductTags + } + + if (product.ProductAttributes != null) + { + _writer.WriteStartElement("ProductAttributes"); + foreach (dynamic pva in product.ProductAttributes) + { + ProductVariantAttribute entityPva = pva.Entity; + + _writer.WriteStartElement("ProductAttribute"); + _writer.Write("Id", entityPva.Id.ToString()); + _writer.Write("TextPrompt", (string)pva.TextPrompt); + _writer.Write("IsRequired", entityPva.IsRequired.ToString()); + _writer.Write("AttributeControlTypeId", entityPva.AttributeControlTypeId.ToString()); + _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); + + WriteLocalized(pva.Attribute); + + _writer.WriteEndElement(); // Attribute + + _writer.WriteStartElement("AttributeValues"); + foreach (dynamic value in pva.Attribute.Values) + { + ProductVariantAttributeValue entityPvav = value.Entity; + + _writer.WriteStartElement("AttributeValue"); + _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("PriceAdjustment", ((decimal)value.PriceAdjustment).ToString(_culture)); + _writer.Write("WeightAdjustment", ((decimal)value.WeightAdjustment).ToString(_culture)); + _writer.Write("IsPreSelected", entityPvav.IsPreSelected.ToString()); + _writer.Write("DisplayOrder", entityPvav.DisplayOrder.ToString()); + _writer.Write("ValueTypeId", entityPvav.ValueTypeId.ToString()); + _writer.Write("LinkedProductId", entityPvav.LinkedProductId.ToString()); + _writer.Write("Quantity", entityPvav.Quantity.ToString()); + + WriteLocalized(value); + + _writer.WriteEndElement(); // AttributeValue + } + _writer.WriteEndElement(); // AttributeValues + + _writer.WriteEndElement(); // ProductAttribute + } + _writer.WriteEndElement(); // ProductAttributes + } + + if (product.ProductAttributeCombinations != null) + { + _writer.WriteStartElement("ProductAttributeCombinations"); + foreach (dynamic combination in product.ProductAttributeCombinations) + { + ProductVariantAttributeCombination entityPvac = combination.Entity; + + _writer.WriteStartElement("ProductAttributeCombination"); + _writer.Write("Id", entityPvac.Id.ToString()); + _writer.Write("StockQuantity", entityPvac.StockQuantity.ToString()); + _writer.Write("AllowOutOfStockOrders", entityPvac.AllowOutOfStockOrders.ToString()); + _writer.Write("AttributesXml", entityPvac.AttributesXml); + _writer.Write("Sku", entityPvac.Sku); + _writer.Write("Gtin", entityPvac.Gtin); + _writer.Write("ManufacturerPartNumber", entityPvac.ManufacturerPartNumber); + _writer.Write("Price", entityPvac.Price.HasValue ? entityPvac.Price.Value.ToString(_culture) : ""); + _writer.Write("Length", entityPvac.Length.HasValue ? entityPvac.Length.Value.ToString(_culture) : ""); + _writer.Write("Width", entityPvac.Width.HasValue ? entityPvac.Width.Value.ToString(_culture) : ""); + _writer.Write("Height", entityPvac.Height.HasValue ? entityPvac.Height.Value.ToString(_culture) : ""); + _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"); + + WriteQuantityUnit(combination.QuantityUnit, "QuantityUnit"); + + _writer.WriteStartElement("Pictures"); + foreach (dynamic assignedPicture in combination.Pictures) + { + WritePicture(assignedPicture, "Picture"); + } + _writer.WriteEndElement(); // Pictures + + _writer.WriteEndElement(); // ProductAttributeCombination + } + _writer.WriteEndElement(); // ProductAttributeCombinations + } + + if (product.ProductPictures != null) + { + _writer.WriteStartElement("ProductPictures"); + foreach (dynamic productPicture in product.ProductPictures) + { + ProductPicture entityProductPicture = productPicture.Entity; + + _writer.WriteStartElement("ProductPicture"); + _writer.Write("Id", entityProductPicture.Id.ToString()); + _writer.Write("DisplayOrder", entityProductPicture.DisplayOrder.ToString()); + + WritePicture(productPicture.Picture, "Picture"); + + _writer.WriteEndElement(); // ProductPicture + } + _writer.WriteEndElement(); // ProductPictures + } + + if (product.ProductCategories != null) + { + _writer.WriteStartElement("ProductCategories"); + foreach (dynamic productCategory in product.ProductCategories) + { + ProductCategory entityProductCategory = productCategory.Entity; + + _writer.WriteStartElement("ProductCategory"); + _writer.Write("Id", entityProductCategory.Id.ToString()); + _writer.Write("DisplayOrder", entityProductCategory.DisplayOrder.ToString()); + _writer.Write("IsFeaturedProduct", entityProductCategory.IsFeaturedProduct.ToString()); + + WriteCategory(productCategory.Category, "Category"); + + _writer.WriteEndElement(); // ProductCategory + } + _writer.WriteEndElement(); // ProductCategories + } + + if (product.ProductManufacturers != null) + { + _writer.WriteStartElement("ProductManufacturers"); + foreach (dynamic productManu in product.ProductManufacturers) + { + ProductManufacturer entityProductManu = productManu.Entity; + + _writer.WriteStartElement("ProductManufacturer"); + + _writer.Write("Id", entityProductManu.Id.ToString()); + _writer.Write("DisplayOrder", entityProductManu.DisplayOrder.ToString()); + _writer.Write("IsFeaturedProduct", entityProductManu.IsFeaturedProduct.ToString()); + + WriteManufacturer(productManu.Manufacturer, "Manufacturer"); + + _writer.WriteEndElement(); // ProductManufacturer + } + _writer.WriteEndElement(); // ProductManufacturers + } + + if (product.ProductSpecificationAttributes != null) + { + _writer.WriteStartElement("ProductSpecificationAttributes"); + foreach (dynamic psa in product.ProductSpecificationAttributes) + { + ProductSpecificationAttribute entityPsa = psa.Entity; + + _writer.WriteStartElement("ProductSpecificationAttribute"); + + _writer.Write("Id", entityPsa.Id.ToString()); + _writer.Write("ProductId", entityPsa.ProductId.ToString()); + _writer.Write("SpecificationAttributeOptionId", entityPsa.SpecificationAttributeOptionId.ToString()); + _writer.Write("AllowFiltering", entityPsa.AllowFiltering.ToString()); + _writer.Write("ShowOnProductPage", entityPsa.ShowOnProductPage.ToString()); + _writer.Write("DisplayOrder", entityPsa.DisplayOrder.ToString()); + + dynamic option = psa.SpecificationAttributeOption; + SpecificationAttributeOption entitySao = option.Entity; + SpecificationAttribute entitySa = option.SpecificationAttribute.Entity; + + _writer.WriteStartElement("SpecificationAttributeOption"); + _writer.Write("Id", entitySao.Id.ToString()); + _writer.Write("SpecificationAttributeId", entitySao.SpecificationAttributeId.ToString()); + _writer.Write("DisplayOrder", entitySao.DisplayOrder.ToString()); + _writer.Write("Name", (string)option.Name); + + WriteLocalized(option); + + _writer.WriteStartElement("SpecificationAttribute"); + _writer.Write("Id", entitySa.Id.ToString()); + _writer.Write("Name", (string)option.SpecificationAttribute.Name); + _writer.Write("DisplayOrder", entitySa.DisplayOrder.ToString()); + + WriteLocalized(option.SpecificationAttribute); + + _writer.WriteEndElement(); // SpecificationAttribute + _writer.WriteEndElement(); // SpecificationAttributeOption + + _writer.WriteEndElement(); // ProductSpecificationAttribute + } + _writer.WriteEndElement(); // ProductSpecificationAttributes + } + + if (product.ProductBundleItems != null) + { + _writer.WriteStartElement("ProductBundleItems"); + foreach (dynamic bundleItem in product.ProductBundleItems) + { + ProductBundleItem entityPbi = bundleItem.Entity; + + _writer.WriteStartElement("ProductBundleItem"); + _writer.Write("Id", entityPbi.Id.ToString()); + _writer.Write("ProductId", entityPbi.ProductId.ToString()); + _writer.Write("BundleProductId", entityPbi.BundleProductId.ToString()); + _writer.Write("Quantity", entityPbi.Quantity.ToString()); + _writer.Write("Discount", entityPbi.Discount.HasValue ? entityPbi.Discount.Value.ToString(_culture) : ""); + _writer.Write("DiscountPercentage", entityPbi.DiscountPercentage.ToString()); + _writer.Write("Name", (string)bundleItem.Name); + _writer.Write("ShortDescription", (string)bundleItem.ShortDescription); + _writer.Write("FilterAttributes", entityPbi.FilterAttributes.ToString()); + _writer.Write("HideThumbnail", entityPbi.HideThumbnail.ToString()); + _writer.Write("Visible", entityPbi.Visible.ToString()); + _writer.Write("Published", entityPbi.Published.ToString()); + _writer.Write("DisplayOrder", ((int)bundleItem.DisplayOrder).ToString()); + _writer.Write("CreatedOnUtc", entityPbi.CreatedOnUtc.ToString(_culture)); + _writer.Write("UpdatedOnUtc", entityPbi.UpdatedOnUtc.ToString(_culture)); + + WriteLocalized(bundleItem); + + _writer.WriteEndElement(); // ProductBundleItem + } + _writer.WriteEndElement(); // ProductBundleItems + } + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteCustomer(dynamic customer, string node) + { + if (customer == null) + return; + + Customer entity = customer.Entity; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", entity.Id.ToString()); + _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()); + _writer.Write("Active", entity.Active.ToString()); + _writer.Write("Deleted", entity.Deleted.ToString()); + _writer.Write("IsSystemAccount", entity.IsSystemAccount.ToString()); + _writer.Write("SystemName", entity.SystemName); + _writer.Write("LastIpAddress", entity.LastIpAddress); + _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()); + + if (customer.CustomerRoles != null) + { + _writer.WriteStartElement("CustomerRoles"); + foreach (dynamic role in customer.CustomerRoles) + { + CustomerRole entityRole = role.Entity; + + _writer.WriteStartElement("CustomerRole"); + _writer.Write("Id", entityRole.Id.ToString()); + _writer.Write("Name", (string)role.Name); + _writer.Write("FreeShipping", entityRole.FreeShipping.ToString()); + _writer.Write("TaxExempt", entityRole.TaxExempt.ToString()); + _writer.Write("TaxDisplayType", entityRole.TaxDisplayType.HasValue ? entityRole.TaxDisplayType.Value.ToString() : ""); + _writer.Write("Active", entityRole.Active.ToString()); + _writer.Write("IsSystemRole", entityRole.IsSystemRole.ToString()); + _writer.Write("SystemName", entityRole.SystemName); + _writer.WriteEndElement(); // CustomerRole + } + _writer.WriteEndElement(); // CustomerRoles + } + + WriteRewardPointsHistory(customer.RewardPointsHistory, "RewardPointsHistories"); + WriteAddress(customer.BillingAddress, "BillingAddress"); + WriteAddress(customer.ShippingAddress, "ShippingAddress"); + + if (customer.Addresses != null) + { + _writer.WriteStartElement("Addresses"); + foreach (dynamic address in customer.Addresses) + { + WriteAddress(address, "Address"); + } + _writer.WriteEndElement(); // Addresses + } + + WriteGenericAttributes(customer); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + } + + + /// + /// Allows to exclude XML nodes from export + /// + [Flags] + public enum ExportXmlExclude + { + None = 0, + Category = 1 + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs new file mode 100644 index 0000000000..34b96faf24 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange.Export +{ + public interface IDataExporter + { + DataExportResult Export(DataExportRequest request, CancellationToken cancellationToken); + + IList Preview(DataExportRequest request, int pageIndex, int? totalRecords = null); + + int GetDataCount(DataExportRequest request); + } + + + public class DataExportRequest + { + private readonly static ProgressValueSetter _voidProgressValueSetter = DataExportRequest.SetProgress; + + public DataExportRequest(ExportProfile profile, Provider provider) + { + Guard.ArgumentNotNull(() => profile); + Guard.ArgumentNotNull(() => provider); + + Profile = profile; + Provider = provider; + ProgressValueSetter = _voidProgressValueSetter; + + EntitiesToExport = new List(); + CustomData = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public ExportProfile Profile { get; private set; } + + public Provider Provider { get; private set; } + + public ProgressValueSetter ProgressValueSetter { get; set; } + + public bool HasPermission { get; set; } + + public IList EntitiesToExport { get; set; } + + public IDictionary CustomData { get; private set; } + + public IQueryable ProductQuery { get; set; } + + + private static void SetProgress(int val, int max, string msg) + { + // do nothing + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs new file mode 100644 index 0000000000..b0b0c8a6bd --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange.Export +{ + public interface IExportProfileService + { + /// + /// Inserts an export profile + /// + /// Provider system name + /// The name of the profile + /// File extension supported by provider + /// Features supportde by provider + /// Whether the new profile is a system profile + /// Profile system name + /// Identifier of a profile the settings should be copied from + /// New export profile + ExportProfile InsertExportProfile( + string providerSystemName, + string name, + string fileExtension, + ExportFeatures features, + bool isSystemProfile = false, + string profileSystemName = null, + int cloneFromProfileId = 0); + + /// + /// Inserts an export profile + /// + /// Export provider + /// Whether the new profile is a system profile + /// Profile system name + /// Identifier of a profile the settings should be copied from + /// New export profile + ExportProfile InsertExportProfile( + Provider provider, + bool isSystemProfile = false, + string profileSystemName = null, + int cloneFromProfileId = 0); + + /// + /// Updates an export profile + /// + /// Export profile + void UpdateExportProfile(ExportProfile profile); + + /// + /// Deletes an export profile + /// + /// Export profile + /// Whether to delete system profiles + void DeleteExportProfile(ExportProfile profile, bool force = false); + + /// + /// Get queryable export profiles + /// + /// Whether to filter enabled or disabled profiles + /// Export profiles + IQueryable GetExportProfiles(bool? enabled = null); + + /// + /// Gets an export profile by identifier + /// + /// Export profile identifier + /// Export profile + ExportProfile GetExportProfileById(int id); + + /// + /// Gets system export profile by provider system name + /// + /// Provider system name + /// + ExportProfile GetSystemExportProfile(string providerSystemName); + + /// + /// Gets export profiles by provider system name + /// + /// Provider system name + /// List of export profiles + IList GetExportProfilesBySystemName(string providerSystemName); + + + /// + /// Load all export providers + /// + /// Store identifier + /// Whether to load hidden providers + /// Export providers + IEnumerable> LoadAllExportProviders(int storeId = 0, bool showHidden = true); + + /// + /// Load export provider by system name + /// + /// Provider system name + /// Store identifier + /// Export provider + Provider LoadProvider(string systemName, int storeId = 0); + + /// + /// Get export deployment by identifier + /// + /// Export deployment identifier + /// Export deployment + ExportDeployment GetExportDeploymentById(int id); + + /// + /// Update an export deployment + /// + /// Export deployment + void UpdateExportDeployment(ExportDeployment deployment); + + /// + /// Deleted an export deployment + /// + /// Export deployment + void DeleteExportDeployment(ExportDeployment deployment); + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs new file mode 100644 index 0000000000..60be2e11a9 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs @@ -0,0 +1,35 @@ +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange.Export +{ + public partial interface IExportProvider : IProvider, IUserEditable + { + /// + /// The exported entity type + /// + ExportEntityType EntityType { get; } + + /// + /// File extension of the export files (without dot). Return null for a non file based, on-the-fly export. + /// + string FileExtension { get; } + + /// + /// Get provider specific configuration information. Return null when no provider specific configuration is required. + /// + ExportConfigurationInfo ConfigurationInfo { get; } + + /// + /// Export data to a file + /// + /// Export execution context + void Execute(ExportExecuteContext context); + + /// + /// Called once per store when export execution ended + /// + /// Export execution context + void OnExecuted(ExportExecuteContext context); + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CategoryExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CategoryExportContext.cs new file mode 100644 index 0000000000..ab6665b31e --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CategoryExportContext.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Media; + +namespace SmartStore.Services.DataExchange.Export.Internal +{ + internal class CategoryExportContext + { + protected List _categoryIds; + protected List _pictureIds; + + private Func> _funcProductCategories; + private Func> _funcPictures; + + private LazyMultimap _productCategories; + private LazyMultimap _pictures; + + public CategoryExportContext( + IEnumerable categories, + Func> productCategories, + Func> pictures) + { + if (categories == null) + { + _categoryIds = new List(); + _pictureIds = new List(); + } + else + { + _categoryIds = new List(categories.Select(x => x.Id)); + _pictureIds = new List(categories.Where(x => (x.PictureId ?? 0) != 0).Select(x => x.PictureId ?? 0)); + } + + _funcProductCategories = productCategories; + _funcPictures = pictures; + } + + public void Clear() + { + if (_productCategories != null) + _productCategories.Clear(); + if (_pictures != null) + _pictures.Clear(); + + _categoryIds.Clear(); + _pictureIds.Clear(); + } + + public LazyMultimap ProductCategories + { + get + { + if (_productCategories == null) + { + _productCategories = new LazyMultimap(keys => _funcProductCategories(keys), _categoryIds); + } + return _productCategories; + } + } + + public LazyMultimap Pictures + { + get + { + if (_pictures == null) + { + _pictures = new LazyMultimap(keys => _funcPictures(keys).ToMultimap(x => x.Id, x => x), _pictureIds); + } + return _pictures; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CustomerExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CustomerExportContext.cs new file mode 100644 index 0000000000..918cb3e110 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CustomerExportContext.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Services.DataExchange.Export.Internal +{ + public class CustomerExportContext + { + protected List _customerIds; + + private Func> _funcGenericAttributes; + + private LazyMultimap _genericAttributes; + + public CustomerExportContext( + IEnumerable customers, + Func> genericAttributes) + { + if (customers == null) + { + _customerIds = new List(); + } + else + { + _customerIds = new List(customers.Select(x => x.Id)); + } + + _funcGenericAttributes = genericAttributes; + } + + public void Clear() + { + if (_genericAttributes != null) + _genericAttributes.Clear(); + + _customerIds.Clear(); + } + + public LazyMultimap GenericAttributes + { + get + { + if (_genericAttributes == null) + { + _genericAttributes = new LazyMultimap(keys => _funcGenericAttributes(keys), _customerIds); + } + return _genericAttributes; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs new file mode 100644 index 0000000000..5c6c1315dd --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +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.Stores; +using SmartStore.Core.Logging; + +namespace SmartStore.Services.DataExchange.Export.Internal +{ + internal class DataExporterContext + { + public DataExporterContext( + DataExportRequest request, + CancellationToken cancellationToken, + bool isPreview = false) + { + Request = request; + CancellationToken = cancellationToken; + Filter = XmlHelper.Deserialize(request.Profile.Filtering); + Projection = XmlHelper.Deserialize(request.Profile.Projection); + IsPreview = isPreview; + + FolderContent = request.Profile.GetExportFolder(true, true); + + Categories = new Dictionary(); + CategoryPathes = new Dictionary(); + DeliveryTimes = new Dictionary(); + QuantityUnits = new Dictionary(); + Stores = new Dictionary(); + Languages = new Dictionary(); + Countries = new Dictionary(); + ProductTemplates = new Dictionary(); + CategoryTemplates = new Dictionary(); + NewsletterSubscriptions = new HashSet(); + + RecordsPerStore = new Dictionary(); + EntityIdsLoaded = new List(); + EntityIdsPerSegment = new List(); + + Result = new DataExportResult + { + FileFolder = (IsFileBasedExport ? FolderContent : null) + }; + + ExecuteContext = new ExportExecuteContext(Result, CancellationToken, FolderContent); + ExecuteContext.Projection = XmlHelper.Deserialize(request.Profile.Projection); + + if (!IsPreview) + { + ExecuteContext.ProgressValueSetter = Request.ProgressValueSetter; + } + } + + /// + /// All entity identifiers per export + /// + public List EntityIdsLoaded { get; set; } + public void SetLoadedEntityIds(IEnumerable ids) + { + EntityIdsLoaded = EntityIdsLoaded + .Union(ids) + .Distinct() + .ToList(); + } + + /// + /// All entity identifiers per segment (to avoid exporting products multiple times) + /// + public List EntityIdsPerSegment { get; set; } + + public int RecordCount { get; set; } + public Dictionary RecordsPerStore { get; set; } + public string ProgressInfo { get; 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)); + } + + public ExportFilter Filter { get; private set; } + public ExportProjection Projection { get; private set; } + public Currency ContextCurrency { get; set; } + public Customer ContextCustomer { get; set; } + public Language ContextLanguage { get; set; } + + public TraceLogger Log { get; set; } + public Store Store { get; set; } + + public string FolderContent { get; private set; } + + 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; } + public Dictionary DeliveryTimes { get; set; } + public Dictionary QuantityUnits { get; set; } + public Dictionary Stores { get; set; } + public Dictionary Languages { get; set; } + public Dictionary Countries { get; set; } + public Dictionary ProductTemplates { get; set; } + 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; } + public ManufacturerExportContext ManufacturerExportContext { get; set; } + public CategoryExportContext CategoryExportContext { get; set; } + public CustomerExportContext CustomerExportContext { get; set; } + + public ExportExecuteContext ExecuteContext { get; set; } + public DataExportResult Result { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DynamicEntity.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DynamicEntity.cs new file mode 100644 index 0000000000..874d43e7e3 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DynamicEntity.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using SmartStore.ComponentModel; + +namespace SmartStore.Services.DataExchange.Export.Internal +{ + internal class DynamicEntity : HybridExpando + { + public DynamicEntity(DynamicEntity dynamicEntity) + : this(dynamicEntity.WrappedObject) + { + MergeRange(dynamicEntity); + } + + public DynamicEntity(object entity) + : base(entity) + { + base.Properties["Entity"] = entity; + } + + public void Merge(string name, object value) + { + Properties[name] = value; + } + + public void MergeRange(IDictionary other) + { + foreach (var kvp in other) + { + Properties[kvp.Key] = kvp.Value; + } + } + + protected override bool TrySetMemberCore(string name, object value) + { + Properties[name] = value; + return true; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ManufacturerExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ManufacturerExportContext.cs new file mode 100644 index 0000000000..82c607f5ad --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ManufacturerExportContext.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Media; + +namespace SmartStore.Services.DataExchange.Export.Internal +{ + internal class ManufacturerExportContext + { + protected List _manufacturerIds; + protected List _pictureIds; + + private Func> _funcProductManufacturers; + private Func> _funcPictures; + + private LazyMultimap _productManufacturers; + private LazyMultimap _pictures; + + public ManufacturerExportContext( + IEnumerable manufacturers, + Func> productManufacturers, + Func> pictures) + { + if (manufacturers == null) + { + _manufacturerIds = new List(); + _pictureIds = new List(); + } + else + { + _manufacturerIds = new List(manufacturers.Select(x => x.Id)); + _pictureIds = new List(manufacturers.Where(x => (x.PictureId ?? 0) != 0).Select(x => x.PictureId ?? 0)); + } + + _funcProductManufacturers = productManufacturers; + _funcPictures = pictures; + } + + public void Clear() + { + if (_productManufacturers != null) + _productManufacturers.Clear(); + if (_pictures != null) + _pictures.Clear(); + + _manufacturerIds.Clear(); + _pictureIds.Clear(); + } + + public LazyMultimap ProductManufacturers + { + get + { + if (_productManufacturers == null) + { + _productManufacturers = new LazyMultimap(keys => _funcProductManufacturers(keys), _manufacturerIds); + } + return _productManufacturers; + } + } + + public LazyMultimap Pictures + { + get + { + if (_pictures == null) + { + _pictures = new LazyMultimap(keys => _funcPictures(keys).ToMultimap(x => x.Id, x => x), _pictureIds); + } + return _pictures; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/OrderExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/OrderExportContext.cs new file mode 100644 index 0000000000..9ebe5c25a5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/OrderExportContext.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Shipping; + +namespace SmartStore.Services.DataExchange.Export.Internal +{ + internal class OrderExportContext + { + protected List _orderIds; + protected List _customerIds; + protected List _addressIds; + + private Func> _funcCustomers; + private Func> _funcRewardPointsHistories; + private Func> _funcAddresses; + private Func> _funcOrderItems; + private Func> _funcShipments; + + private LazyMultimap _customers; + private LazyMultimap _rewardPointsHistories; + private LazyMultimap
_addresses; + private LazyMultimap _orderItems; + private LazyMultimap _shipments; + + public OrderExportContext(IEnumerable orders, + Func> customers, + Func> rewardPointsHistory, + Func> addresses, + Func> orderItems, + Func> shipments) + { + if (orders == null) + { + _orderIds = new List(); + _customerIds = new List(); + _addressIds = new List(); + } + else + { + _orderIds = new List(orders.Select(x => x.Id)); + _customerIds = new List(orders.Select(x => x.CustomerId)); + + _addressIds = orders.Select(x => x.BillingAddressId) + .Union(orders.Select(x => x.ShippingAddressId ?? 0)) + .Where(x => x != 0) + .Distinct() + .ToList(); + } + + _funcCustomers = customers; + _funcRewardPointsHistories = rewardPointsHistory; + _funcAddresses = addresses; + _funcOrderItems = orderItems; + _funcShipments = shipments; + } + + public void Clear() + { + if (_customers != null) + _customers.Clear(); + if (_rewardPointsHistories != null) + _rewardPointsHistories.Clear(); + if (_addresses != null) + _addresses.Clear(); + if (_orderItems != null) + _orderItems.Clear(); + if (_shipments != null) + _shipments.Clear(); + + _orderIds.Clear(); + _customerIds.Clear(); + _addressIds.Clear(); + } + + public LazyMultimap Customers + { + get + { + if (_customers == null) + { + _customers = new LazyMultimap(keys => _funcCustomers(keys).ToMultimap(x => x.Id, x => x), _customerIds); + } + return _customers; + } + } + + public LazyMultimap RewardPointsHistories + { + get + { + if (_rewardPointsHistories == null) + { + _rewardPointsHistories = new LazyMultimap(keys => _funcRewardPointsHistories(keys), _customerIds); + } + return _rewardPointsHistories; + } + } + + public LazyMultimap
Addresses + { + get + { + if (_addresses == null) + { + _addresses = new LazyMultimap
(keys => _funcAddresses(keys).ToMultimap(x => x.Id, x => x), _addressIds); + } + return _addresses; + } + } + + public LazyMultimap OrderItems + { + get + { + if (_orderItems == null) + { + _orderItems = new LazyMultimap(keys => _funcOrderItems(keys), _orderIds); + } + return _orderItems; + } + } + + public LazyMultimap Shipments + { + get + { + if (_shipments == null) + { + _shipments = new LazyMultimap(keys => _funcShipments(keys), _orderIds); + } + return _shipments; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ProductExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ProductExportContext.cs new file mode 100644 index 0000000000..6a76e1ffef --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ProductExportContext.cs @@ -0,0 +1,148 @@ +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/ISyncMappingService.cs b/src/Libraries/SmartStore.Services/DataExchange/ISyncMappingService.cs new file mode 100644 index 0000000000..eb72d07e19 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ISyncMappingService.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core; +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Services.DataExchange +{ + public partial interface ISyncMappingService + { + /// + /// Inserts a sync mapping entity + /// + /// Sync mapping + void InsertSyncMapping(SyncMapping mapping); + + /// + /// Inserts a range of sync mapping entities + /// + /// A sequence of sync mappings + void InsertSyncMappings(IEnumerable mappings); + + /// + /// Gets all sync mappings + /// + /// 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. + /// SyncMappings + IList GetAllSyncMappings(string contextName = null, string entityName = null); + + /// + /// Gets a sync mapping record by (target) entity id, name and context name. + /// + /// Entity nd + /// Entity name + /// Context name + /// SyncMapping + SyncMapping GetSyncMappingByEntity(int entityId, string entityName, string contextName); + + /// + /// Gets a sync mapping record by (external) source key, entity name and context name. + /// + /// Source key + /// Entity name + /// Context name + /// SyncMappings + SyncMapping GetSyncMappingBySource(string sourceKey, string entityName, string contextName); + + /// + /// Deletes a sync mapping entity + /// + /// Sync mapping + void DeleteSyncMapping(SyncMapping mapping); + + /// + /// Deletes a range of sync mapping entities + /// + /// Sync mappings + void DeleteSyncMappings(IEnumerable mappings); + + /// + /// Deletes all sync mapping entities referencing the specified entity + /// + /// The entity + void DeleteSyncMappingsFor(T entity) where T : BaseEntity; + + /// + /// Deletes a range of sync mapping entities + /// + /// The context (external application) name. + /// The entity name. Leave null to delete all context specific mappings regardless of entity name. + /// SyncMappings + void DeleteSyncMappings(string contextName, string entityName = null); + + /// + /// Updates a sync mapping entity + /// + /// Sync mapping + void UpdateSyncMapping(SyncMapping mapping); + } + + + 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); + + if (entity is SyncMapping) + { + throw Error.InvalidOperation("Cannot insert a sync mapping record for a SyncMapping entity"); + } + + if (entity.IsTransientRecord()) + { + throw Error.InvalidOperation("Cannot insert a sync mapping record for a transient (unsaved) entity"); + } + + var mapping = new SyncMapping + { + ContextName = contextName, + EntityId = entity.Id, + EntityName = typeof(T).Name, + SourceKey = sourceKey + }; + + return mapping; + } + + public static SyncMapping GetSyncMappingByEntity(this ISyncMappingService svc, T entity, string contextName) where T : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotEmpty(() => contextName); + + if (entity is SyncMapping) + { + throw Error.InvalidOperation("Cannot get a sync mapping record for a SyncMapping entity"); + } + + if (entity.IsTransientRecord()) + { + throw Error.InvalidOperation("Cannot get a sync mapping record for a transient (unsaved) entity"); + } + + return svc.GetSyncMappingByEntity(entity.Id, typeof(T).Name, contextName); + } + + /// + /// Inserts a range of sync mapping entities by specifying two equal sized sequences for entity ids and source keys. + /// + /// Context name for all mappings + /// Entity name for all mappings + /// A sequence of entity ids + /// A sequence of source keys + /// List of persisted sync mappings + /// 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); + + if (!entityIds.Any() || !sourceKeys.Any() || entityIds.Count() != sourceKeys.Count()) + { + throw Error.InvalidOperation("Both sequences must contain at least one element and must be of equal size."); + } + + var mappings = new List(); + var arrIds = entityIds.ToArray(); + var arrKeys = sourceKeys.ToArray(); + + for (int i = 0; i < arrIds.Length; i++) + { + mappings.Add(new SyncMapping + { + ContextName = contextName, + EntityName = entityName, + EntityId = arrIds[i], + SourceKey = arrKeys[i] + }); + } + + svc.InsertSyncMappings(mappings); + + return mappings; + } + + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs new file mode 100644 index 0000000000..14a0ac77e9 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace SmartStore.Services.DataExchange.Import +{ + public class ColumnMap + { + // maps source column to property + private readonly Dictionary _map = new Dictionary(StringComparer.OrdinalIgnoreCase); + + private static bool IsIndexed(string name) + { + return (name.EmptyNull().EndsWith("]") && name.EmptyNull().Contains("[")); + } + + private static string CreateSourceName(string name, string index) + { + if (index.HasValue()) + { + name += String.Concat("[", index, "]"); + } + + return name; + } + + public IReadOnlyDictionary Mappings + { + get { return _map; } + } + + public static bool ParseSourceName(string sourceName, out string nameWithoutIndex, out string index) + { + nameWithoutIndex = sourceName; + index = null; + + var result = true; + + if (sourceName.HasValue() && IsIndexed(sourceName)) + { + var x1 = sourceName.IndexOf('['); + var x2 = sourceName.IndexOf(']', x1); + + if (x1 != -1 && x2 != -1 && x2 > x1) + { + nameWithoutIndex = sourceName.Substring(0, x1); + index = sourceName.Substring(x1 + 1, x2 - x1 - 1); + } + else + { + result = false; + } + } + + return result; + } + + public void AddMapping(string sourceName, string mappedName, string defaultValue = null) + { + AddMapping(sourceName, null, mappedName, defaultValue); + } + + public void AddMapping(string sourceName, string index, string mappedName, string defaultValue = null) + { + Guard.ArgumentNotEmpty(() => sourceName); + Guard.ArgumentNotEmpty(() => mappedName); + + var key = CreateSourceName(sourceName, index); + + _map[key] = new ColumnMappingItem + { + SoureName = key, + MappedName = mappedName, + Default = defaultValue + }; + } + + /// + /// Gets a mapped column value + /// + /// The name of the column to get a mapped value for. + /// The column index, e.g. a language code (de, en etc.) + /// The mapped column value OR - if the name is unmapped - a value with the passed [] + public ColumnMappingItem GetMapping(string sourceName, string index) + { + return GetMapping(CreateSourceName(sourceName, index)); + } + + /// + /// Gets a mapped column value + /// + /// The name of the column to get a mapped value for. + /// The mapped column value OR - if the name is unmapped - the value of the passed + public ColumnMappingItem GetMapping(string sourceName) + { + ColumnMappingItem result; + + if (_map.TryGetValue(sourceName, out result)) + { + return result; + } + + return new ColumnMappingItem { SoureName = sourceName, MappedName = sourceName }; + } + } + + + [JsonObject(MemberSerialization.OptIn)] + public class ColumnMappingItem + { + private bool? _ignored; + + /// + /// The source name + /// + [JsonIgnore] + public string SoureName { get; set; } + + /// + /// The mapped name + /// + [JsonProperty] + public string MappedName { get; set; } + + /// + /// An optional default value + /// + [JsonProperty] + public string Default { get; set; } + + /// + /// Indicates whether to explicitly ignore this property + /// + public bool IgnoreProperty + { + get + { + if (_ignored == null) + { + _ignored = Default != null && Default == "[IGNOREPROPERTY]"; + } + + return _ignored.Value; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMapConverter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMapConverter.cs new file mode 100644 index 0000000000..c46b90b82a --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMapConverter.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Newtonsoft.Json; +using SmartStore.ComponentModel; + +namespace SmartStore.Services.DataExchange.Import +{ + public class ColumnMapConverter : TypeConverterBase + { + public ColumnMapConverter() + : 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) + { + var dict = JsonConvert.DeserializeObject>((string)value); + var map = new ColumnMap(); + + foreach (var kvp in dict) + { + map.AddMapping(kvp.Key, null, kvp.Value.MappedName, kvp.Value.Default); + } + + return map; + } + + return base.ConvertFrom(culture, value); + } + + public T ConvertFrom(string value) + { + if (value.HasValue()) + return (T)ConvertFrom(CultureInfo.InvariantCulture, value); + + return default(T); + } + + public override object ConvertTo(CultureInfo culture, string format, object value, Type to) + { + if (to == typeof(string)) + { + if (value is ColumnMap) + { + return JsonConvert.SerializeObject(((ColumnMap)value).Mappings); + } + else + { + return string.Empty; + } + } + + return base.ConvertTo(culture, format, value, to); + } + + public string ConvertTo(object value) + { + return (string)ConvertTo(CultureInfo.InvariantCulture, null, value, typeof(string)); + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs new file mode 100644 index 0000000000..a25235fe00 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs @@ -0,0 +1,34 @@ +using SmartStore.Services.Tasks; + +namespace SmartStore.Services.DataExchange.Import +{ + // note: namespace persisted in ScheduleTask.Type + public partial class DataImportTask : ITask + { + private readonly IDataImporter _importer; + private readonly IImportProfileService _importProfileService; + + public DataImportTask( + IDataImporter importer, + IImportProfileService importProfileService) + { + _importer = importer; + _importProfileService = importProfileService; + } + + public void Execute(TaskExecutionContext ctx) + { + var profileId = ctx.ScheduleTask.Alias.ToInt(); + var profile = _importProfileService.GetImportProfileById(profileId); + + var request = new DataImportRequest(profile); + + request.ProgressValueSetter = delegate (int val, int max, string msg) + { + ctx.SetProgress(val, max, msg, true); + }; + + _importer.Import(request, ctx.CancellationToken); + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs new file mode 100644 index 0000000000..aeb4b81897 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -0,0 +1,360 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Linq; +using SmartStore.Core; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Email; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.Services.DataExchange.Csv; +using SmartStore.Services.DataExchange.Import.Internal; +using SmartStore.Services.Localization; +using SmartStore.Services.Messages; +using SmartStore.Services.Security; +using SmartStore.Utilities; + +namespace SmartStore.Services.DataExchange.Import +{ + public partial class DataImporter : IDataImporter + { + private readonly ICommonServices _services; + private readonly IImportProfileService _importProfileService; + private readonly ILanguageService _languageService; + private readonly Func _importerFactory; + private readonly Lazy _emailAccountService; + private readonly Lazy _emailSender; + private readonly Lazy _contactDataSettings; + private readonly Lazy _dataExchangeSettings; + + public DataImporter( + ICommonServices services, + IImportProfileService importProfileService, + ILanguageService languageService, + Func importerFactory, + Lazy emailAccountService, + Lazy emailSender, + Lazy contactDataSettings, + Lazy dataExchangeSettings) + { + _services = services; + _importProfileService = importProfileService; + _languageService = languageService; + _importerFactory = importerFactory; + _emailAccountService = emailAccountService; + _emailSender = emailSender; + _contactDataSettings = contactDataSettings; + _dataExchangeSettings = dataExchangeSettings; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + private bool HasPermission(DataImporterContext ctx) + { + if (ctx.Request.HasPermission) + return true; + + var customer = _services.WorkContext.CurrentCustomer; + + if (customer.SystemName == SystemCustomerNames.BackgroundTask) + return true; + + if (ctx.Request.Profile.EntityType == ImportEntityType.Product || ctx.Request.Profile.EntityType == ImportEntityType.Category) + return _services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog, customer); + + if (ctx.Request.Profile.EntityType == ImportEntityType.Customer) + return _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomers, customer); + + if (ctx.Request.Profile.EntityType == ImportEntityType.NewsLetterSubscription) + return _services.Permissions.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers, customer); + + return true; + } + + private void LogResult(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()); + } + } + + private void SendCompletionEmail(DataImporterContext ctx) + { + var emailAccount = _emailAccountService.Value.GetDefaultEmailAccount(); + var smtpContext = new SmtpContext(emailAccount); + var message = new EmailMessage(); + + var store = _services.StoreContext.CurrentStore; + var storeInfo = "{0} ({1})".FormatInvariant(store.Name, store.Url); + var intro = _services.Localization.GetResource("Admin.DataExchange.Import.CompletedEmail.Body").FormatInvariant(storeInfo); + var body = new StringBuilder(intro); + var result = ctx.ExecuteContext.Result; + + if (result.LastError.HasValue()) + { + body.AppendFormat("

{0}

", result.LastError); + } + + body.Append("

"); + + body.AppendFormat("

{0}: {1} · {2}: {3}
", + T("Admin.Common.TotalRows"), result.TotalRecords, + T("Admin.Common.Skipped"), result.SkippedRecords); + + body.AppendFormat("
{0}: {1} · {2}: {3}
", + T("Admin.Common.NewRecords"), result.NewRecords, + T("Admin.Common.Updated"), result.ModifiedRecords); + + body.AppendFormat("
{0}: {1} · {2}: {3}
", + T("Admin.Common.Errors"), result.Errors, + T("Admin.Common.Warnings"), result.Warnings); + + body.Append("

"); + + message.From = new EmailAddress(emailAccount.Email, emailAccount.DisplayName); + + if (_contactDataSettings.Value.WebmasterEmailAddress.HasValue()) + message.To.Add(new EmailAddress(_contactDataSettings.Value.WebmasterEmailAddress)); + + if (message.To.Count == 0 && _contactDataSettings.Value.CompanyEmailAddress.HasValue()) + message.To.Add(new EmailAddress(_contactDataSettings.Value.CompanyEmailAddress)); + + if (message.To.Count == 0) + message.To.Add(new EmailAddress(emailAccount.Email, emailAccount.DisplayName)); + + message.Subject = T("Admin.DataExchange.Import.CompletedEmail.Subject").Text.FormatInvariant(ctx.Request.Profile.Name); + + message.Body = body.ToString(); + + _emailSender.Value.SendEmail(smtpContext, message); + + //Core.Infrastructure.EngineContext.Current.Resolve().InsertQueuedEmail(new QueuedEmail + //{ + // From = emailAccount.Email, + // FromName = emailAccount.DisplayName, + // To = message.To.First().Address, + // Subject = message.Subject, + // Body = message.Body, + // CreatedOnUtc = DateTime.UtcNow, + // EmailAccountId = emailAccount.Id, + // SendManually = true + //}); + //_services.DbContext.SaveChanges(); + } + + private void ImportCoreInner(DataImporterContext ctx, string filePath) + { + 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)); + } + + CsvConfiguration csvConfiguration = null; + var extension = Path.GetExtension(filePath); + + if ((new string[] { ".csv", ".txt", ".tab" }).Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + var converter = new CsvConfigurationConverter(); + csvConfiguration = converter.ConvertFrom(ctx.Request.Profile.FileTypeConfiguration); + } + + if (csvConfiguration == null) + { + csvConfiguration = CsvConfiguration.ExcelFriendlyConfiguration; + } + + using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + ctx.ExecuteContext.DataTable = LightweightDataTable.FromFile( + Path.GetFileName(filePath), + 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."); + } + } + + private void ImportCoreOuter(DataImporterContext ctx) + { + if (ctx.Request.Profile == null || !ctx.Request.Profile.Enabled) + return; + + var logPath = ctx.Request.Profile.GetImportLogPath(); + + FileSystemHelper.Delete(logPath); + + using (var logger = new TraceLogger(logPath)) + { + try + { + ctx.Log = logger; + + 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)); + } + catch (Exception exception) + { + ctx.ExecuteContext.Result.AddError(exception); + } + finally + { + 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) + { + ctx.ExecuteContext.Result.AddError(exception); + } + + try + { + SendCompletionEmail(ctx); + } + catch (Exception exception) + { + ctx.ExecuteContext.Result.AddError(exception); + } + + try + { + ctx.ExecuteContext.Result.EndDateUtc = DateTime.UtcNow; + + LogResult(ctx); + } + catch (Exception exception) + { + logger.ErrorsAll(exception); + } + + try + { + ctx.Request.Profile.ResultInfo = XmlHelper.Serialize(ctx.ExecuteContext.Result.Clone()); + + _importProfileService.UpdateImportProfile(ctx.Request.Profile); + } + catch (Exception exception) + { + logger.ErrorsAll(exception); + } + + try + { + ctx.Request.CustomData.Clear(); + ctx.Log = null; + } + catch (Exception exception) + { + logger.ErrorsAll(exception); + } + } + } + } + + public void Import(DataImportRequest request, CancellationToken cancellationToken) + { + Guard.ArgumentNotNull(() => request); + Guard.ArgumentNotNull(() => cancellationToken); + + var ctx = new DataImporterContext(request, cancellationToken, T("Admin.DataExchange.Import.ProgressInfo")); + + ImportCoreOuter(ctx); + + cancellationToken.ThrowIfCancellationRequested(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/IDataTable.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/IDataTable.cs new file mode 100644 index 0000000000..f35c328fb6 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/IDataTable.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Services.DataExchange.Import +{ + public interface IDataColumn + { + string Name { get; } + Type Type { get; } + } + + public interface IDataRow + { + object[] Values { get; } + object this[int index] { get; set; } + object this[string name] { get; set; } + + IDataTable Table { get; } + } + + public interface IDataTable + { + bool HasColumn(string name); + int GetColumnIndex(string name); + IList Columns { get; } + IList Rows { get; } + } + + public static class IDataRowExtensions + { + public static object GetValue(this IDataRow row, int index) + { + return row[index]; + } + + public static object GetValue(this IDataRow row, string name) + { + return row[name]; + } + + public static void SetValue(this IDataRow row, int index, object value) + { + row[index] = value; + } + + public static void SetValue(this IDataRow row, string name, object value) + { + row[name] = value; + } + + public static bool TryGetValue(this IDataRow row, string name, out object value) + { + value = null; + + var index = row.Table.GetColumnIndex(name); + if (index < 0) + return false; + + value = row[index]; + return true; + } + + public static bool TrySetValue(this IDataRow row, string name, object value) + { + var index = row.Table.GetColumnIndex(name); + if (index < 0) + return false; + + row[index] = value; + return true; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/LightweightDataTable.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/LightweightDataTable.cs new file mode 100644 index 0000000000..52be0a8dfa --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/LightweightDataTable.cs @@ -0,0 +1,406 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.Data.Common; +using System.Dynamic; +using System.IO; +using System.Linq; +using System.Web; +using SmartStore.Services.DataExchange.Csv; +using SmartStore.Services.DataExchange.Excel; + +namespace SmartStore.Services.DataExchange.Import +{ + public class LightweightDataTable : IDataTable + { + private readonly IList _columns; + private readonly IList _rows; + private readonly IDictionary _columnIndexes; + private readonly IDictionary _alternativeColumnIndexes; + + public LightweightDataTable(IList columns, IList data) + { + Guard.ArgumentNotNull(() => columns); + Guard.ArgumentNotNull(() => data); + + if (columns.Select(x => x.Name.ToLower()).Distinct().ToArray().Length != columns.Count) + { + throw Error.Argument("columns", "The columns collection cannot contain duplicate column names."); + } + + _columns = new ReadOnlyCollection(columns); + + TrimData(data); + + var rows = data.Select(x => new LightweightDataRow(this, x)).Cast().ToList(); + _rows = new ReadOnlyCollection(rows); + + _columnIndexes = new Dictionary(StringComparer.OrdinalIgnoreCase); + _alternativeColumnIndexes = new Dictionary(StringComparer.OrdinalIgnoreCase); + + for (var i = 0; i < columns.Count; i++) + { + var name = columns[i].Name; + var alternativeName = GetAlternativeColumnNameFor(name); + + _columnIndexes[name] = i; + + if (!alternativeName.IsCaseInsensitiveEqual(name)) + _alternativeColumnIndexes[alternativeName] = i; + } + } + + private static void TrimData(IList data) + { + // When a user deletes content instead of whole rows from an excel sheet, + // our data table contains completely empty rows at the end. + // Here we get rid of them as they are absolutely useless. + for (int i = data.Count - 1; i >= 0; i--) + { + var allColumnsEmpty = data[i].All(x => x == null || x == DBNull.Value); + if (allColumnsEmpty) + { + data.RemoveAt(i); + //i--; + } + else + { + // get out here on the first occurence of a NON-empty row + break; + } + } + } + + public bool HasColumn(string name) + { + if (name.HasValue()) + { + return (_columnIndexes.ContainsKey(name) || _alternativeColumnIndexes.ContainsKey(name)); + } + + return false; + } + + public int GetColumnIndex(string name) + { + int index; + + if (name.HasValue()) + { + if (_columnIndexes.TryGetValue(name, out index)) + return index; + + if (_alternativeColumnIndexes.TryGetValue(name, out index)) + return index; + } + + return -1; + } + + public IList Columns + { + get + { + return _columns; + } + } + + public IList Rows + { + get + { + return _rows; + } + } + + public static string GetAlternativeColumnNameFor(string name) + { + if (name.IsEmpty()) + return name; + + return name + .Replace(" ", "") + .Replace("-", "") + .Replace("_", ""); + } + + public static IDataTable FromPostedFile( + HttpPostedFileBase file, + int skip = 0, + int take = int.MaxValue) + { + Guard.ArgumentNotNull(() => file); + + return FromFile(file.FileName, file.InputStream, file.ContentLength, new CsvConfiguration(), skip, take); + } + + public static IDataTable FromPostedFile( + HttpPostedFileBase file, + CsvConfiguration configuration, + int skip = 0, + int take = int.MaxValue) + { + Guard.ArgumentNotNull(() => file); + + return FromFile(file.FileName, file.InputStream, file.ContentLength, configuration, skip, take); + } + + public static IDataTable FromFile( + string fileName, + Stream stream, + long contentLength, + CsvConfiguration configuration, + int skip = 0, + int take = int.MaxValue) + { + Guard.ArgumentNotEmpty(() => fileName); + Guard.ArgumentNotNull(() => stream); + Guard.ArgumentNotNull(() => configuration); + + if (contentLength == 0) + { + throw Error.Argument("fileName", "The posted file '{0}' does not contain any data.".FormatInvariant(fileName)); + } + + IDataReader dataReader = null; + + try + { + var fileExt = System.IO.Path.GetExtension(fileName).ToLowerInvariant(); + + switch (fileExt) + { + case ".xlsx": + dataReader = new ExcelDataReader(stream, true); // TODO: let the user specify if excel file has headers + break; + default: + dataReader = new CsvDataReader(new StreamReader(stream), configuration); + break; + } + + var table = LightweightDataTable.FromDataReader(dataReader, skip, take); + + if (table.Columns.Count == 0 || table.Rows.Count == 0) + { + throw Error.InvalidOperation("The posted file '{0}' does not contain any columns or data rows.".FormatInvariant(fileName)); + } + + return table; + } + catch (Exception ex) + { + throw ex; + } + finally + { + if (dataReader != null) + { + if (!dataReader.IsClosed) + { + dataReader.Dispose(); + } + dataReader = null; + } + } + } + + public static IDataTable FromDataReader( + IDataReader reader, + int skip = 0, + int take = int.MaxValue) + { + Guard.ArgumentNotNull(() => reader); + + if (reader.IsClosed) + throw new ArgumentException("This operation is invalid when the reader is closed.", "reader"); + + var columns = new List(reader.FieldCount); + var data = new List(); + + var schema = reader.GetSchemaTable(); + + var nameCol = schema.Columns[SchemaTableColumn.ColumnName]; + var typeCol = schema.Columns[SchemaTableColumn.DataType]; + + foreach (DataRow schemaRow in schema.Rows) + { + var column = new LightweightDataColumn((string)schemaRow[nameCol], (Type)schemaRow[typeCol]); + columns.Add(column); + } + + var fieldCount = reader.FieldCount; + + take = Math.Min(take, int.MaxValue - skip); + + int i = -1; + while (reader.Read()) + { + i++; + + if (skip > i) + continue; + + if (i >= skip + take) + break; + + var values = new object[fieldCount]; + reader.GetValues(values); + data.Add(values); + } + + var table = new LightweightDataTable(columns, data); + + return table; + } + } + + internal class LightweightDataRow : DynamicObject, IDataRow + { + private readonly IDataTable _table; + private readonly object[] _values; + + public LightweightDataRow(IDataTable table, object[] values) + { + Guard.ArgumentNotNull(() => values); + + if (table.Columns.Count != values.Length) + { + throw new ArgumentOutOfRangeException( + "values", + "The number of row values must match the number of columns. Expected: {0}, actual: {1}".FormatInvariant(table.Columns.Count, values.Length)); + } + + _table = table; + _values = values; + } + + public IDataTable Table + { + get { return _table; } + } + + public object[] Values + { + get { return _values; } + } + + public object this[string name] + { + get + { + var index = _table.GetColumnIndex(name); + if (index < 0) + throw new KeyNotFoundException(); + + return _values[index]; + } + set + { + var index = _table.GetColumnIndex(name); + if (index < 0) + throw new KeyNotFoundException(); + + _values[index] = value; + } + } + + public object this[int index] + { + get + { + ValidateColumnIndex(index); + return _values[index]; + } + set + { + ValidateColumnIndex(index); + _values[index] = value; + } + } + + private void ValidateColumnIndex(int index) + { + if (index < 0 || index >= _table.Columns.Count) + { + throw new ArgumentOutOfRangeException("index", index, + "Column index must be included within [0, {0}], but specified column index was: '{1}'.".FormatInvariant(_table.Columns.Count, index)); + } + } + + public override IEnumerable GetDynamicMemberNames() + { + return _table.Columns.Select(x => x.Name); + } + + + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + result = null; + + if (this.TryGetValue(binder.Name, out result)) + { + return true; + } + + return false; + } + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + return this.TrySetValue(binder.Name, value); + } + + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + { + result = null; + + try + { + result = _values[(int)indexes[0]]; + return true; + } + catch + { + return false; + } + } + + public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) + { + try + { + _values[(int)indexes[0]] = value; + return true; + } + catch + { + return false; + } + } + } + + internal class LightweightDataColumn : IDataColumn + { + public LightweightDataColumn(string name, Type type) + { + Guard.ArgumentNotEmpty(() => name); + Guard.ArgumentNotNull(() => type); + + this.Name = name; + this.Type = type; + } + + public string Name + { + get; + private set; + } + + public Type Type + { + get; + private set; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs new file mode 100644 index 0000000000..eff63d8431 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -0,0 +1,302 @@ +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 Autofac; +using SmartStore.Services.Localization; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Seo; +using SmartStore.Services.Seo; +using SmartStore.Core.Data; +using SmartStore.Services.Stores; +using SmartStore.Core.Domain.Stores; + +namespace SmartStore.Services.DataExchange.Import +{ + public abstract class EntityImporterBase : IEntityImporter + { + private const string _imageDownloadFolder = @"Content\DownloadedImages"; + + public DateTime UtcNow + { + get; + private set; + } + + public Dictionary DownloadedItems + { + get; + private set; + } + + public string ImageDownloadFolder + { + get; + private set; + } + + public string ImageFolder + { + get; + private set; + } + + public FileDownloadManagerContext DownloaderContext + { + get; + private set; + } + + public void Execute(ImportExecuteContext context) + { + Import(context); + } + + protected abstract void Import(ImportExecuteContext context); + + protected void Initialize(ImportExecuteContext context) + { + UtcNow = DateTime.UtcNow; + DownloadedItems = new Dictionary(); + ImageDownloadFolder = Path.Combine(context.ImportFolder, _imageDownloadFolder); + + var settings = context.DataExchangeSettings; + + if (settings.ImageImportFolder.HasValue()) + ImageFolder = Path.Combine(context.ImportFolder, settings.ImageImportFolder); + else + ImageFolder = context.ImportFolder; + + if (!System.IO.Directory.Exists(ImageDownloadFolder)) + System.IO.Directory.CreateDirectory(ImageDownloadFolder); + + DownloaderContext = new FileDownloadManagerContext + { + Timeout = TimeSpan.FromMinutes(settings.ImageDownloadTimeout), + Logger = context.Log, + CancellationToken = context.CancellationToken + }; + + context.Result.TotalRecords = context.DataSegmenter.TotalRows; + } + + public FileDownloadManagerItem CreateDownloadImage(string urlOrPath, string seoName, int displayOrder) + { + var item = new FileDownloadManagerItem + { + Id = displayOrder, + DisplayOrder = displayOrder, + MimeType = MimeTypes.MapNameToMimeType(urlOrPath) + }; + + if (item.MimeType.IsEmpty()) + { + item.MimeType = MediaTypeNames.Image.Jpeg; + } + + var extension = MimeTypes.MapMimeTypeToExtension(item.MimeType); + + if (extension.HasValue()) + { + if (urlOrPath.IsWebUrl()) + { + item.Url = urlOrPath; + item.FileName = "{0}-{1}".FormatInvariant(seoName, item.Id).ToValidFileName(); + + if (DownloadedItems.ContainsKey(urlOrPath)) + { + item.Path = Path.Combine(ImageDownloadFolder, DownloadedItems[urlOrPath]); + item.Success = true; + } + else + { + item.Path = Path.Combine(ImageDownloadFolder, item.FileName + extension.EnsureStartsWith(".")); + } + } + else if (Path.IsPathRooted(urlOrPath)) + { + item.Path = urlOrPath; + item.Success = true; + } + else + { + item.Path = Path.Combine(ImageFolder, urlOrPath); + item.Success = true; + } + + return item; + } + + return null; + } + + public void Succeeded(FileDownloadManagerItem item) + { + if ((item.Success ?? false) && item.Url.HasValue() && !DownloadedItems.ContainsKey(item.Url)) + { + DownloadedItems.Add(item.Url, Path.GetFileName(item.Path)); + } + } + + protected virtual int ProcessLocalizations( + ImportExecuteContext context, + IEnumerable> batch, + IDictionary>> localizableProperties) where TEntity : BaseEntity, ILocalizedEntity + { + Guard.ArgumentNotNull(() => context); + Guard.ArgumentNotNull(() => batch); + Guard.ArgumentNotNull(() => localizableProperties); + + // Perf: determine whether our localizable properties actually have + // counterparts in the source BEFORE import batch begins. This way we spare ourself + // to query over and over for values. + var localizedProps = (from kvp in localizableProperties + where context.DataSegmenter.GetColumnIndexes(kvp.Key).Length > 0 + select kvp.Key).ToArray(); + + if (localizedProps.Length == 0) + { + return 0; + } + + var localizedEntityService = context.Services.Resolve(); + + bool shouldSave = false; + + foreach (var row in batch) + { + foreach (var prop in localizedProps) + { + var lambda = localizableProperties[prop]; + foreach (var lang in context.Languages) + { + var code = lang.UniqueSeoCode; + string value; + + if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) + { + localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); + shouldSave = true; + } + } + } + } + + if (shouldSave) + { + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } + + return 0; + } + + protected virtual int ProcessStoreMappings( + ImportExecuteContext context, + IEnumerable> batch) where TEntity : BaseEntity, IStoreMappingSupported + { + var storeMappingService = context.Services.Resolve(); + var storeMappingRepository = context.Services.Resolve>(); + + storeMappingRepository.AutoCommitEnabled = false; + + foreach (var row in batch) + { + var storeIds = row.GetDataValue>("StoreIds"); + if (!storeIds.IsNullOrEmpty()) + { + storeMappingService.SaveStoreMappings(row.Entity, storeIds.ToArray()); + } + } + + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } + + protected virtual int ProcessSlugs( + ImportExecuteContext context, + IEnumerable> batch, + string entityName) where TEntity : BaseEntity, ISlugSupported + { + var slugMap = new Dictionary(); + UrlRecord urlRecord = null; + + var urlRecordService = context.Services.Resolve(); + var urlRecordRepository = context.Services.Resolve>(); + var seoSettings = context.Services.Resolve(); + + Func slugLookup = ((s) => + { + return (slugMap.ContainsKey(s) ? slugMap[s] : null); + }); + + foreach (var row in batch) + { + try + { + string seName = null; + string localizedName = null; + + if (row.TryGetDataValue("SeName", out seName) || row.IsNew || row.NameChanged) + { + seName = row.Entity.ValidateSeName(seName, row.EntityDisplayName, true, urlRecordService, seoSettings, extraSlugLookup: slugLookup); + + if (row.IsNew) + { + // dont't bother validating SeName for new entities. + urlRecord = new UrlRecord + { + EntityId = row.Entity.Id, + EntityName = entityName, + Slug = seName, + LanguageId = 0, + IsActive = true, + }; + urlRecordRepository.Insert(urlRecord); + } + else + { + urlRecord = urlRecordService.SaveSlug(row.Entity, seName, 0); + } + + if (urlRecord != null) + { + // a new record was inserted to the store: keep track of it for this batch. + slugMap[seName] = urlRecord; + } + } + + // process localized SeNames + foreach (var lang in context.Languages) + { + var hasSeName = row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName); + var hasLocalizedName = row.TryGetDataValue("Name", lang.UniqueSeoCode, out localizedName); + + if (hasSeName || hasLocalizedName) + { + seName = row.Entity.ValidateSeName(seName, localizedName, false, urlRecordService, seoSettings, lang.Id, slugLookup); + urlRecord = urlRecordService.SaveSlug(row.Entity, seName, lang.Id); + if (urlRecord != null) + { + slugMap[seName] = urlRecord; + } + } + } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.Message, row.GetRowInfo(), "SeName"); + } + } + + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/IDataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/IDataImporter.cs new file mode 100644 index 0000000000..8c2fbebf73 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/IDataImporter.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Services.DataExchange.Import +{ + public interface IDataImporter + { + void Import(DataImportRequest request, CancellationToken cancellationToken); + } + + + public class DataImportRequest + { + private readonly static ProgressValueSetter _voidProgressValueSetter = DataImportRequest.SetProgress; + + public DataImportRequest(ImportProfile profile) + { + Guard.ArgumentNotNull(() => profile); + + Profile = profile; + ProgressValueSetter = _voidProgressValueSetter; + + EntitiesToImport = new List(); + CustomData = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public ImportProfile Profile { get; private set; } + + public ProgressValueSetter ProgressValueSetter { get; set; } + + public bool HasPermission { get; set; } + + public IList EntitiesToImport { get; set; } + + public IDictionary CustomData { get; private set; } + + + private static void SetProgress(int val, int max, string msg) + { + // do nothing + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/IEntityImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/IEntityImporter.cs new file mode 100644 index 0000000000..bc64b63531 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/IEntityImporter.cs @@ -0,0 +1,7 @@ +namespace SmartStore.Services.DataExchange.Import +{ + public partial interface IEntityImporter + { + void Execute(ImportExecuteContext context); + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/IImportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/IImportProfileService.cs new file mode 100644 index 0000000000..6f7b7d4f8e --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/IImportProfileService.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Services.DataExchange.Import +{ + public interface IImportProfileService + { + /// + /// Gets a new profile name + /// + /// Entity type + /// Suggestion for a new profile name + string GetNewProfileName(ImportEntityType entityType); + + /// + /// Inserts an import profile + /// + /// Name of the import file + /// Profile name + /// Entity type + /// Inserted import profile + ImportProfile InsertImportProfile(string fileName, string name, ImportEntityType entityType); + + /// + /// Updates an import profile + /// + /// Import profile + void UpdateImportProfile(ImportProfile profile); + + /// + /// Deletes an import profile + /// + /// Import profile + void DeleteImportProfile(ImportProfile profile); + + /// + /// Get queryable import profiles + /// + /// Whether to filter enabled or disabled profiles + /// Import profiles + IQueryable GetImportProfiles(bool? enabled = null); + + /// + /// Gets an import profile by identifier + /// + /// Import profile identifier + /// Import profile + ImportProfile GetImportProfileById(int id); + + /// + /// Gets an import profile by name + /// + /// Name of the import profile + /// Import profile + ImportProfile GetImportProfileByName(string name); + + /// + /// Get all importable entity properties and their localized values + /// + /// Import entity type + /// Importable entity properties + Dictionary GetImportableEntityProperties(ImportEntityType entityType); + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs new file mode 100644 index 0000000000..41129e24f8 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using SmartStore.Core; + +namespace SmartStore.Services.DataExchange.Import +{ + public class ImportDataSegmenter + { + private const int BATCHSIZE = 100; + + private readonly IDataTable _table; + private object[] _currentBatch; + private readonly IPageable _pageable; + private bool _bof; + private CultureInfo _culture; + private ColumnMap _columnMap; + + private readonly IDictionary _columnIndexes = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public ImportDataSegmenter(IDataTable table, ColumnMap map) + { + Guard.ArgumentNotNull(() => table); + Guard.ArgumentNotNull(() => map); + + _table = table; + _columnMap = map; + + _bof = true; + _pageable = new PagedList(0, BATCHSIZE, table.Rows.Count); + _culture = CultureInfo.InvariantCulture; + } + + public CultureInfo Culture + { + get + { + return _culture; + } + set + { + _culture = value ?? CultureInfo.InvariantCulture; + } + } + + public ColumnMap ColumnMap + { + get + { + return _columnMap; + } + set + { + _columnMap = value ?? new ColumnMap(); + } + } + + public int TotalRows + { + get { return _table.Rows.Count; } + } + + public int TotalColumns + { + get { return _table.Columns.Count; } + } + + public int CurrentSegment + { + get { return _bof ? 0 : _pageable.PageNumber; } + } + + public int CurrentSegmentFirstRowIndex + { + get { return _pageable.FirstItemIndex; } + } + + public int TotalSegments + { + get { return _pageable.TotalPages; } + } + + public int BatchSize + { + get { return BATCHSIZE; } + } + + /// + /// Determines whether a specific column exists in the underlying data table. + /// + /// The name of the column to find + /// + /// If true and a column with the passed does not exist, + /// this method tests for the existence of any indexed column with the same name. + /// + /// true if the column exists, false otherwise + /// + /// This method takes mapped column names into account. + /// + public bool HasColumn(string name, bool withAnyIndex = false) + { + var result = HasColumn(name, null); + + if (!result && withAnyIndex) + { + // Column does not exist, but withAnyIndex is true: + // Test for existence of any indexed column. + result = GetColumnIndexes(name).Length > 0; + } + + return result; + } + + /// + /// Determines whether the column name[index] exists in the underlying data table. + /// + /// The name of the column to find + /// The index of the column + /// true if the column exists, false otherwise + /// + /// This method takes mapped column names into account. + /// + public bool HasColumn(string name, string index) + { + return _table.HasColumn(_columnMap.GetMapping(name, index).MappedName); + } + + /// + /// Indicates whether to ignore the property that is mapped to columnName + /// + /// The name of the column + /// true ignore, false do not ignore + public bool IsIgnored(string columnName) + { + return IsIgnored(columnName, null); + } + + /// + /// Indicates whether to ignore the property that is mapped to columnName + /// + /// The name of the column + /// The index of the column + /// true ignore, false do not ignore + public bool IsIgnored(string columnName, string index) + { + var mapping = _columnMap.GetMapping(columnName, index); + + return mapping.IgnoreProperty; + } + + /// + /// Returns an array of exisiting index names for a column + /// + /// The name of the columns without index qualification + /// An array of index names + /// + /// If following columns exist in source: Attr[Color], Attr[Size] + /// This method returns: string[] { "Color", "Size" } + /// + public string[] GetColumnIndexes(string name) + { + string[] indexes; + + if (!_columnIndexes.TryGetValue(name, out indexes)) + { + var startsWith = name + "["; + + var columns1 = _columnMap.Mappings + .Where(x => x.Key.StartsWith(startsWith, StringComparison.OrdinalIgnoreCase)) + .Select(x => x.Key); + + var columns2 = _table.Columns + .Where(x => x.Name.StartsWith(startsWith, StringComparison.OrdinalIgnoreCase)) + .Select(x => x.Name); + + indexes = columns1.Concat(columns2) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(x => x.Substring(x.IndexOf("[", StringComparison.OrdinalIgnoreCase) + 1).TrimEnd(']')) + .ToArray(); + + _columnIndexes[name] = indexes; + } + + return indexes; + } + + public void Reset() + { + if (_pageable.PageIndex != 0 && _currentBatch != null) + { + _currentBatch = null; + } + _bof = true; + _pageable.PageIndex = 0; + } + + public bool ReadNextBatch() + { + if (_currentBatch != null) + { + _currentBatch = null; + } + + if (_bof) + { + _bof = false; + return _pageable.TotalCount > 0; + } + + if (_pageable.HasNextPage) + { + _pageable.PageIndex++; + return true; + } + + Reset(); + return false; + } + + public IEnumerable> GetCurrentBatch() where T : BaseEntity + { + if (_currentBatch == null) + { + int start = _pageable.FirstItemIndex - 1; + int end = _pageable.LastItemIndex - 1; + + _currentBatch = new ImportRow[(end - start) + 1]; + + // Determine values per row + int i = 0; + for (int r = start; r <= end; r++) + { + _currentBatch[i] = new ImportRow(this, _table.Rows[r], r); + i++; + } + } + + return _currentBatch.Cast>(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs new file mode 100644 index 0000000000..70170c0fd3 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -0,0 +1,227 @@ +using System.Collections.Generic; +using System.Threading; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Logging; + +namespace SmartStore.Services.DataExchange.Import +{ + public class ImportExecuteContext + { + private DataExchangeAbortion _abortion; + private ProgressValueSetter _progressValueSetter; + private string _progressInfo; + + private IDataTable _dataTable; + private ImportDataSegmenter _segmenter; + + public ImportExecuteContext( + CancellationToken cancellation, + ProgressValueSetter progressValueSetter, + string progressInfo) + { + _progressValueSetter = progressValueSetter; + _progressInfo = progressInfo; + + CancellationToken = cancellation; + CustomProperties = new Dictionary(); + Result = new ImportResult(); + } + + /// + /// Import settings + /// + public DataExchangeSettings DataExchangeSettings + { + get; + internal set; + } + + /// + /// The data source (CSV, Excel etc.) + /// + public IDataTable DataTable + { + get + { + return _dataTable; + } + internal set + { + _dataTable = value; + _segmenter = null; + } + } + + /// + /// Mapping information between database and data source + /// + public ColumnMap ColumnMap + { + get; + internal set; + } + + /// + /// Whether to only update existing records + /// + public bool UpdateOnly + { + get; + internal set; + } + + /// + /// Name of key fields to identify existing records for updating + /// + public string[] KeyFieldNames + { + get; + internal set; + } + + /// + /// All active languages + /// + public IList Languages + { + get; + internal set; + } + + /// + /// To log information into the import log file + /// + public ILogger Log + { + get; + internal set; + } + + /// + /// Common Services + /// + public ICommonServices Services + { + get; + internal set; + } + + /// + /// Cancellation token + /// + public CancellationToken CancellationToken + { + get; + private set; + } + + /// + /// The import folder + /// + public string ImportFolder + { + get; + internal set; + } + + /// + /// Use this dictionary for any custom data required along the import + /// + public Dictionary CustomProperties + { + get; + set; + } + + /// + /// Result of the import + /// + public ImportResult Result + { + get; + set; + } + + /// + /// Extra import configuration data + /// + public ImportExtraData ExtraData + { + get; + internal set; + } + + /// + /// Indicates whether and how to abort the import + /// + public DataExchangeAbortion Abort + { + get + { + if (CancellationToken.IsCancellationRequested || IsMaxFailures) + return DataExchangeAbortion.Hard; + + return _abortion; + } + set + { + _abortion = value; + } + } + + public bool IsMaxFailures + { + get + { + return Result.Errors > 11; + } + } + + public ImportDataSegmenter DataSegmenter + { + get + { + if (_segmenter == null) + { + if (this.DataTable == null || this.ColumnMap == null) + { + throw new SmartException("A DataTable and a ColumnMap must be specified before accessing the DataSegmenter property."); + } + _segmenter = new ImportDataSegmenter(DataTable, ColumnMap); + } + + return _segmenter; + } + } + + /// + /// Allows to set a progress message + /// + /// Progress value + /// /// Progress maximum + public void SetProgress(int value, int maximum) + { + try + { + if (_progressValueSetter != null) + _progressValueSetter.Invoke(value, maximum, _progressInfo.FormatInvariant(value, maximum)); + } + catch { } + } + + /// + /// Allows to set a message + /// + /// Message to display + public void SetProgress(string message) + { + try + { + if (_progressValueSetter != null && 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 new file mode 100644 index 0000000000..89f576e47a --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtensions.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SmartStore.Core.Domain; +using SmartStore.Utilities; + +namespace SmartStore.Services.DataExchange.Import +{ + 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) + { + var path = CommonHelper.MapPath(string.Concat("~/App_Data/ImportProfiles/", profile.FolderName, content ? "/Content" : "")); + + 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); + + if (System.IO.Directory.Exists(importFolder)) + { + return System.IO.Directory.EnumerateFiles(importFolder, "*", SearchOption.TopDirectoryOnly) + .OrderBy(x => x) + .ToList(); + } + + return new List(); + } + + /// + /// 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/ImportExtraData.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtraData.cs new file mode 100644 index 0000000000..ecbe710677 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtraData.cs @@ -0,0 +1,13 @@ +using System; + +namespace SmartStore.Services.DataExchange.Import +{ + [Serializable] + public class ImportExtraData + { + /// + /// Number of images per object to be imported + /// + public int? NumberOfPictures { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Data/Impex/ImportMessage.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportMessage.cs similarity index 64% rename from src/Libraries/SmartStore.Core/Data/Impex/ImportMessage.cs rename to src/Libraries/SmartStore.Services/DataExchange/Import/ImportMessage.cs index e1658cd057..1061e690e7 100644 --- a/src/Libraries/SmartStore.Core/Data/Impex/ImportMessage.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportMessage.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; -namespace SmartStore.Core.Data +namespace SmartStore.Services.DataExchange.Import { public class ImportMessage @@ -39,9 +37,28 @@ public string Message set; } + public string FullMessage + { + get; + set; + } + public override string ToString() { - return "{0} - {1}".FormatCurrent(MessageType.ToString().ToUpper(), Message.EmptyNull()); + var result = Message.NaIfEmpty(); + + string appendix = null; + + if (AffectedItem != null) + appendix = appendix.Grow("Pos: " + (AffectedItem.Position + 1).ToString(), ", "); + + if (AffectedField.HasValue()) + appendix = appendix.Grow("Field: " + AffectedField, ", "); + + if (appendix.HasValue()) + result = "{0} [{1}]".FormatInvariant(result, appendix); + + return result; } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs new file mode 100644 index 0000000000..c1e54633dc --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs @@ -0,0 +1,411 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity.Core.Metadata.Edm; +using System.Data.Entity.Infrastructure; +using System.Diagnostics; +using System.IO; +using System.Linq; +using SmartStore.Core.Data; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Tasks; +using SmartStore.Core.Events; +using SmartStore.Core.Localization; +using SmartStore.Services.Catalog.Importer; +using SmartStore.Services.Customers.Importer; +using SmartStore.Services.Localization; +using SmartStore.Services.Messages.Importer; +using SmartStore.Services.Tasks; +using SmartStore.Utilities; + +namespace SmartStore.Services.DataExchange.Import +{ + public partial class ImportProfileService : IImportProfileService + { + private static object _lock = new object(); + private static Dictionary> _entityProperties = null; + + private readonly IRepository _importProfileRepository; + private readonly IEventPublisher _eventPublisher; + private readonly IScheduleTaskService _scheduleTaskService; + private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; + private readonly DataExchangeSettings _dataExchangeSettings; + + public ImportProfileService( + IRepository importProfileRepository, + IEventPublisher eventPublisher, + IScheduleTaskService scheduleTaskService, + ILocalizationService localizationService, + ILanguageService languageService, + DataExchangeSettings dataExchangeSettings) + { + _importProfileRepository = importProfileRepository; + _eventPublisher = eventPublisher; + _scheduleTaskService = scheduleTaskService; + _localizationService = localizationService; + _languageService = languageService; + _dataExchangeSettings = dataExchangeSettings; + } + + private string GetLocalizedPropertyName(ImportEntityType type, string property) + { + if (property.IsEmpty()) + return ""; + + string key = null; + string prefixKey = null; + + if (property.StartsWith("BillingAddress.")) + prefixKey = "Admin.Orders.Fields.BillingAddress"; + else if (property.StartsWith("ShippingAddress.")) + prefixKey = "Admin.Orders.Fields.ShippingAddress"; + + #region Get resource key + + switch (property) + { + case "Id": + key = "Admin.Common.Entity.Fields.Id"; + break; + case "LimitedToStores": + key = "Admin.Common.Store.LimitedTo"; + break; + case "DisplayOrder": + key = "Common.DisplayOrder"; + break; + case "Deleted": + key = "Admin.Common.Deleted"; + break; + case "CreatedOnUtc": + case "BillingAddress.CreatedOnUtc": + case "ShippingAddress.CreatedOnUtc": + key = "Common.CreatedOn"; + break; + case "UpdatedOnUtc": + key = "Common.UpdatedOn"; + break; + case "HasDiscountsApplied": + key = "Admin.Catalog.Products.Fields.HasDiscountsApplied"; + break; + case "DefaultViewMode": + key = "Admin.Configuration.Settings.Catalog.DefaultViewMode"; + break; + case "StoreId": + key = "Admin.Common.Store"; + break; + case "ParentGroupedProductId": + key = "Admin.Catalog.Products.Fields.AssociatedToProductName"; + break; + case "PasswordFormatId": + key = "Admin.Configuration.Settings.CustomerUser.DefaultPasswordFormat"; + break; + case "LastIpAddress": + key = "Admin.Customers.Customers.Fields.IPAddress"; + break; + default: + switch (type) + { + case ImportEntityType.Product: + key = "Admin.Catalog.Products.Fields." + property; + break; + case ImportEntityType.Category: + key = "Admin.Catalog.Categories.Fields." + property; + break; + case ImportEntityType.Customer: + if (property.StartsWith("BillingAddress.") || property.StartsWith("ShippingAddress.")) + key = "Admin.Address.Fields." + property.Substring(property.IndexOf('.') + 1); + else + key = "Admin.Customers.Customers.Fields." + property; + break; + case ImportEntityType.NewsLetterSubscription: + key = "Admin.Promotions.NewsLetterSubscriptions.Fields." + property; + break; + } + break; + } + + #endregion + + if (key.IsEmpty()) + return ""; + + var result = _localizationService.GetResource(key, 0, false, "", true); + + if (result.IsEmpty()) + { + if (key.EndsWith("Id")) + result = _localizationService.GetResource(key.Substring(0, key.Length - 2), 0, false, "", true); + else if (key.EndsWith("Utc")) + result = _localizationService.GetResource(key.Substring(0, key.Length - 3), 0, false, "", true); + } + + if (result.IsEmpty()) + { + Debug.WriteLine("Missing string resource mapping for {0} - {1}".FormatInvariant(type.ToString(), property)); + result = property.SplitPascalCase(); + } + + if (prefixKey.HasValue()) + { + result = string.Concat(_localizationService.GetResource(prefixKey, 0, false, "", true), " - ", result); + } + + return result; + } + + public string GetNewProfileName(ImportEntityType entityType) + { + var defaultNames = _localizationService.GetResource("Admin.DataExchange.Import.DefaultProfileNames").SplitSafe(";"); + + var result = defaultNames.SafeGet((int)entityType); + + if (result.IsEmpty()) + result = entityType.ToString(); + + var profileCount = _importProfileRepository.Table.Count(x => x.EntityTypeId == (int)entityType); + + result = string.Concat(result, " ", profileCount + 1); + + return result; + } + + public virtual ImportProfile InsertImportProfile(string fileName, string name, ImportEntityType entityType) + { + Guard.ArgumentNotEmpty(() => fileName); + + if (name.IsEmpty()) + name = GetNewProfileName(entityType); + + var task = new ScheduleTask + { + CronExpression = "0 */24 * * *", + Type = typeof(DataImportTask).AssemblyQualifiedNameWithoutVersion(), + Enabled = false, + StopOnError = false, + IsHidden = true + }; + + task.Name = string.Concat(name, " Task"); + + _scheduleTaskService.InsertTask(task); + + var profile = new ImportProfile + { + Name = name, + EntityType = entityType, + Enabled = true, + SchedulingTaskId = task.Id + }; + + if (Path.GetExtension(fileName).IsCaseInsensitiveEqual(".xlsx")) + profile.FileType = ImportFileType.XLSX; + else + profile.FileType = ImportFileType.CSV; + + string[] keyFieldNames = null; + + switch (entityType) + { + case ImportEntityType.Product: + keyFieldNames = ProductImporter.DefaultKeyFields; + break; + case ImportEntityType.Category: + keyFieldNames = CategoryImporter.DefaultKeyFields; + break; + case ImportEntityType.Customer: + keyFieldNames = CustomerImporter.DefaultKeyFields; + break; + case ImportEntityType.NewsLetterSubscription: + keyFieldNames = NewsLetterSubscriptionImporter.DefaultKeyFields; + break; + } + + profile.KeyFieldNames = string.Join(",", keyFieldNames); + + profile.FolderName = SeoHelper.GetSeName(name, true, false) + .ToValidPath() + .Truncate(_dataExchangeSettings.MaxFileNameLength); + + profile.FolderName = FileSystemHelper.CreateNonExistingDirectoryName(CommonHelper.MapPath("~/App_Data/ImportProfiles"), profile.FolderName); + + _importProfileRepository.Insert(profile); + + task.Alias = profile.Id.ToString(); + _scheduleTaskService.UpdateTask(task); + + _eventPublisher.EntityInserted(profile); + + return profile; + } + + public virtual void UpdateImportProfile(ImportProfile profile) + { + if (profile == null) + throw new ArgumentNullException("profile"); + + _importProfileRepository.Update(profile); + + _eventPublisher.EntityUpdated(profile); + } + + public virtual void DeleteImportProfile(ImportProfile profile) + { + if (profile == null) + throw new ArgumentNullException("profile"); + + var scheduleTaskId = profile.SchedulingTaskId; + var folder = profile.GetImportFolder(); + + _importProfileRepository.Delete(profile); + + var scheduleTask = _scheduleTaskService.GetTaskById(scheduleTaskId); + _scheduleTaskService.DeleteTask(scheduleTask); + + _eventPublisher.EntityDeleted(profile); + + if (System.IO.Directory.Exists(folder)) + { + FileSystemHelper.ClearDirectory(folder, true); + } + } + + public virtual IQueryable GetImportProfiles(bool? enabled = null) + { + var query = _importProfileRepository.Table + .Expand(x => x.ScheduleTask); + + if (enabled.HasValue) + { + query = query.Where(x => x.Enabled == enabled.Value); + } + + query = query + .OrderBy(x => x.EntityTypeId) + .ThenBy(x => x.Name); + + return query; + } + + public virtual ImportProfile GetImportProfileById(int id) + { + if (id == 0) + return null; + + var profile = _importProfileRepository.Table + .Expand(x => x.ScheduleTask) + .FirstOrDefault(x => x.Id == id); + + return profile; + } + + public virtual ImportProfile GetImportProfileByName(string name) + { + if (name.IsEmpty()) + return null; + + var profile = _importProfileRepository.Table + .Expand(x => x.ScheduleTask) + .FirstOrDefault(x => x.Name == name); + + return profile; + } + + public virtual Dictionary GetImportableEntityProperties(ImportEntityType entityType) + { + if (_entityProperties == null) + { + lock (_lock) + { + if (_entityProperties == null) + { + _entityProperties = new Dictionary>(); + + var context = ((IObjectContextAdapter)_importProfileRepository.Context).ObjectContext; + var container = context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace); + + var allLanguages = _languageService.GetAllLanguages(true); + var allLanguageNames = allLanguages.ToDictionarySafe(x => x.UniqueSeoCode, x => LocalizationHelper.GetLanguageNativeName(x.LanguageCulture) ?? x.Name); + + var localizableProperties = new Dictionary + { + { ImportEntityType.Product, new string[] { "Name", "ShortDescription", "FullDescription", "MetaKeywords", "MetaDescription", "MetaTitle", "SeName" } }, + { ImportEntityType.Category, new string[] { "Name", "FullName", "Description", "BottomDescription", "MetaKeywords", "MetaDescription", "MetaTitle", "SeName" } }, + { ImportEntityType.Customer, new string[] { } }, + { ImportEntityType.NewsLetterSubscription, new string[] { } } + }; + + var addressSet = container.GetEntitySetByName("Addresses", true); + + var addressProperties = addressSet.ElementType.Members + .Where(x => !x.Name.IsCaseInsensitiveEqual("Id") && x.BuiltInTypeKind.HasFlag(BuiltInTypeKind.EdmProperty)) + .Select(x => x.Name) + .ToList(); + + + foreach (ImportEntityType type in Enum.GetValues(typeof(ImportEntityType))) + { + EntitySet entitySet = null; + + try + { + if (type == ImportEntityType.Category) + entitySet = container.GetEntitySetByName("Categories", true); + else + entitySet = container.GetEntitySetByName(type.ToString() + "s", true); + } + catch (Exception) + { + throw new SmartException("There is no entity set for ImportEntityType {0}. Note, the enum value must equal the entity name.".FormatInvariant(type.ToString())); + } + + var dic = entitySet.ElementType.Members + .Where(x => !x.Name.IsCaseInsensitiveEqual("Id") && x.BuiltInTypeKind.HasFlag(BuiltInTypeKind.EdmProperty)) + .Select(x => x.Name) + .ToDictionary(x => x, x => "", StringComparer.OrdinalIgnoreCase); + + // lack of abstractness? + if ((type == ImportEntityType.Product || type == ImportEntityType.Category) && !dic.ContainsKey("SeName")) + { + dic.Add("SeName", ""); + } + + // shipping and billing address + if (type == ImportEntityType.Customer) + { + foreach (var property in addressProperties) + { + dic.Add("BillingAddress." + property, ""); + dic.Add("ShippingAddress." + property, ""); + } + } + + // add localized property names + foreach (var key in dic.Keys.ToList()) + { + var localizedValue = GetLocalizedPropertyName(type, key); + + dic[key] = localizedValue.NaIfEmpty(); + + if (localizableProperties[type].Contains(key)) + { + foreach (var language in allLanguages) + { + dic.Add( + "{0}[{1}]".FormatInvariant(key, language.UniqueSeoCode.EmptyNull().ToLower()), + "{0} {1}".FormatInvariant(localizedValue.NaIfEmpty(), allLanguageNames[language.UniqueSeoCode]) + ); + } + } + } + + _entityProperties.Add(type, dic); + } + } + } + } + + return (_entityProperties.ContainsKey(entityType) ? _entityProperties[entityType] : null); + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportResult.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportResult.cs new file mode 100644 index 0000000000..a8a29366f7 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportResult.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Serialization; + +namespace SmartStore.Services.DataExchange.Import +{ + public class ImportResult : ICloneable + { + public ImportResult() + { + this.Messages = new List(); + Clear(); + } + + public DateTime StartDateUtc + { + get; + set; + } + + public DateTime EndDateUtc + { + get; + set; + } + + public int TotalRecords + { + get; + set; + } + + public int SkippedRecords + { + get; + set; + } + + public int NewRecords + { + get; + set; + } + + public int ModifiedRecords + { + get; + set; + } + + public int AffectedRecords + { + get { return NewRecords + ModifiedRecords; } + } + + public bool Cancelled + { + get; + set; + } + + public void Clear() + { + Messages.Clear(); + StartDateUtc = EndDateUtc = DateTime.UtcNow; + TotalRecords = 0; + SkippedRecords = 0; + NewRecords = 0; + ModifiedRecords = 0; + Cancelled = false; + } + + public ImportMessage AddInfo(string message, ImportRowInfo affectedRow = null, string affectedField = null) + { + return this.AddMessage(message, ImportMessageType.Info, affectedRow, affectedField); + } + + public ImportMessage AddWarning(string message, ImportRowInfo affectedRow = null, string affectedField = null) + { + return this.AddMessage(message, ImportMessageType.Warning, affectedRow, affectedField); + } + + public ImportMessage AddError(string message, ImportRowInfo affectedRow = null, string affectedField = null) + { + return this.AddMessage(message, ImportMessageType.Error, affectedRow, affectedField); + } + + public ImportMessage AddError(Exception exception, int? affectedBatch = null, string stage = null) + { + var prefix = new List(); + if (affectedBatch.HasValue) + { + prefix.Add("Batch: " + affectedBatch.Value); + } + if (stage.HasValue()) + { + prefix.Add("Stage: " + stage); + } + + string msg = string.Empty; + if (prefix.Any()) + { + msg = "[{0}] ".FormatCurrent(String.Join(", ", prefix)); + } + + msg += exception.ToAllMessages(); + + return this.AddMessage(msg, ImportMessageType.Error, fullMessage: exception.StackTrace); + } + + public ImportMessage AddError(Exception exception, string message) + { + return AddMessage( + message ?? exception.ToAllMessages(), + ImportMessageType.Error, + null, + null, + exception.StackTrace); + } + + public ImportMessage AddMessage(string message, ImportMessageType severity, ImportRowInfo affectedRow = null, string affectedField = null, string fullMessage = null) + { + var msg = new ImportMessage(message, severity); + + msg.AffectedItem = affectedRow; + msg.AffectedField = affectedField; + msg.FullMessage = fullMessage; + + this.Messages.Add(msg); + return msg; + } + + public IList Messages + { + get; + private set; + } + + public bool HasWarnings + { + get { return this.Messages.Any(x => x.MessageType == ImportMessageType.Warning); } + } + + public int Warnings + { + get { return Messages.Count(x => x.MessageType == ImportMessageType.Warning); } + } + + public bool HasErrors + { + get { return this.Messages.Any(x => x.MessageType == ImportMessageType.Error); } + } + + public int Errors + { + get { return Messages.Count(x => x.MessageType == ImportMessageType.Error); } + } + + public string LastError + { + get + { + var lastError = Messages.LastOrDefault(x => x.MessageType == ImportMessageType.Error); + if (lastError != null) + return lastError.Message; + + return null; + } + } + + object ICloneable.Clone() + { + return this.Clone(); + } + + 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; + } + } + + + [Serializable] + public partial class SerializableImportResult + { + public DateTime StartDateUtc { get; set; } + public DateTime EndDateUtc { get; set; } + public int TotalRecords { get; set; } + public int SkippedRecords { get; set; } + public int NewRecords { get; set; } + public int ModifiedRecords { get; set; } + public int AffectedRecords { get; set; } + public bool Cancelled { get; set; } + public int Warnings { get; set; } + public int Errors { get; set; } + public string LastError { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs new file mode 100644 index 0000000000..1f213ee9e4 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -0,0 +1,331 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using SmartStore.ComponentModel; +using SmartStore.Core; + +namespace SmartStore.Services.DataExchange.Import +{ + public class ImportRow where T : BaseEntity + { + private const string ExplicitNull = "[NULL]"; + + private bool _initialized = false; + private T _entity; + private string _entityDisplayName; + private readonly int _position; + private bool _isNew; + private bool _isDirty; + private ImportRowInfo _rowInfo; + + private readonly ImportDataSegmenter _segmenter; + private readonly IDataRow _row; + + public ImportRow(ImportDataSegmenter parent, IDataRow row, int position) + { + _segmenter = parent; + _row = row; + _position = position; + } + + public void Initialize(T entity, string entityDisplayName) + { + _entity = entity; + _entityDisplayName = entityDisplayName; + _isNew = _entity.Id == 0; + + _initialized = true; + } + + private void CheckInitialized() + { + if (_initialized) + { + throw Error.InvalidOperation("A row must be initialized before interacting with the entity or the data store"); + } + } + + private TProp GetDefaultValue(ColumnMappingItem mapping, TProp defaultValue, ImportResult result = null) + { + if (mapping != null && mapping.Default.HasValue()) + { + try + { + return mapping.Default.Convert(_segmenter.Culture); + } + catch (Exception exception) + { + if (result != null) + { + var msg = "Failed to convert default value '{0}'. Please specify a convertable default value. Column: {1}"; + result.AddWarning(msg.FormatInvariant(mapping.Default, exception.Message), this.GetRowInfo(), mapping.SoureName); + } + } + } + + return defaultValue; + } + + public bool IsTransient + { + get { return _entity.Id == 0; } + } + + public bool IsNew + { + get { return _isNew; } + } + + public bool IsDirty + { + get { return _isDirty; } + } + + public ImportDataSegmenter Segmenter + { + get { return _segmenter; } + } + + public T Entity + { + get { return _entity; } + } + + public IDataRow DataRow + { + get { return _row; } + } + + public string EntityDisplayName + { + get { return _entityDisplayName; } + } + + public bool NameChanged + { + get; + set; + } + + public int Position + { + get { return _position; } + } + + /// + /// Determines whether a specific column exists in the underlying data table + /// and contains a non-null, convertible value. + /// + /// The name of the column + /// + /// If true and a column with the passed does not exist, + /// this method seeks for any indexed column with the same name. + /// + /// true if the column exists and contains a value, false otherwise + /// + /// This method takes mapped column names into account. + /// + public bool HasDataValue(string columnName, bool withAnyIndex = false) + { + var result = HasDataValue(columnName, null); + + if (!result && withAnyIndex) + { + // Column does not have a value, but withAnyIndex is true: + // Test for values in any indexed column. + var indexes = _segmenter.GetColumnIndexes(columnName); + foreach (var idx in indexes) + { + result = HasDataValue(columnName, idx); + if (result) + break; + } + } + + return result; + } + + /// + /// Determines whether the column name[index] exists in the underlying data table + /// and contains a non-null, convertible value. + /// + /// The name of the column + /// The index of the column + /// true if the column exists and contains a value, false otherwise + /// + /// This method takes mapped column names into account. + /// + public bool HasDataValue(string columnName, string index) + { + var mapping = _segmenter.ColumnMap.GetMapping(columnName, index); + + object value; + return (_row.TryGetValue(mapping.MappedName, out value) && value != null && value != DBNull.Value); + } + + public TProp GetDataValue(string columnName, bool force = false) + { + TProp value; + TryGetDataValue(columnName, null, out value, force); + return value; + } + + public TProp GetDataValue(string columnName, string index, bool force = false) + { + TProp value; + TryGetDataValue(columnName, index, out value, force); + return value; + } + + public bool TryGetDataValue(string columnName, out TProp value, bool force = false) + { + return TryGetDataValue(columnName, null, out value, force); + } + + public bool TryGetDataValue(string columnName, string index, out TProp value, bool force = false) + { + var mapping = _segmenter.ColumnMap.GetMapping(columnName, index); + + if (!force && mapping.IgnoreProperty) + { + value = default(TProp); + return false; + } + + object rawValue; + if (_row.TryGetValue(mapping.MappedName, out rawValue) && rawValue != null && rawValue != DBNull.Value) + { + value = rawValue.ToString().IsCaseInsensitiveEqual(ExplicitNull) + ? default(TProp) + : rawValue.Convert(_segmenter.Culture); + return true; + } + + if (IsNew) + { + // only transient/new entities should fallback to possible defaults. + value = GetDefaultValue(mapping, default(TProp)); + return true; + } + + value = default(TProp); + return false; + } + + public bool SetProperty( + ImportResult result, + Expression> prop, + TProp defaultValue = default(TProp), + Func converter = null) + { + return SetProperty( + result, + null, // columnName + prop, + defaultValue, + converter); + } + + public bool SetProperty( + ImportResult result, + string columnName, + Expression> prop, + TProp defaultValue = default(TProp), + Func converter = null) + { + // TBD: (MC) do not check or validate for perf reason? + //CheckInitialized(); + + var isPropertySet = false; + var pi = prop.ExtractPropertyInfo(); + var propName = pi.Name; + var target = _entity; + + columnName = columnName ?? propName; + + try + { + object value; + var mapping = _segmenter.ColumnMap.GetMapping(columnName); + + if (mapping.IgnoreProperty) + { + // explicitly ignore this property + } + else if (_row.TryGetValue(mapping.MappedName, out value) && (value != null && value != DBNull.Value)) + { + // source contains field value. Set it. + TProp converted; + if (converter != null) + { + converted = converter(value, _segmenter.Culture); + } + else if (value.ToString().IsCaseInsensitiveEqual(ExplicitNull)) + { + // prop is "explicitly" set to null. Don't fallback to any default! + converted = default(TProp); + } + else + { + converted = value.Convert(_segmenter.Culture); + } + + var fastProp = FastProperty.GetProperty(target.GetUnproxiedType(), propName, PropertyCachingStrategy.EagerCached); + fastProp.SetValue(target, converted); + isPropertySet = true; + } + else + { + // source field value does not exist or is null/empty + if (IsNew) + { + // if entity is new and source field value is null, determine default value in this particular order: + // 2.) Default value in field mapping table + // 3.) passed default value argument + defaultValue = GetDefaultValue(mapping, defaultValue, result); + + // source does not contain field data or is empty... + if (defaultValue != null) + { + // ...but the entity is new. In this case set the default value if given. + var fastProp = FastProperty.GetProperty(target.GetUnproxiedType(), propName, PropertyCachingStrategy.EagerCached); + fastProp.SetValue(target, defaultValue); + isPropertySet = true; + } + } + } + } + catch (Exception exception) + { + result.AddWarning("Conversion failed: " + exception.Message, this.GetRowInfo(), propName); + } + + if (isPropertySet && !_isDirty) + { + _isDirty = true; + } + + return isPropertySet; + } + + public ImportRowInfo GetRowInfo() + { + if (_rowInfo == null) + { + _rowInfo = new ImportRowInfo(this.Position, this.EntityDisplayName); + } + + return _rowInfo; + } + + public override string ToString() + { + var str = "Pos: {0} - Name: {1}, IsNew: {2}, IsTransient: {3}".FormatCurrent( + Position, + EntityDisplayName.EmptyNull(), + _initialized ? IsNew.ToString() : "-", + _initialized ? IsTransient.ToString() : "-"); + return str; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/Internal/DataImporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/Internal/DataImporterContext.cs new file mode 100644 index 0000000000..41efff1883 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/Internal/DataImporterContext.cs @@ -0,0 +1,28 @@ +using System.Threading; +using SmartStore.Core.Logging; + +namespace SmartStore.Services.DataExchange.Import.Internal +{ + internal class DataImporterContext + { + public DataImporterContext( + DataImportRequest request, + CancellationToken cancellationToken, + string progressInfo) + { + Request = request; + CancellationToken = cancellationToken; + + ExecuteContext = new ImportExecuteContext(CancellationToken, Request.ProgressValueSetter, progressInfo); + } + + 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; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/SyncMappingService.cs b/src/Libraries/SmartStore.Services/DataExchange/SyncMappingService.cs new file mode 100644 index 0000000000..3c3a11551c --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/SyncMappingService.cs @@ -0,0 +1,142 @@ +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; + + public SyncMappingService(IRepository syncMappingsRepository) + { + this._syncMappingsRepository = syncMappingsRepository; + } + + public void InsertSyncMapping(SyncMapping mapping) + { + Guard.ArgumentNotNull(() => mapping); + + _syncMappingsRepository.Insert(mapping); + } + + public void InsertSyncMappings(IEnumerable mappings) + { + Guard.ArgumentNotNull(() => mappings); + + _syncMappingsRepository.InsertRange(mappings); + } + + public IList GetAllSyncMappings(string contextName = null, string entityName = null) + { + var query = _syncMappingsRepository.Table; + + if (entityName.HasValue()) + { + query = query.Where(x => x.EntityName == entityName); + } + + if (contextName.HasValue()) + { + query = query.Where(x => x.ContextName == contextName); + } + + return query.ToList(); + } + + public SyncMapping GetSyncMappingByEntity(int entityId, string entityName, string contextName) + { + Guard.ArgumentIsPositive(entityId, "entityId"); + Guard.ArgumentNotEmpty(() => entityName); + Guard.ArgumentNotEmpty(() => contextName); + + var query = from x in _syncMappingsRepository.Table + where + x.EntityId == entityId + && x.EntityName == entityName + && x.ContextName == contextName + select x; + + return query.FirstOrDefault(); + } + + public SyncMapping GetSyncMappingBySource(string sourceKey, string entityName, string contextName) + { + Guard.ArgumentNotEmpty(() => sourceKey); + Guard.ArgumentNotEmpty(() => entityName); + Guard.ArgumentNotEmpty(() => contextName); + + var query = from x in _syncMappingsRepository.Table + where + x.SourceKey == sourceKey + && x.EntityName == entityName + && x.ContextName == contextName + select x; + + return query.FirstOrDefault(); + } + + public void DeleteSyncMapping(SyncMapping mapping) + { + Guard.ArgumentNotNull(() => mapping); + + _syncMappingsRepository.Delete(mapping); + } + + public void DeleteSyncMappings(IEnumerable mappings) + { + Guard.ArgumentNotNull(() => mappings); + + _syncMappingsRepository.DeleteRange(mappings); + } + + public void DeleteSyncMappingsFor(T entity) where T : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + + if (entity is SyncMapping) + { + throw Error.InvalidOperation("Cannot delete a sync mapping record for a SyncMapping entity"); + } + + if (entity.IsTransientRecord()) + { + return; + } + + _syncMappingsRepository.DeleteAll(x => x.EntityId == entity.Id && x.EntityName == typeof(T).Name); + } + + public void DeleteSyncMappings(string contextName, string entityName = null) + { + Guard.ArgumentNotEmpty(() => contextName); + + if (entityName.HasValue()) + { + _syncMappingsRepository.DeleteAll(x => x.ContextName == contextName && x.EntityName == entityName); + } + else + { + _syncMappingsRepository.DeleteAll(x => x.ContextName == contextName); + } + } + + + public void UpdateSyncMapping(SyncMapping mapping) + { + Guard.ArgumentNotNull(() => 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 aeda79b995..40b2e16a8b 100644 --- a/src/Libraries/SmartStore.Services/Directory/CountryService.cs +++ b/src/Libraries/SmartStore.Services/Directory/CountryService.cs @@ -1,9 +1,11 @@ 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 @@ -25,26 +27,28 @@ public partial class CountryService : ICountryService private readonly IRepository _countryRepository; private readonly IEventPublisher _eventPublisher; private readonly ICacheManager _cacheManager; + private readonly IStoreContext _storeContext; + private readonly IRepository _storeMappingRepository; #endregion #region Ctor - /// - /// Ctor - /// - /// Cache manager - /// Country repository - /// Event published public CountryService(ICacheManager cacheManager, IRepository countryRepository, - IEventPublisher eventPublisher) + IEventPublisher eventPublisher, + IStoreContext storeContext, + IRepository storeMappingRepository) { _cacheManager = cacheManager; _countryRepository = countryRepository; _eventPublisher = eventPublisher; + _storeContext = storeContext; + _storeMappingRepository = storeMappingRepository; } + public DbQuerySettings QuerySettings { get; set; } + #endregion #region Methods @@ -76,10 +80,31 @@ public virtual IList GetAllCountries(bool showHidden = false) string key = string.Format(COUNTRIES_ALL_KEY, showHidden); return _cacheManager.Get(key, () => { - var query = from c in _countryRepository.Table - orderby c.DisplayOrder, c.Name - where showHidden || c.Published - select c; + var query = _countryRepository.Table; + + if (!showHidden) + query = query.Where(c => c.Published); + + query = query.OrderBy(c => c.DisplayOrder).ThenBy(c => c.Name); + + if (!showHidden && !QuerySettings.IgnoreMultiStore) + { + var currentStoreId = _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 + from sc in c_sm.DefaultIfEmpty() + where !c.LimitedToStores || currentStoreId == sc.StoreId + select c; + + query = from c in query + group c by c.Id into cGroup + orderby cGroup.Key + select cGroup.FirstOrDefault(); + + query = query.OrderBy(c => c.DisplayOrder).ThenBy(c => c.Name); + } + var countries = query.ToList(); return countries; }); @@ -95,11 +120,9 @@ public virtual IList GetAllCountriesForBilling(bool showHidden = false) string key = string.Format(COUNTRIES_BILLING_KEY, showHidden); return _cacheManager.Get(key, () => { - var query = from c in _countryRepository.Table - orderby c.DisplayOrder, c.Name - where (showHidden || c.Published) && c.AllowsBilling - select c; - var countries = query.ToList(); + var allCountries = GetAllCountries(showHidden); + + var countries = allCountries.Where(x => x.AllowsBilling).ToList(); return countries; }); } @@ -113,12 +136,10 @@ public virtual IList GetAllCountriesForShipping(bool showHidden = false { string key = string.Format(COUNTRIES_SHIPPING_KEY, showHidden); return _cacheManager.Get(key, () => - { - var query = from c in _countryRepository.Table - orderby c.DisplayOrder, c.Name - where (showHidden || c.Published) && c.AllowsShipping - select c; - var countries = query.ToList(); + { + var allCountries = GetAllCountries(showHidden); + + var countries = allCountries.Where(x => x.AllowsShipping).ToList(); return countries; }); } @@ -160,11 +181,14 @@ public virtual Country GetCountryByTwoOrThreeLetterIsoCode(string letterIsoCode) /// Country public virtual Country GetCountryByTwoLetterIsoCode(string twoLetterIsoCode) { + if (twoLetterIsoCode.IsEmpty()) + return null; + var query = from c in _countryRepository.Table where c.TwoLetterIsoCode == twoLetterIsoCode select c; - var country = query.FirstOrDefault(); + var country = query.FirstOrDefault(); return country; } @@ -175,9 +199,13 @@ public virtual Country GetCountryByTwoLetterIsoCode(string twoLetterIsoCode) /// Country public virtual Country GetCountryByThreeLetterIsoCode(string threeLetterIsoCode) { + if (threeLetterIsoCode.IsEmpty()) + return null; + var query = from c in _countryRepository.Table where c.ThreeLetterIsoCode == threeLetterIsoCode select c; + var country = query.FirstOrDefault(); return country; } diff --git a/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs b/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs index 6b436fd14d..5dd60700e7 100644 --- a/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs +++ b/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs @@ -8,7 +8,6 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; using SmartStore.Core.Plugins; -using SmartStore.Services.Customers; using SmartStore.Services.Stores; namespace SmartStore.Services.Directory @@ -33,6 +32,7 @@ public partial class CurrencyService : ICurrencyService private readonly IPluginFinder _pluginFinder; private readonly IEventPublisher _eventPublisher; private readonly IProviderManager _providerManager; + private readonly IStoreContext _storeContext; #endregion @@ -53,7 +53,8 @@ public CurrencyService(ICacheManager cacheManager, CurrencySettings currencySettings, IPluginFinder pluginFinder, IEventPublisher eventPublisher, - IProviderManager providerManager) + IProviderManager providerManager, + IStoreContext storeContext) { this._cacheManager = cacheManager; this._currencyRepository = currencyRepository; @@ -62,6 +63,7 @@ public CurrencyService(ICacheManager cacheManager, this._pluginFinder = pluginFinder; this._eventPublisher = eventPublisher; this._providerManager = providerManager; + this._storeContext = storeContext; } #endregion @@ -189,7 +191,6 @@ public virtual void UpdateCurrency(Currency currency) } - /// /// Converts currency /// @@ -209,16 +210,17 @@ public virtual decimal ConvertCurrency(decimal amount, decimal exchangeRate) /// 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) + public virtual decimal ConvertCurrency(decimal amount, Currency sourceCurrencyCode, Currency targetCurrencyCode, Store store = null) { decimal result = amount; if (sourceCurrencyCode.Id == targetCurrencyCode.Id) return result; if (result != decimal.Zero && sourceCurrencyCode.Id != targetCurrencyCode.Id) { - result = ConvertToPrimaryExchangeRateCurrency(result, sourceCurrencyCode); - result = ConvertFromPrimaryExchangeRateCurrency(result, targetCurrencyCode); + result = ConvertToPrimaryExchangeRateCurrency(result, sourceCurrencyCode, store); + result = ConvertFromPrimaryExchangeRateCurrency(result, targetCurrencyCode, store); } return result; } @@ -228,11 +230,13 @@ public virtual decimal ConvertCurrency(decimal amount, Currency sourceCurrencyCo /// /// Amount /// Source currency code + /// Store to get the primary exchange rate currency from /// Converted value - public virtual decimal ConvertToPrimaryExchangeRateCurrency(decimal amount, Currency sourceCurrencyCode) + public virtual decimal ConvertToPrimaryExchangeRateCurrency(decimal amount, Currency sourceCurrencyCode, Store store = null) { decimal result = amount; - var primaryExchangeRateCurrency = GetCurrencyById(_currencySettings.PrimaryExchangeRateCurrencyId); + var primaryExchangeRateCurrency = (store == null ? _storeContext.CurrentStore.PrimaryExchangeRateCurrency : store.PrimaryExchangeRateCurrency); + if (result != decimal.Zero && sourceCurrencyCode.Id != primaryExchangeRateCurrency.Id) { decimal exchangeRate = sourceCurrencyCode.Rate; @@ -248,11 +252,13 @@ public virtual decimal ConvertToPrimaryExchangeRateCurrency(decimal amount, Curr /// /// Amount /// Target currency code + /// Store to get the primary exchange rate currency from /// Converted value - public virtual decimal ConvertFromPrimaryExchangeRateCurrency(decimal amount, Currency targetCurrencyCode) + public virtual decimal ConvertFromPrimaryExchangeRateCurrency(decimal amount, Currency targetCurrencyCode, Store store = null) { decimal result = amount; - var primaryExchangeRateCurrency = GetCurrencyById(_currencySettings.PrimaryExchangeRateCurrencyId); + var primaryExchangeRateCurrency = (store == null ? _storeContext.CurrentStore.PrimaryExchangeRateCurrency : store.PrimaryExchangeRateCurrency); + if (result != decimal.Zero && targetCurrencyCode.Id != primaryExchangeRateCurrency.Id) { decimal exchangeRate = targetCurrencyCode.Rate; @@ -268,11 +274,13 @@ public virtual decimal ConvertFromPrimaryExchangeRateCurrency(decimal amount, Cu /// /// Amount /// Source currency code + /// Store to get the primary store currency from /// Converted value - public virtual decimal ConvertToPrimaryStoreCurrency(decimal amount, Currency sourceCurrencyCode) + public virtual decimal ConvertToPrimaryStoreCurrency(decimal amount, Currency sourceCurrencyCode, Store store = null) { decimal result = amount; - var primaryStoreCurrency = GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); + var primaryStoreCurrency = (store == null ? _storeContext.CurrentStore.PrimaryStoreCurrency : store.PrimaryStoreCurrency); + if (result != decimal.Zero && sourceCurrencyCode.Id != primaryStoreCurrency.Id) { decimal exchangeRate = sourceCurrencyCode.Rate; @@ -289,11 +297,11 @@ public virtual decimal ConvertToPrimaryStoreCurrency(decimal amount, Currency so /// Amount /// Target currency code /// Converted value - public virtual decimal ConvertFromPrimaryStoreCurrency(decimal amount, Currency targetCurrencyCode) + public virtual decimal ConvertFromPrimaryStoreCurrency(decimal amount, Currency targetCurrencyCode, Store store = null) { decimal result = amount; - var primaryStoreCurrency = GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); - result = ConvertCurrency(amount, primaryStoreCurrency, targetCurrencyCode); + var primaryStoreCurrency = (store == null ? _storeContext.CurrentStore.PrimaryStoreCurrency : store.PrimaryStoreCurrency); + result = ConvertCurrency(amount, primaryStoreCurrency, targetCurrencyCode, store); return result; } diff --git a/src/Libraries/SmartStore.Services/Directory/ICurrencyService.cs b/src/Libraries/SmartStore.Services/Directory/ICurrencyService.cs index b1481d03f3..65f633fed4 100644 --- a/src/Libraries/SmartStore.Services/Directory/ICurrencyService.cs +++ b/src/Libraries/SmartStore.Services/Directory/ICurrencyService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Plugins; namespace SmartStore.Services.Directory @@ -72,40 +73,45 @@ public partial interface ICurrencyService /// Amount /// Source currency code /// Target currency code + /// Store to get the primary currencies from /// Converted value - decimal ConvertCurrency(decimal amount, Currency sourceCurrencyCode, Currency targetCurrencyCode); + decimal ConvertCurrency(decimal amount, Currency sourceCurrencyCode, Currency targetCurrencyCode, Store store = null); /// /// Converts to primary exchange rate currency /// /// Amount /// Source currency code + /// Store to get the primary exchange rate currency from /// Converted value - decimal ConvertToPrimaryExchangeRateCurrency(decimal amount, Currency sourceCurrencyCode); + decimal ConvertToPrimaryExchangeRateCurrency(decimal amount, Currency sourceCurrencyCode, Store store = null); /// /// Converts from primary exchange rate currency /// /// Amount /// Target currency code + /// Store to get the primary exchange rate currency from /// Converted value - decimal ConvertFromPrimaryExchangeRateCurrency(decimal amount, Currency targetCurrencyCode); + decimal ConvertFromPrimaryExchangeRateCurrency(decimal amount, Currency targetCurrencyCode, Store store = null); /// /// Converts to primary store currency /// /// Amount /// Source currency code + /// Store to get the primary store currency from /// Converted value - decimal ConvertToPrimaryStoreCurrency(decimal amount, Currency sourceCurrencyCode); + decimal ConvertToPrimaryStoreCurrency(decimal amount, Currency sourceCurrencyCode, Store store = null); /// /// Converts from primary store currency /// /// Amount /// Target currency code + /// Store to get the primary store currency from /// Converted value - decimal ConvertFromPrimaryStoreCurrency(decimal amount, Currency targetCurrencyCode); + decimal ConvertFromPrimaryStoreCurrency(decimal amount, Currency targetCurrencyCode, Store store = null); diff --git a/src/Libraries/SmartStore.Services/Directory/IStateProvinceService.cs b/src/Libraries/SmartStore.Services/Directory/IStateProvinceService.cs index 95f0c91c2a..e62ff53527 100644 --- a/src/Libraries/SmartStore.Services/Directory/IStateProvinceService.cs +++ b/src/Libraries/SmartStore.Services/Directory/IStateProvinceService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using SmartStore.Core.Domain.Directory; namespace SmartStore.Services.Directory @@ -14,12 +15,19 @@ public partial interface IStateProvinceService /// The state/province void DeleteStateProvince(StateProvince stateProvince); - /// - /// Gets a state/province - /// - /// The state/province identifier - /// State/province - StateProvince GetStateProvinceById(int stateProvinceId); + /// + /// Get all states/provinces + /// + /// A value indicating whether to show hidden records + /// + IQueryable GetAllStateProvinces(bool showHidden = false); + + /// + /// Gets a state/province + /// + /// The state/province identifier + /// State/province + StateProvince GetStateProvinceById(int stateProvinceId); /// /// Gets a state/province diff --git a/src/Libraries/SmartStore.Services/Directory/StateProvinceService.cs b/src/Libraries/SmartStore.Services/Directory/StateProvinceService.cs index 27ddf6f5a6..b631dca9e9 100644 --- a/src/Libraries/SmartStore.Services/Directory/StateProvinceService.cs +++ b/src/Libraries/SmartStore.Services/Directory/StateProvinceService.cs @@ -8,10 +8,10 @@ namespace SmartStore.Services.Directory { - /// - /// State province service - /// - public partial class StateProvinceService : IStateProvinceService + /// + /// State province service + /// + public partial class StateProvinceService : IStateProvinceService { #region Constants private const string STATEPROVINCES_ALL_KEY = "SmartStore.stateprovince.all-{0}"; @@ -46,6 +46,7 @@ public StateProvinceService(ICacheManager cacheManager, #endregion #region Methods + /// /// Deletes a state/province /// @@ -63,12 +64,22 @@ public virtual void DeleteStateProvince(StateProvince stateProvince) _eventPublisher.EntityDeleted(stateProvince); } - /// - /// Gets a state/province - /// - /// The state/province identifier - /// State/province - public virtual StateProvince GetStateProvinceById(int stateProvinceId) + public virtual IQueryable GetAllStateProvinces(bool showHidden = false) + { + var query = _stateProvinceRepository.Table; + + if (!showHidden) + query = query.Where(x => x.Published); + + return query; + } + + /// + /// Gets a state/province + /// + /// The state/province identifier + /// State/province + public virtual StateProvince GetStateProvinceById(int stateProvinceId) { if (stateProvinceId == 0) return null; diff --git a/src/Libraries/SmartStore.Services/Directory/UpdateExchangeRateTask.cs b/src/Libraries/SmartStore.Services/Directory/UpdateExchangeRateTask.cs index a3d52f317c..0882af90c5 100644 --- a/src/Libraries/SmartStore.Services/Directory/UpdateExchangeRateTask.cs +++ b/src/Libraries/SmartStore.Services/Directory/UpdateExchangeRateTask.cs @@ -1,6 +1,5 @@ using System; using SmartStore.Core.Domain.Directory; -using SmartStore.Services.Configuration; using SmartStore.Services.Tasks; namespace SmartStore.Services.Directory @@ -11,15 +10,17 @@ namespace SmartStore.Services.Directory public partial class UpdateExchangeRateTask : ITask { private readonly ICurrencyService _currencyService; - private readonly ISettingService _settingService; private readonly CurrencySettings _currencySettings; + private readonly ICommonServices _services; - public UpdateExchangeRateTask(ICurrencyService currencyService, - ISettingService settingService, CurrencySettings currencySettings) + public UpdateExchangeRateTask( + ICurrencyService currencyService, + CurrencySettings currencySettings, + ICommonServices services) { this._currencyService = currencyService; - this._settingService = settingService; this._currencySettings = currencySettings; + this._services = services; } /// @@ -33,10 +34,11 @@ public void Execute(TaskExecutionContext ctx) long lastUpdateTimeTicks = _currencySettings.LastUpdateTime; DateTime lastUpdateTime = DateTime.FromBinary(lastUpdateTimeTicks); lastUpdateTime = DateTime.SpecifyKind(lastUpdateTime, DateTimeKind.Utc); + if (lastUpdateTime.AddHours(1) < DateTime.UtcNow) { //update rates each one hour - var exchangeRates = _currencyService.GetCurrencyLiveRates(_currencyService.GetCurrencyById(_currencySettings.PrimaryExchangeRateCurrencyId).CurrencyCode); + var exchangeRates = _currencyService.GetCurrencyLiveRates(_services.StoreContext.CurrentStore.PrimaryExchangeRateCurrency.CurrencyCode); foreach (var exchageRate in exchangeRates) { @@ -51,7 +53,7 @@ public void Execute(TaskExecutionContext ctx) //save new update time value _currencySettings.LastUpdateTime = DateTime.UtcNow.ToBinary(); - _settingService.SaveSetting(_currencySettings); + _services.Settings.SaveSetting(_currencySettings); } } } diff --git a/src/Libraries/SmartStore.Services/Discounts/DiscountExtentions.cs b/src/Libraries/SmartStore.Services/Discounts/DiscountExtentions.cs index d8c29939db..ccc62d5921 100644 --- a/src/Libraries/SmartStore.Services/Discounts/DiscountExtentions.cs +++ b/src/Libraries/SmartStore.Services/Discounts/DiscountExtentions.cs @@ -29,7 +29,6 @@ public static decimal GetDiscountAmount(this Discount discount, decimal amount) return result; } - public static Discount GetPreferredDiscount(this IList discounts, decimal amount) { @@ -47,20 +46,5 @@ public static Discount GetPreferredDiscount(this IList discounts, deci return preferredDiscount; } - - public static bool ContainsDiscount(this IList discounts, Discount discount) - { - if (discounts == null) - throw new ArgumentNullException("discounts"); - - if (discount == null) - throw new ArgumentNullException("discount"); - - foreach (var dis1 in discounts) - if (discount.Id == dis1.Id) - return true; - - return false; - } } } diff --git a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs index 9b75b86e8b..ffa0fa2615 100644 --- a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs +++ b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs @@ -12,6 +12,7 @@ using SmartStore.Services.Customers; using SmartStore.Services.Common; using SmartStore.Services.Configuration; +using SmartStore.Services.Orders; namespace SmartStore.Services.Discounts { @@ -317,18 +318,18 @@ public virtual bool IsDiscountValid(Discount discount, Customer customer, string } //check date range - DateTime now = DateTime.UtcNow; - int storeId = _storeContext.CurrentStore.Id; + var now = DateTime.UtcNow; + var store = _storeContext.CurrentStore; if (discount.StartDateUtc.HasValue) { - DateTime startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); + var startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); if (startDate.CompareTo(now) > 0) return false; } if (discount.EndDateUtc.HasValue) { - DateTime endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); + var endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); if (endDate.CompareTo(now) < 0) return false; } @@ -336,33 +337,37 @@ public virtual bool IsDiscountValid(Discount discount, Customer customer, string if (!CheckDiscountLimitations(discount, customer)) return false; - // discount requirements - var requirements = discount.DiscountRequirements; + // 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; + } + + // discount requirements + var requirements = discount.DiscountRequirements; foreach (var req in requirements) { - var requirementRule = LoadDiscountRequirementRuleBySystemName(req.DiscountRequirementRuleSystemName, storeId); + var requirementRule = LoadDiscountRequirementRuleBySystemName(req.DiscountRequirementRuleSystemName, store.Id); if (requirementRule == null) continue; - var request = new CheckDiscountRequirementRequest() + var request = new CheckDiscountRequirementRequest { DiscountRequirement = req, Customer = customer, - Store = _storeContext.CurrentStore + Store = store }; - if (!requirementRule.Value.CheckRequirement(request)) + + // TODO: cache result... CheckRequirement is very often called + if (!requirementRule.Value.CheckRequirement(request)) return false; } - // better not to apply discounts if there are gift cards in the cart cause the customer could "earn" money through that. - if (discount.DiscountType == DiscountType.AssignedToOrderTotal || discount.DiscountType == DiscountType.AssignedToOrderSubTotal) - { - var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, storeId); - - if (cart.Any(x => x.Item.Product.IsGiftCard)) - return false; - } - return true; } diff --git a/src/Libraries/SmartStore.Services/Discounts/IDiscountRequirementRule.cs b/src/Libraries/SmartStore.Services/Discounts/IDiscountRequirementRule.cs index 527dcacab1..15325d3141 100644 --- a/src/Libraries/SmartStore.Services/Discounts/IDiscountRequirementRule.cs +++ b/src/Libraries/SmartStore.Services/Discounts/IDiscountRequirementRule.cs @@ -1,6 +1,4 @@ - -using SmartStore.Core.ComponentModel; -using SmartStore.Core.Plugins; +using SmartStore.Core.Plugins; namespace SmartStore.Services.Discounts { diff --git a/src/Libraries/SmartStore.Services/Events/DefaultConsumerFactory.cs b/src/Libraries/SmartStore.Services/Events/DefaultConsumerFactory.cs index dd84254caa..f9c0a1e032 100644 --- a/src/Libraries/SmartStore.Services/Events/DefaultConsumerFactory.cs +++ b/src/Libraries/SmartStore.Services/Events/DefaultConsumerFactory.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using SmartStore.Core.Infrastructure; using SmartStore.Core.Events; +using SmartStore.Core.Plugins; namespace SmartStore.Services.Events { @@ -10,10 +11,14 @@ namespace SmartStore.Services.Events public class DefaultConsumerFactory : IConsumerFactory { private readonly IEnumerable, EventConsumerMetadata>> _consumers; + private readonly ICommonServices _services; - public DefaultConsumerFactory(IEnumerable, EventConsumerMetadata>> consumers) + public DefaultConsumerFactory( + IEnumerable, EventConsumerMetadata>> consumers, + ICommonServices services) { this._consumers = consumers; + this._services = services; } public IEnumerable> GetConsumers(bool? resolveAsyncs = null) @@ -22,9 +27,14 @@ public IEnumerable> GetConsumers(bool? resolveAsyncs = null) { var isActive = consumer.Metadata.IsActive; var isAsync = consumer.Metadata.ExecuteAsync; + var pluginDescriptor = consumer.Metadata.PluginDescriptor; + if (isActive && (resolveAsyncs == null || (resolveAsyncs.Value == isAsync))) { - yield return consumer.Value; + if (pluginDescriptor == null || IsActiveForStore(pluginDescriptor, _services.StoreContext.CurrentStore.Id)) + { + yield return consumer.Value; + } } } } @@ -37,6 +47,29 @@ public bool HasAsyncConsumer return _consumers.Any(c => c.Metadata.IsActive == true && c.Metadata.ExecuteAsync == true); } } + + private bool IsActiveForStore(PluginDescriptor plugin, int storeId) + { + if (storeId == 0) + { + return true; + } + + var limitedToStoresSetting = _services.Settings.GetSettingByKey(plugin.GetSettingKey("LimitedToStores")); + if (limitedToStoresSetting.IsEmpty()) + { + return true; + } + + var limitedToStores = limitedToStoresSetting.ToIntArray(); + if (limitedToStores.Length > 0) + { + var flag = limitedToStores.Contains(storeId); + return flag; + } + + return true; + } } } diff --git a/src/Libraries/SmartStore.Services/Events/EventPublisher.cs b/src/Libraries/SmartStore.Services/Events/EventPublisher.cs index 88bfc4e8cd..fc8b33df35 100644 --- a/src/Libraries/SmartStore.Services/Events/EventPublisher.cs +++ b/src/Libraries/SmartStore.Services/Events/EventPublisher.cs @@ -43,7 +43,7 @@ public void Publish(T eventMessage) // first fire/forget all async consumers if (consumerFactory.HasAsyncConsumer) { - AsyncRunner.Run(c => + AsyncRunner.Run((c, ct) => { // for wiring up dependencies correctly var newFactory = c.Resolve>(); diff --git a/src/Libraries/SmartStore.Services/ExportImport/DataSegmenter.cs b/src/Libraries/SmartStore.Services/ExportImport/DataSegmenter.cs deleted file mode 100644 index 303f12a2c5..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/DataSegmenter.cs +++ /dev/null @@ -1,370 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Text; -using Fasterflect; -using OfficeOpenXml; -using SmartStore.Core; -using SmartStore.Core.Data; - -namespace SmartStore.Services.ExportImport -{ - - internal class DataSegmenter : DisposableObject where T : BaseEntity - { - private const int BATCHSIZE = 100; - - private ExcelPackage _excelPackage; - private ExcelWorksheet _sheet; - private int _totalRows; - private int _totalColumns; - private readonly string[] _columns; - private readonly IDictionary _properties; - private IList> _currentBatch; - private IPageable _pageable; - private bool _bof; - - public DataSegmenter(Stream source) - { - Guard.ArgumentNotNull(() => source); - - _excelPackage = new ExcelPackage(source); - - // get the first worksheet in the workbook - _sheet = _excelPackage.Workbook.Worksheets.FirstOrDefault(); - if (_sheet == null) - { - throw Error.InvalidOperation("The excel package does not contain any worksheet."); - } - - if (_sheet.Dimension == null) - { - throw Error.InvalidOperation("The excel worksheet does not contain any data."); - } - - _totalColumns = _sheet.Dimension.End.Column; - _totalRows = _sheet.Dimension.End.Row - 1; // excluding 1st - - // Determine column names from 1st row (excel indexes start from 1) - var cols = new List(); - for (int i = 1; i <= _totalColumns; i++) - { - cols.Add(_sheet.Cells[1, i].Text); - } - - _columns = cols.ToArray(); - ValidateColumns(_columns); - _properties = new Dictionary(_columns.Length, StringComparer.InvariantCultureIgnoreCase); - - // determine corresponding Properties for given columns - var t = typeof(T); - foreach (var col in _columns) - { - var pi = t.GetProperty(col); - if (pi != null) - { - _properties[col] = new TargetProperty - { - IsSettable = pi.CanWrite && pi.GetSetMethod().IsPublic, - PropertyInfo = pi - }; - } - } - - _bof = true; - _pageable = new PagedList(0, BATCHSIZE, _totalRows); - } - - public int TotalRows - { - get { return _totalRows; } - } - - public int TotalColumns - { - get { return _totalColumns; } - } - - public int CurrentSegment - { - get { return _bof ? 0 : _pageable.PageNumber; } - } - - public int CurrentSegmentFirstRowIndex - { - get { return _pageable.FirstItemIndex; } - } - - public int TotalSegments - { - get { return _pageable.TotalPages; } - } - - public int BatchSize - { - get { return BATCHSIZE; } - } - - public void Reset() - { - if (_pageable.PageIndex != 0 && _currentBatch != null) - { - _currentBatch.Clear(); - _currentBatch = null; - } - _bof = true; - _pageable.PageIndex = 0; - } - - public bool ReadNextBatch() - { - if (_currentBatch != null) - { - _currentBatch.Clear(); - _currentBatch = null; - } - - if (_bof) - { - _bof = false; - return _pageable.TotalCount > 0; - } - - if (_pageable.HasNextPage) - { - _pageable.PageIndex++; - return true; - } - - Reset(); - return false; - } - - public ICollection> CurrentBatch - { - get - { - if (_currentBatch == null) - { - _currentBatch = new List>(); - - int start = _pageable.FirstItemIndex + 1; - int end = _pageable.LastItemIndex + 1; - - // Determine cell values per row - for (int r = start; r <= end; r++) - { - var values = new List(); - for (int c = 1; c <= _totalColumns; c++) - { - values.Add(_sheet.Cells[r, c].Value); - } - - _currentBatch.Add(new ImportRow(_columns, values.ToArray(), _properties, r - 1)); - } - } - - return _currentBatch.AsReadOnly(); - } - } - - protected override void OnDispose(bool disposing) - { - if (disposing) - { - _sheet = null; - if (_excelPackage != null) - { - _excelPackage.Dispose(); - _excelPackage = null; - } - } - } - - private void ValidateColumns(string[] columns) - { - if (columns.Any(x => x.IsEmpty())) - { - throw Error.InvalidOperation("The first row must contain the column names and therefore cannot have empty cells."); - } - - if (columns.Select(x => x.ToLower()).Distinct().ToArray().Length != columns.Length) - { - throw Error.InvalidOperation("The first row cannot contain duplicate column names."); - } - } - } - - internal class ImportRow : Dictionary where T : BaseEntity - { - private bool _initialized = false; - private T _entity; - private string _entityDisplayName; - private int _position; - private bool _isNew; - private ImportRowInfo _rowInfo; - - public ImportRow(string[] columns, object[] values, IDictionary properties, int position) - : base(columns.Length, StringComparer.InvariantCultureIgnoreCase) - { - _position = position; - - for (int i = 0; i < columns.Length; i++) - { - var col = columns[i]; - var val = values[i]; - - if (val != null && val.ToString().HasValue()) - { - if (!properties.ContainsKey(col) || properties[col].IsSettable) - { - // only add value when no correponding property exists (special field) - // or when property exists but it's publicly settable. - this[col] = val; - } - } - } - } - - public void Initialize(T entity, string entityDisplayName) - { - _entity = entity; - _entityDisplayName = entityDisplayName; - _isNew = _entity.Id == 0; - - _initialized = true; - } - - private void CheckInitialized() - { - if (_initialized) - { - throw Error.InvalidOperation("A row must be initialized before interacting with the entity or the data store"); - } - } - - public bool IsTransient - { - get { return _entity.Id == 0; } - } - - public bool IsNew - { - get { return _isNew; } - } - - public T Entity - { - get { return _entity; } - } - - public string EntityDisplayName - { - get { return _entityDisplayName; } - } - - public bool NameChanged - { - get; - set; - } - - public int Position - { - get { return _position; } - } - - public TProp GetValue(string columnName) - { - object value; - if (this.TryGetValue(columnName, out value)) - { - return value.Convert(); - } - - return default(TProp); - } - - public bool SetProperty(ImportResult result, T target, Expression> prop, TProp defaultValue = default(TProp), Func converter = null) - { - // TBD: (MC) do not check for perf reason? - //CheckInitialized(); - - var pi = prop.ExtractPropertyInfo(); - var propName = pi.Name; - - try - { - object value; - if (this.TryGetValue(propName, out value)) - { - // source contains field value. Set it. - TProp converted; - if (converter != null) - { - converted = converter(value); - } - else - { - if (value.ToString().ToUpper().Equals("NULL")) - { - // prop is set "explicitly" to null. - converted = default(TProp); - } - else - { - converted = value.Convert(); - } - } - return target.TrySetPropertyValue(propName, converted); - } - else - { - // source does not contain field data or it's empty... - if (IsTransient && defaultValue != null) - { - // ...but the entity is new. In this case - // set the default value if given. - return target.TrySetPropertyValue(propName, defaultValue); - } - } - } - catch (Exception ex) - { - result.AddWarning("Conversion failed: " + ex.Message, this.GetRowInfo(), propName); - } - - return false; - } - - public ImportRowInfo GetRowInfo() - { - if (_rowInfo == null) - { - _rowInfo = new ImportRowInfo(this.Position, this.EntityDisplayName); - } - - return _rowInfo; - } - - public override string ToString() - { - var str = "Pos: {0} - Name: {1}, IsNew: {2}, IsTransient: {3}".FormatCurrent( - Position, - EntityDisplayName.EmptyNull(), - _initialized ? IsNew.ToString() : "-", - _initialized ? IsTransient.ToString() : "-"); - return str; - } - } - - - internal class TargetProperty - { - public bool IsSettable { get; set; } - public PropertyInfo PropertyInfo { get; set; } - } -} diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs deleted file mode 100644 index 49c8ad6374..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ /dev/null @@ -1,1715 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; -using OfficeOpenXml; -using OfficeOpenXml.Style; -using SmartStore.Core; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Catalog; -using SmartStore.Services.Common; -using SmartStore.Services.Customers; -using SmartStore.Services.Localization; -using SmartStore.Services.Media; -using SmartStore.Services.Messages; -using SmartStore.Services.Seo; - -namespace SmartStore.Services.ExportImport -{ - /// - /// Export manager - /// - public partial class ExportManager : IExportManager - { - #region Fields - - private readonly ICategoryService _categoryService; - private readonly IManufacturerService _manufacturerService; - private readonly IProductService _productService; - private readonly IPictureService _pictureService; - private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; - private readonly ILanguageService _languageService; - - #endregion - - #region Ctor - - public ExportManager(ICategoryService categoryService, - IManufacturerService manufacturerService, - IProductService productService, - IPictureService pictureService, - INewsLetterSubscriptionService newsLetterSubscriptionService, - ILanguageService languageService) - { - this._categoryService = categoryService; - this._manufacturerService = manufacturerService; - this._productService = productService; - this._pictureService = pictureService; - this._newsLetterSubscriptionService = newsLetterSubscriptionService; - this._languageService = languageService; - } - - #endregion - - #region Utilities - - protected virtual void WriteCategories(XmlWriter xmlWriter, int parentCategoryId) - { - var categories = _categoryService.GetAllCategoriesByParentCategoryId(parentCategoryId, true); - if (categories != null && categories.Count > 0) - { - foreach (var category in categories) - { - xmlWriter.WriteStartElement("Category"); - xmlWriter.WriteElementString("Id", null, category.Id.ToString()); - xmlWriter.WriteElementString("Name", null, category.Name); - xmlWriter.WriteElementString("Description", null, category.Description); - xmlWriter.WriteElementString("CategoryTemplateId", null, category.CategoryTemplateId.ToString()); - xmlWriter.WriteElementString("MetaKeywords", null, category.MetaKeywords); - xmlWriter.WriteElementString("MetaDescription", null, category.MetaDescription); - xmlWriter.WriteElementString("MetaTitle", null, category.MetaTitle); - xmlWriter.WriteElementString("SeName", null, category.GetSeName(0)); - xmlWriter.WriteElementString("ParentCategoryId", null, category.ParentCategoryId.ToString()); - xmlWriter.WriteElementString("PictureId", null, category.PictureId.ToString()); - xmlWriter.WriteElementString("PageSize", null, category.PageSize.ToString()); - xmlWriter.WriteElementString("AllowCustomersToSelectPageSize", null, category.AllowCustomersToSelectPageSize.ToString()); - xmlWriter.WriteElementString("PageSizeOptions", null, category.PageSizeOptions); - xmlWriter.WriteElementString("PriceRanges", null, category.PriceRanges); - xmlWriter.WriteElementString("ShowOnHomePage", null, category.ShowOnHomePage.ToString()); - xmlWriter.WriteElementString("Published", null, category.Published.ToString()); - xmlWriter.WriteElementString("Deleted", null, category.Deleted.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, category.DisplayOrder.ToString()); - xmlWriter.WriteElementString("CreatedOnUtc", null, category.CreatedOnUtc.ToString()); - xmlWriter.WriteElementString("UpdatedOnUtc", null, category.UpdatedOnUtc.ToString()); - - - xmlWriter.WriteStartElement("Products"); - var productCategories = _categoryService.GetProductCategoriesByCategoryId(category.Id, 0, int.MaxValue, true); - foreach (var productCategory in productCategories) - { - var product = productCategory.Product; - if (product != null && !product.Deleted) - { - xmlWriter.WriteStartElement("ProductCategory"); - xmlWriter.WriteElementString("ProductCategoryId", null, productCategory.Id.ToString()); - xmlWriter.WriteElementString("ProductId", null, productCategory.ProductId.ToString()); - xmlWriter.WriteElementString("IsFeaturedProduct", null, productCategory.IsFeaturedProduct.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, productCategory.DisplayOrder.ToString()); - xmlWriter.WriteEndElement(); - } - } - xmlWriter.WriteEndElement(); - - xmlWriter.WriteStartElement("SubCategories"); - WriteCategories(xmlWriter, category.Id); - xmlWriter.WriteEndElement(); - xmlWriter.WriteEndElement(); - } - } - } - - #endregion - - #region Methods - - /// - /// Export manufacturer list to xml - /// - /// Manufacturers - /// Result in XML format - public virtual string ExportManufacturersToXml(IList manufacturers) - { - var sb = new StringBuilder(); - var stringWriter = new StringWriter(sb); - var xmlWriter = new XmlTextWriter(stringWriter); - xmlWriter.WriteStartDocument(); - xmlWriter.WriteStartElement("Manufacturers"); - xmlWriter.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - - foreach (var manufacturer in manufacturers) - { - xmlWriter.WriteStartElement("Manufacturer"); - - xmlWriter.WriteElementString("ManufacturerId", null, manufacturer.Id.ToString()); - xmlWriter.WriteElementString("Name", null, manufacturer.Name); - xmlWriter.WriteElementString("Description", null, manufacturer.Description); - xmlWriter.WriteElementString("ManufacturerTemplateId", null, manufacturer.ManufacturerTemplateId.ToString()); - xmlWriter.WriteElementString("MetaKeywords", null, manufacturer.MetaKeywords); - xmlWriter.WriteElementString("MetaDescription", null, manufacturer.MetaDescription); - xmlWriter.WriteElementString("MetaTitle", null, manufacturer.MetaTitle); - xmlWriter.WriteElementString("SEName", null, manufacturer.GetSeName(0)); - xmlWriter.WriteElementString("PictureId", null, manufacturer.PictureId.ToString()); - xmlWriter.WriteElementString("PageSize", null, manufacturer.PageSize.ToString()); - xmlWriter.WriteElementString("AllowCustomersToSelectPageSize", null, manufacturer.AllowCustomersToSelectPageSize.ToString()); - xmlWriter.WriteElementString("PageSizeOptions", null, manufacturer.PageSizeOptions); - xmlWriter.WriteElementString("PriceRanges", null, manufacturer.PriceRanges); - xmlWriter.WriteElementString("Published", null, manufacturer.Published.ToString()); - xmlWriter.WriteElementString("Deleted", null, manufacturer.Deleted.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, manufacturer.DisplayOrder.ToString()); - xmlWriter.WriteElementString("CreatedOnUtc", null, manufacturer.CreatedOnUtc.ToString()); - xmlWriter.WriteElementString("UpdatedOnUtc", null, manufacturer.UpdatedOnUtc.ToString()); - - xmlWriter.WriteStartElement("Products"); - var productManufacturers = _manufacturerService.GetProductManufacturersByManufacturerId(manufacturer.Id, 0, int.MaxValue, true); - if (productManufacturers != null) - { - foreach (var productManufacturer in productManufacturers) - { - var product = productManufacturer.Product; - if (product != null && !product.Deleted) - { - xmlWriter.WriteStartElement("ProductManufacturer"); - xmlWriter.WriteElementString("ProductManufacturerId", null, productManufacturer.Id.ToString()); - xmlWriter.WriteElementString("ProductId", null, productManufacturer.ProductId.ToString()); - xmlWriter.WriteElementString("IsFeaturedProduct", null, productManufacturer.IsFeaturedProduct.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, productManufacturer.DisplayOrder.ToString()); - xmlWriter.WriteEndElement(); - } - } - } - xmlWriter.WriteEndElement(); - - - xmlWriter.WriteEndElement(); - } - - xmlWriter.WriteEndElement(); - xmlWriter.WriteEndDocument(); - xmlWriter.Close(); - return stringWriter.ToString(); - } - - /// - /// Export category list to xml - /// - /// Result in XML format - public virtual string ExportCategoriesToXml() - { - var sb = new StringBuilder(); - var stringWriter = new StringWriter(sb); - var xmlWriter = new XmlTextWriter(stringWriter); - xmlWriter.WriteStartDocument(); - xmlWriter.WriteStartElement("Categories"); - xmlWriter.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - WriteCategories(xmlWriter, 0); - xmlWriter.WriteEndElement(); - xmlWriter.WriteEndDocument(); - xmlWriter.Close(); - return stringWriter.ToString(); - } - - /// - /// Export product list to xml - /// - /// Products - /// Result in XML format - public virtual string ExportProductsToXml(IList products) - { - var sb = new StringBuilder(); - var stringWriter = new StringWriter(sb); - var xmlWriter = new XmlTextWriter(stringWriter); - xmlWriter.WriteStartDocument(); - xmlWriter.WriteStartElement("Products"); - xmlWriter.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - - foreach (var product in products) - { - xmlWriter.WriteStartElement("Product"); - - xmlWriter.WriteElementString("ProductId", null, product.Id.ToString()); - xmlWriter.WriteElementString("ProductTypeId", null, product.ProductTypeId.ToString()); - xmlWriter.WriteElementString("ParentGroupedProductId", null, product.ParentGroupedProductId.ToString()); - xmlWriter.WriteElementString("VisibleIndividually", null, product.VisibleIndividually.ToString()); - xmlWriter.WriteElementString("Name", null, product.Name); - xmlWriter.WriteElementString("ShortDescription", null, product.ShortDescription); - xmlWriter.WriteElementString("FullDescription", null, product.FullDescription.RemoveInvalidXmlChars()); - xmlWriter.WriteElementString("AdminComment", null, product.AdminComment); - xmlWriter.WriteElementString("ProductTemplateId", null, product.ProductTemplateId.ToString()); - xmlWriter.WriteElementString("ShowOnHomePage", null, product.ShowOnHomePage.ToString()); - xmlWriter.WriteElementString("MetaKeywords", null, product.MetaKeywords); - xmlWriter.WriteElementString("MetaDescription", null, product.MetaDescription); - xmlWriter.WriteElementString("MetaTitle", null, product.MetaTitle); - xmlWriter.WriteElementString("SEName", null, product.GetSeName(0)); - xmlWriter.WriteElementString("AllowCustomerReviews", null, product.AllowCustomerReviews.ToString()); - xmlWriter.WriteElementString("SKU", null, product.Sku); - xmlWriter.WriteElementString("ManufacturerPartNumber", null, product.ManufacturerPartNumber); - xmlWriter.WriteElementString("Gtin", null, product.Gtin); - xmlWriter.WriteElementString("IsGiftCard", null, product.IsGiftCard.ToString()); - xmlWriter.WriteElementString("GiftCardType", null, product.GiftCardType.ToString()); - xmlWriter.WriteElementString("RequireOtherProducts", null, product.RequireOtherProducts.ToString()); - xmlWriter.WriteElementString("RequiredProductIds", null, product.RequiredProductIds); - xmlWriter.WriteElementString("AutomaticallyAddRequiredProducts", null, product.AutomaticallyAddRequiredProducts.ToString()); - xmlWriter.WriteElementString("IsDownload", null, product.IsDownload.ToString()); - xmlWriter.WriteElementString("DownloadId", null, product.DownloadId.ToString()); - xmlWriter.WriteElementString("UnlimitedDownloads", null, product.UnlimitedDownloads.ToString()); - xmlWriter.WriteElementString("MaxNumberOfDownloads", null, product.MaxNumberOfDownloads.ToString()); - if (product.DownloadExpirationDays.HasValue) - xmlWriter.WriteElementString("DownloadExpirationDays", null, product.DownloadExpirationDays.ToString()); - else - xmlWriter.WriteElementString("DownloadExpirationDays", null, string.Empty); - xmlWriter.WriteElementString("DownloadActivationType", null, product.DownloadActivationType.ToString()); - xmlWriter.WriteElementString("HasSampleDownload", null, product.HasSampleDownload.ToString()); - xmlWriter.WriteElementString("SampleDownloadId", null, product.SampleDownloadId.ToString()); - xmlWriter.WriteElementString("HasUserAgreement", null, product.HasUserAgreement.ToString()); - xmlWriter.WriteElementString("UserAgreementText", null, product.UserAgreementText); - xmlWriter.WriteElementString("IsRecurring", null, product.IsRecurring.ToString()); - xmlWriter.WriteElementString("RecurringCycleLength", null, product.RecurringCycleLength.ToString()); - xmlWriter.WriteElementString("RecurringCyclePeriodId", null, product.RecurringCyclePeriodId.ToString()); - xmlWriter.WriteElementString("RecurringTotalCycles", null, product.RecurringTotalCycles.ToString()); - xmlWriter.WriteElementString("IsShipEnabled", null, product.IsShipEnabled.ToString()); - xmlWriter.WriteElementString("IsFreeShipping", null, product.IsFreeShipping.ToString()); - xmlWriter.WriteElementString("AdditionalShippingCharge", null, product.AdditionalShippingCharge.ToString()); - xmlWriter.WriteElementString("IsEsd", null, product.IsEsd.ToString()); - xmlWriter.WriteElementString("IsTaxExempt", null, product.IsTaxExempt.ToString()); - xmlWriter.WriteElementString("TaxCategoryId", null, product.TaxCategoryId.ToString()); - xmlWriter.WriteElementString("ManageInventoryMethodId", null, product.ManageInventoryMethodId.ToString()); - xmlWriter.WriteElementString("StockQuantity", null, product.StockQuantity.ToString()); - xmlWriter.WriteElementString("DisplayStockAvailability", null, product.DisplayStockAvailability.ToString()); - xmlWriter.WriteElementString("DisplayStockQuantity", null, product.DisplayStockQuantity.ToString()); - xmlWriter.WriteElementString("MinStockQuantity", null, product.MinStockQuantity.ToString()); - xmlWriter.WriteElementString("LowStockActivityId", null, product.LowStockActivityId.ToString()); - xmlWriter.WriteElementString("NotifyAdminForQuantityBelow", null, product.NotifyAdminForQuantityBelow.ToString()); - xmlWriter.WriteElementString("BackorderModeId", null, product.BackorderModeId.ToString()); - xmlWriter.WriteElementString("AllowBackInStockSubscriptions", null, product.AllowBackInStockSubscriptions.ToString()); - xmlWriter.WriteElementString("OrderMinimumQuantity", null, product.OrderMinimumQuantity.ToString()); - xmlWriter.WriteElementString("OrderMaximumQuantity", null, product.OrderMaximumQuantity.ToString()); - xmlWriter.WriteElementString("AllowedQuantities", null, product.AllowedQuantities); - xmlWriter.WriteElementString("DisableBuyButton", null, product.DisableBuyButton.ToString()); - xmlWriter.WriteElementString("DisableWishlistButton", null, product.DisableWishlistButton.ToString()); - xmlWriter.WriteElementString("AvailableForPreOrder", null, product.AvailableForPreOrder.ToString()); - xmlWriter.WriteElementString("CallForPrice", null, product.CallForPrice.ToString()); - xmlWriter.WriteElementString("Price", null, product.Price.ToString()); - xmlWriter.WriteElementString("OldPrice", null, product.OldPrice.ToString()); - xmlWriter.WriteElementString("ProductCost", null, product.ProductCost.ToString()); - xmlWriter.WriteElementString("SpecialPrice", null, product.SpecialPrice.HasValue ? product.SpecialPrice.ToString() : ""); - xmlWriter.WriteElementString("SpecialPriceStartDateTimeUtc", null, product.SpecialPriceStartDateTimeUtc.HasValue ? product.SpecialPriceStartDateTimeUtc.ToString() : ""); - xmlWriter.WriteElementString("SpecialPriceEndDateTimeUtc", null, product.SpecialPriceEndDateTimeUtc.HasValue ? product.SpecialPriceEndDateTimeUtc.ToString() : ""); - xmlWriter.WriteElementString("CustomerEntersPrice", null, product.CustomerEntersPrice.ToString()); - xmlWriter.WriteElementString("MinimumCustomerEnteredPrice", null, product.MinimumCustomerEnteredPrice.ToString()); - xmlWriter.WriteElementString("MaximumCustomerEnteredPrice", null, product.MaximumCustomerEnteredPrice.ToString()); - xmlWriter.WriteElementString("LowestAttributeCombinationPrice", null, product.LowestAttributeCombinationPrice.HasValue ? product.LowestAttributeCombinationPrice.ToString() : ""); - xmlWriter.WriteElementString("Weight", null, product.Weight.ToString()); - xmlWriter.WriteElementString("Length", null, product.Length.ToString()); - xmlWriter.WriteElementString("Width", null, product.Width.ToString()); - xmlWriter.WriteElementString("Height", null, product.Height.ToString()); - xmlWriter.WriteElementString("Published", null, product.Published.ToString()); - xmlWriter.WriteElementString("CreatedOnUtc", null, product.CreatedOnUtc.ToString()); - xmlWriter.WriteElementString("UpdatedOnUtc", null, product.UpdatedOnUtc.ToString()); - - if (product.DeliveryTimeId.HasValue) - xmlWriter.WriteElementString("DeliveryTimeId", null, product.DeliveryTimeId.Value.ToString()); - if (product.QuantityUnitId.HasValue) - xmlWriter.WriteElementString("QuantityUnitId", null, product.QuantityUnitId.Value.ToString()); - - xmlWriter.WriteElementString("BasePriceEnabled", null, product.BasePriceEnabled.ToString()); - xmlWriter.WriteElementString("BasePriceMeasureUnit", null, product.BasePriceMeasureUnit); - if (product.BasePriceAmount.HasValue) - xmlWriter.WriteElementString("BasePriceAmount", null, product.BasePriceAmount.Value.ToString()); - if (product.BasePriceBaseAmount.HasValue) - xmlWriter.WriteElementString("BasePriceBaseAmount", null, product.BasePriceBaseAmount.Value.ToString()); - - xmlWriter.WriteElementString("BundleTitleText", null, product.BundleTitleText); - xmlWriter.WriteElementString("BundlePerItemShipping", null, product.BundlePerItemShipping.ToString()); - xmlWriter.WriteElementString("BundlePerItemPricing", null, product.BundlePerItemPricing.ToString()); - xmlWriter.WriteElementString("BundlePerItemShoppingCart", null, product.BundlePerItemShoppingCart.ToString()); - - xmlWriter.WriteStartElement("ProductDiscounts"); - var discounts = product.AppliedDiscounts; - foreach (var discount in discounts) - { - xmlWriter.WriteStartElement("ProductDiscount"); - xmlWriter.WriteElementString("DiscountId", null, discount.Id.ToString()); - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - - - xmlWriter.WriteStartElement("TierPrices"); - var tierPrices = product.TierPrices; - foreach (var tierPrice in tierPrices) - { - xmlWriter.WriteStartElement("TierPrice"); - xmlWriter.WriteElementString("TierPriceId", null, tierPrice.Id.ToString()); - xmlWriter.WriteElementString("StoreId", null, tierPrice.StoreId.ToString()); - xmlWriter.WriteElementString("CustomerRoleId", null, tierPrice.CustomerRoleId.HasValue ? tierPrice.CustomerRoleId.ToString() : "0"); - xmlWriter.WriteElementString("Quantity", null, tierPrice.Quantity.ToString()); - xmlWriter.WriteElementString("Price", null, tierPrice.Price.ToString()); - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - - xmlWriter.WriteStartElement("ProductAttributes"); - var productVariantAttributes = product.ProductVariantAttributes; - foreach (var productVariantAttribute in productVariantAttributes) - { - xmlWriter.WriteStartElement("ProductVariantAttribute"); - xmlWriter.WriteElementString("ProductVariantAttributeId", null, productVariantAttribute.Id.ToString()); - xmlWriter.WriteElementString("ProductAttributeId", null, productVariantAttribute.ProductAttributeId.ToString()); - xmlWriter.WriteElementString("TextPrompt", null, productVariantAttribute.TextPrompt); - xmlWriter.WriteElementString("IsRequired", null, productVariantAttribute.IsRequired.ToString()); - xmlWriter.WriteElementString("AttributeControlTypeId", null, productVariantAttribute.AttributeControlTypeId.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, productVariantAttribute.DisplayOrder.ToString()); - - xmlWriter.WriteStartElement("ProductVariantAttributeValues"); - var productVariantAttributeValues = productVariantAttribute.ProductVariantAttributeValues; - foreach (var productVariantAttributeValue in productVariantAttributeValues) - { - xmlWriter.WriteStartElement("ProductVariantAttributeValue"); - xmlWriter.WriteElementString("ProductVariantAttributeValueId", null, productVariantAttributeValue.Id.ToString()); - xmlWriter.WriteElementString("Name", null, productVariantAttributeValue.Name); - xmlWriter.WriteElementString("PriceAdjustment", null, productVariantAttributeValue.PriceAdjustment.ToString()); - xmlWriter.WriteElementString("WeightAdjustment", null, productVariantAttributeValue.WeightAdjustment.ToString()); - xmlWriter.WriteElementString("IsPreSelected", null, productVariantAttributeValue.IsPreSelected.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, productVariantAttributeValue.DisplayOrder.ToString()); - xmlWriter.WriteElementString("ValueTypeId", null, productVariantAttributeValue.ValueTypeId.ToString()); - xmlWriter.WriteElementString("LinkedProductId", null, productVariantAttributeValue.LinkedProductId.ToString()); - xmlWriter.WriteElementString("Quantity", null, productVariantAttributeValue.Quantity.ToString()); - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); // ProductAttributes - - xmlWriter.WriteStartElement("ProductVariantAttributeCombinations"); - var combinations = product.ProductVariantAttributeCombinations; - foreach (var combination in combinations) - { - xmlWriter.WriteStartElement("ProductVariantAttributeCombination"); - xmlWriter.WriteElementString("ProductVariantAttributeCombinationId", null, combination.Id.ToString()); - xmlWriter.WriteElementString("AllowOutOfStockOrders", null, combination.AllowOutOfStockOrders.ToString()); - xmlWriter.WriteElementString("StockQuantity", null, combination.StockQuantity.ToString()); - xmlWriter.WriteElementString("AssignedPictureIds", null, combination.AssignedPictureIds); - xmlWriter.WriteStartElement("AttributesXml"); - xmlWriter.WriteCData(combination.AttributesXml); - xmlWriter.WriteEndElement(); // AttributesXml - xmlWriter.WriteElementString("IsActive", null, combination.IsActive.ToString()); - //xmlWriter.WriteElementString("IsDefaultCombination", null, combination.IsDefaultCombination.ToString()); - if (combination.BasePriceAmount.HasValue) - xmlWriter.WriteElementString("BasePriceAmount", null, combination.BasePriceAmount.Value.ToString()); - if (combination.BasePriceBaseAmount.HasValue) - xmlWriter.WriteElementString("BasePriceBaseAmount", null, combination.BasePriceBaseAmount.Value.ToString()); - if (combination.DeliveryTimeId.HasValue) - xmlWriter.WriteElementString("DeliveryTimeId", null, combination.DeliveryTimeId.Value.ToString()); - if (combination.QuantityUnitId.HasValue) - xmlWriter.WriteElementString("QuantityUnitId", null, combination.QuantityUnitId.Value.ToString()); - if (combination.Length.HasValue) - xmlWriter.WriteElementString("Length", null, combination.Length.Value.ToString()); - if (combination.Width.HasValue) - xmlWriter.WriteElementString("Width", null, combination.Width.Value.ToString()); - if (combination.Height.HasValue) - xmlWriter.WriteElementString("Height", null, combination.Height.Value.ToString()); - if (combination.Height.HasValue) - xmlWriter.WriteElementString("Height", null, combination.Height.Value.ToString()); - if (combination.Gtin.HasValue()) - xmlWriter.WriteElementString("Gtin", null, combination.Gtin); - if (combination.Sku.HasValue()) - xmlWriter.WriteElementString("Sku", null, combination.Sku); - if (combination.ManufacturerPartNumber.HasValue()) - xmlWriter.WriteElementString("ManufacturerPartNumber", null, combination.ManufacturerPartNumber); - if (combination.Price.HasValue) - xmlWriter.WriteElementString("Price", null, combination.Price.Value.ToString()); - - xmlWriter.WriteEndElement(); // ProductVariantAttributeCombination - } - xmlWriter.WriteEndElement(); // ProductVariantAttributeCombinations - - xmlWriter.WriteStartElement("ProductPictures"); - var productPictures = product.ProductPictures; - foreach (var productPicture in productPictures) - { - xmlWriter.WriteStartElement("ProductPicture"); - xmlWriter.WriteElementString("ProductPictureId", null, productPicture.Id.ToString()); - xmlWriter.WriteElementString("PictureId", null, productPicture.PictureId.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, productPicture.DisplayOrder.ToString()); - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - - xmlWriter.WriteStartElement("ProductCategories"); - var productCategories = _categoryService.GetProductCategoriesByProductId(product.Id); - if (productCategories != null) - { - foreach (var productCategory in productCategories) - { - xmlWriter.WriteStartElement("ProductCategory"); - xmlWriter.WriteElementString("ProductCategoryId", null, productCategory.Id.ToString()); - xmlWriter.WriteElementString("CategoryId", null, productCategory.CategoryId.ToString()); - xmlWriter.WriteElementString("IsFeaturedProduct", null, productCategory.IsFeaturedProduct.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, productCategory.DisplayOrder.ToString()); - xmlWriter.WriteEndElement(); - } - } - xmlWriter.WriteEndElement(); - - xmlWriter.WriteStartElement("ProductManufacturers"); - var productManufacturers = _manufacturerService.GetProductManufacturersByProductId(product.Id); - if (productManufacturers != null) - { - foreach (var productManufacturer in productManufacturers) - { - xmlWriter.WriteStartElement("ProductManufacturer"); - xmlWriter.WriteElementString("ProductManufacturerId", null, productManufacturer.Id.ToString()); - xmlWriter.WriteElementString("ManufacturerId", null, productManufacturer.ManufacturerId.ToString()); - xmlWriter.WriteElementString("IsFeaturedProduct", null, productManufacturer.IsFeaturedProduct.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, productManufacturer.DisplayOrder.ToString()); - xmlWriter.WriteEndElement(); - } - } - xmlWriter.WriteEndElement(); - - xmlWriter.WriteStartElement("ProductSpecificationAttributes"); - var productSpecificationAttributes = product.ProductSpecificationAttributes; - foreach (var productSpecificationAttribute in productSpecificationAttributes) - { - xmlWriter.WriteStartElement("ProductSpecificationAttribute"); - xmlWriter.WriteElementString("ProductSpecificationAttributeId", null, productSpecificationAttribute.Id.ToString()); - xmlWriter.WriteElementString("SpecificationAttributeOptionId", null, productSpecificationAttribute.SpecificationAttributeOptionId.ToString()); - xmlWriter.WriteElementString("AllowFiltering", null, productSpecificationAttribute.AllowFiltering.ToString()); - xmlWriter.WriteElementString("ShowOnProductPage", null, productSpecificationAttribute.ShowOnProductPage.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, productSpecificationAttribute.DisplayOrder.ToString()); - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - - xmlWriter.WriteStartElement("ProductBundleItems"); - var bundleItems = _productService.GetBundleItems(product.Id, true); - foreach (var bundleItem in bundleItems.Select(x => x.Item)) - { - xmlWriter.WriteStartElement("ProductBundleItem"); - - xmlWriter.WriteElementString("ProductId", null, bundleItem.ProductId.ToString()); - xmlWriter.WriteElementString("BundleProductId", null, bundleItem.BundleProductId.ToString()); - xmlWriter.WriteElementString("Quantity", null, bundleItem.Quantity.ToString()); - if (bundleItem.Discount.HasValue) - xmlWriter.WriteElementString("Discount", null, bundleItem.Discount.Value.ToString()); - xmlWriter.WriteElementString("DiscountPercentage", null, bundleItem.DiscountPercentage.ToString()); - xmlWriter.WriteElementString("Name", null, bundleItem.GetLocalizedName()); - xmlWriter.WriteElementString("ShortDescription", null, bundleItem.ShortDescription); - xmlWriter.WriteElementString("FilterAttributes", null, bundleItem.FilterAttributes.ToString()); - xmlWriter.WriteElementString("HideThumbnail", null, bundleItem.HideThumbnail.ToString()); - xmlWriter.WriteElementString("Visible", null, bundleItem.Visible.ToString()); - xmlWriter.WriteElementString("Published", null, bundleItem.Published.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, bundleItem.DisplayOrder.ToString()); - xmlWriter.WriteElementString("CreatedOnUtc", null, bundleItem.CreatedOnUtc.ToString()); - xmlWriter.WriteElementString("UpdatedOnUtc", null, bundleItem.UpdatedOnUtc.ToString()); - - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - - - - xmlWriter.WriteEndElement(); - } - - xmlWriter.WriteEndElement(); - xmlWriter.WriteEndDocument(); - xmlWriter.Close(); - return stringWriter.ToString(); - } - - /// - /// Export products to XLSX - /// - /// Stream - /// Products - public virtual void ExportProductsToXlsx(Stream stream, IList products) - { - if (stream == null) - throw new ArgumentNullException("stream"); - - // ok, we can run the real code of the sample now - using (var xlPackage = new ExcelPackage(stream)) - { - // uncomment this line if you want the XML written out to the outputDir - //xlPackage.DebugMode = true; - - // get handle to the existing worksheet - var worksheet = xlPackage.Workbook.Worksheets.Add("Products"); - - // get handle to the cells range of the worksheet - var cells = worksheet.Cells; - - //Create Headers and format them - var properties = new string[] - { - "ProductTypeId", - "ParentGroupedProductId", - "VisibleIndividually", - "Name", - "ShortDescription", - "FullDescription", - "ProductTemplateId", - "ShowOnHomePage", - "MetaKeywords", - "MetaDescription", - "MetaTitle", - "SeName", - "AllowCustomerReviews", - "Published", - "SKU", - "ManufacturerPartNumber", - "Gtin", - "IsGiftCard", - "GiftCardTypeId", - "RequireOtherProducts", - "RequiredProductIds", - "AutomaticallyAddRequiredProducts", - "IsDownload", - "DownloadId", - "UnlimitedDownloads", - "MaxNumberOfDownloads", - "DownloadActivationTypeId", - "HasSampleDownload", - "SampleDownloadId", - "HasUserAgreement", - "UserAgreementText", - "IsRecurring", - "RecurringCycleLength", - "RecurringCyclePeriodId", - "RecurringTotalCycles", - "IsShipEnabled", - "IsFreeShipping", - "AdditionalShippingCharge", - "IsEsd", - "IsTaxExempt", - "TaxCategoryId", - "ManageInventoryMethodId", - "StockQuantity", - "DisplayStockAvailability", - "DisplayStockQuantity", - "MinStockQuantity", - "LowStockActivityId", - "NotifyAdminForQuantityBelow", - "BackorderModeId", - "AllowBackInStockSubscriptions", - "OrderMinimumQuantity", - "OrderMaximumQuantity", - "AllowedQuantities", - "DisableBuyButton", - "DisableWishlistButton", - "AvailableForPreOrder", - "CallForPrice", - "Price", - "OldPrice", - "ProductCost", - "SpecialPrice", - "SpecialPriceStartDateTimeUtc", - "SpecialPriceEndDateTimeUtc", - "CustomerEntersPrice", - "MinimumCustomerEnteredPrice", - "MaximumCustomerEnteredPrice", - "Weight", - "Length", - "Width", - "Height", - "CreatedOnUtc", - "CategoryIds", - "ManufacturerIds", - "Picture1", - "Picture2", - "Picture3", - "DeliveryTimeId", - "QuantityUnitId", - "BasePriceEnabled", - "BasePriceMeasureUnit", - "BasePriceAmount", - "BasePriceBaseAmount", - "BundleTitleText", - "BundlePerItemShipping", - "BundlePerItemPricing", - "BundlePerItemShoppingCart", - "BundleItemSkus" - }; - - //BEGIN: add headers for languages - var languages = _languageService.GetAllLanguages(true); - var headlines = new string[properties.Length + languages.Count * 3]; - var languageFields = new string[languages.Count * 3]; - var j = 0; - - foreach (var lang in languages) - { - languageFields.SetValue("Name[" + lang.UniqueSeoCode + "]", j++); - languageFields.SetValue("ShortDescription[" + lang.UniqueSeoCode + "]", j++); - languageFields.SetValue("FullDescription[" + lang.UniqueSeoCode + "]", j++); - } - - properties.CopyTo(headlines, 0); - languageFields.CopyTo(headlines, properties.Length); - //END: add headers for languages - - for (int i = 0; i < headlines.Length; i++) - { - cells[1, i + 1].Value = headlines[i]; - cells[1, i + 1].Style.Fill.PatternType = ExcelFillStyle.Solid; - cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(184, 204, 228)); - cells[1, i + 1].Style.Font.Bold = true; - } - - - int row = 2; - foreach (var p in products) - { - int col = 1; - - cells[row, col].Value = p.ProductTypeId; - col++; - - cells[row, col].Value = p.ParentGroupedProductId; - col++; - - cells[row, col].Value = p.VisibleIndividually; - col++; - - cells[row, col].Value = p.Name; - col++; - - cells[row, col].Value = p.ShortDescription; - col++; - - cells[row, col].Value = p.FullDescription; - col++; - - cells[row, col].Value = p.ProductTemplateId; - col++; - - cells[row, col].Value = p.ShowOnHomePage; - col++; - - cells[row, col].Value = p.MetaKeywords; - col++; - - cells[row, col].Value = p.MetaDescription; - col++; - - cells[row, col].Value = p.MetaTitle; - col++; - - cells[row, col].Value = p.GetSeName(0); - col++; - - cells[row, col].Value = p.AllowCustomerReviews; - col++; - - cells[row, col].Value = p.Published; - col++; - - cells[row, col].Value = p.Sku; - col++; - - cells[row, col].Value = p.ManufacturerPartNumber; - col++; - - cells[row, col].Value = p.Gtin; - col++; - - cells[row, col].Value = p.IsGiftCard; - col++; - - cells[row, col].Value = p.GiftCardTypeId; - col++; - - cells[row, col].Value = p.RequireOtherProducts; - col++; - - cells[row, col].Value = p.RequiredProductIds; - col++; - - cells[row, col].Value = p.AutomaticallyAddRequiredProducts; - col++; - - cells[row, col].Value = p.IsDownload; - col++; - - cells[row, col].Value = p.DownloadId; - col++; - - cells[row, col].Value = p.UnlimitedDownloads; - col++; - - cells[row, col].Value = p.MaxNumberOfDownloads; - col++; - - cells[row, col].Value = p.DownloadActivationTypeId; - col++; - - cells[row, col].Value = p.HasSampleDownload; - col++; - - cells[row, col].Value = p.SampleDownloadId; - col++; - - cells[row, col].Value = p.HasUserAgreement; - col++; - - cells[row, col].Value = p.UserAgreementText; - col++; - - cells[row, col].Value = p.IsRecurring; - col++; - - cells[row, col].Value = p.RecurringCycleLength; - col++; - - cells[row, col].Value = p.RecurringCyclePeriodId; - col++; - - cells[row, col].Value = p.RecurringTotalCycles; - col++; - - cells[row, col].Value = p.IsShipEnabled; - col++; - - cells[row, col].Value = p.IsFreeShipping; - col++; - - cells[row, col].Value = p.AdditionalShippingCharge; - col++; - - cells[row, col].Value = p.IsEsd; - col++; - - cells[row, col].Value = p.IsTaxExempt; - col++; - - cells[row, col].Value = p.TaxCategoryId; - col++; - - cells[row, col].Value = p.ManageInventoryMethodId; - col++; - - cells[row, col].Value = p.StockQuantity; - col++; - - cells[row, col].Value = p.DisplayStockAvailability; - col++; - - cells[row, col].Value = p.DisplayStockQuantity; - col++; - - cells[row, col].Value = p.MinStockQuantity; - col++; - - cells[row, col].Value = p.LowStockActivityId; - col++; - - cells[row, col].Value = p.NotifyAdminForQuantityBelow; - col++; - - cells[row, col].Value = p.BackorderModeId; - col++; - - cells[row, col].Value = p.AllowBackInStockSubscriptions; - col++; - - cells[row, col].Value = p.OrderMinimumQuantity; - col++; - - cells[row, col].Value = p.OrderMaximumQuantity; - col++; - - cells[row, col].Value = p.AllowedQuantities; - col++; - - cells[row, col].Value = p.DisableBuyButton; - col++; - - cells[row, col].Value = p.DisableWishlistButton; - col++; - - cells[row, col].Value = p.AvailableForPreOrder; - col++; - - cells[row, col].Value = p.CallForPrice; - col++; - - cells[row, col].Value = p.Price; - col++; - - cells[row, col].Value = p.OldPrice; - col++; - - cells[row, col].Value = p.ProductCost; - col++; - - cells[row, col].Value = p.SpecialPrice; - col++; - - cells[row, col].Value = p.SpecialPriceStartDateTimeUtc; - col++; - - cells[row, col].Value = p.SpecialPriceEndDateTimeUtc; - col++; - - cells[row, col].Value = p.CustomerEntersPrice; - col++; - - cells[row, col].Value = p.MinimumCustomerEnteredPrice; - col++; - - cells[row, col].Value = p.MaximumCustomerEnteredPrice; - col++; - - cells[row, col].Value = p.Weight; - col++; - - cells[row, col].Value = p.Length; - col++; - - cells[row, col].Value = p.Width; - col++; - - cells[row, col].Value = p.Height; - col++; - - cells[row, col].Value = p.CreatedOnUtc.ToOADate(); - col++; - - //category identifiers - string categoryIds = null; - foreach (var pc in _categoryService.GetProductCategoriesByProductId(p.Id)) - { - categoryIds += pc.CategoryId; - categoryIds += ";"; - } - cells[row, col].Value = categoryIds; - col++; - - //manufacturer identifiers - string manufacturerIds = null; - foreach (var pm in _manufacturerService.GetProductManufacturersByProductId(p.Id)) - { - manufacturerIds += pm.ManufacturerId; - manufacturerIds += ";"; - } - cells[row, col].Value = manufacturerIds; - col++; - - //pictures (up to 3 pictures) - var pics = new string[] { null, null, null }; - var pictures = _pictureService.GetPicturesByProductId(p.Id, 3); - for (int i = 0; i < pictures.Count; i++) - { - pics[i] = _pictureService.GetThumbLocalPath(pictures[i]); - pictures[i].PictureBinary = null; - } - cells[row, col].Value = pics[0]; - col++; - cells[row, col].Value = pics[1]; - col++; - cells[row, col].Value = pics[2]; - col++; - - cells[row, col].Value = p.DeliveryTimeId; - col++; - cells[row, col].Value = p.QuantityUnitId; - col++; - cells[row, col].Value = p.BasePriceEnabled; - col++; - cells[row, col].Value = p.BasePriceMeasureUnit; - col++; - cells[row, col].Value = p.BasePriceAmount; - col++; - cells[row, col].Value = p.BasePriceBaseAmount; - col++; - - cells[row, col].Value = p.BundleTitleText; - col++; - - cells[row, col].Value = p.BundlePerItemShipping; - col++; - - cells[row, col].Value = p.BundlePerItemPricing; - col++; - - cells[row, col].Value = p.BundlePerItemShoppingCart; - col++; - - string bundleItemSkus = ""; - - if (p.ProductType == ProductType.BundledProduct) - { - bundleItemSkus = string.Join(",", _productService.GetBundleItems(p.Id, true).Select(x => x.Item.Product.Sku)); - } - - cells[row, col].Value = bundleItemSkus; - col++; - - //BEGIN: export localized values - foreach (var lang in languages) - { - worksheet.Cells[row, col].Value = p.GetLocalized(x => x.Name, lang.Id, false, false); - col++; - - worksheet.Cells[row, col].Value = p.GetLocalized(x => x.ShortDescription, lang.Id, false, false); - col++; - - worksheet.Cells[row, col].Value = p.GetLocalized(x => x.FullDescription, lang.Id, false, false); - col++; - } - //END: export localized values - - row++; - } - - // we had better add some document properties to the spreadsheet - - // set some core property values - //var storeName = _storeInformationSettings.StoreName; - //var storeUrl = _storeInformationSettings.StoreUrl; - //xlPackage.Workbook.Properties.Title = string.Format("{0} products", storeName); - //xlPackage.Workbook.Properties.Author = storeName; - //xlPackage.Workbook.Properties.Subject = string.Format("{0} products", storeName); - //xlPackage.Workbook.Properties.Keywords = string.Format("{0} products", storeName); - //xlPackage.Workbook.Properties.Category = "Products"; - //xlPackage.Workbook.Properties.Comments = string.Format("{0} products", storeName); - - // set some extended property values - //xlPackage.Workbook.Properties.Company = storeName; - //xlPackage.Workbook.Properties.HyperlinkBase = new Uri(storeUrl); - - // save the new spreadsheet - xlPackage.Save(); - } - - // EPPLus has serious memory leak problems. - // We enforce the garbage collector to release unused memory, - // it's not perfect, but better than nothing. - GC.Collect(); - } - - /// - /// Export order list to xml - /// - /// Orders - /// Result in XML format - public virtual string ExportOrdersToXml(IList orders) - { - var sb = new StringBuilder(); - var stringWriter = new StringWriter(sb); - var xmlWriter = new XmlTextWriter(stringWriter); - xmlWriter.WriteStartDocument(); - xmlWriter.WriteStartElement("Orders"); - xmlWriter.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - - - foreach (var order in orders) - { - xmlWriter.WriteStartElement("Order"); - - xmlWriter.WriteElementString("OrderId", null, order.GetOrderNumber()); - xmlWriter.WriteElementString("OrderGuid", null, order.OrderGuid.ToString()); - xmlWriter.WriteElementString("StoreId", null, order.StoreId.ToString()); - xmlWriter.WriteElementString("CustomerId", null, order.CustomerId.ToString()); - xmlWriter.WriteElementString("CustomerLanguageId", null, order.CustomerLanguageId.ToString()); - xmlWriter.WriteElementString("CustomerTaxDisplayTypeId", null, order.CustomerTaxDisplayTypeId.ToString()); - xmlWriter.WriteElementString("CustomerIp", null, order.CustomerIp); - xmlWriter.WriteElementString("OrderSubtotalInclTax", null, order.OrderSubtotalInclTax.ToString()); - xmlWriter.WriteElementString("OrderSubtotalExclTax", null, order.OrderSubtotalExclTax.ToString()); - xmlWriter.WriteElementString("OrderSubTotalDiscountInclTax", null, order.OrderSubTotalDiscountInclTax.ToString()); - xmlWriter.WriteElementString("OrderSubTotalDiscountExclTax", null, order.OrderSubTotalDiscountExclTax.ToString()); - xmlWriter.WriteElementString("OrderShippingInclTax", null, order.OrderShippingInclTax.ToString()); - xmlWriter.WriteElementString("OrderShippingExclTax", null, order.OrderShippingExclTax.ToString()); - xmlWriter.WriteElementString("PaymentMethodAdditionalFeeInclTax", null, order.PaymentMethodAdditionalFeeInclTax.ToString()); - xmlWriter.WriteElementString("PaymentMethodAdditionalFeeExclTax", null, order.PaymentMethodAdditionalFeeExclTax.ToString()); - xmlWriter.WriteElementString("TaxRates", null, order.TaxRates); - xmlWriter.WriteElementString("OrderTax", null, order.OrderTax.ToString()); - xmlWriter.WriteElementString("OrderTotal", null, order.OrderTotal.ToString()); - xmlWriter.WriteElementString("RefundedAmount", null, order.RefundedAmount.ToString()); - xmlWriter.WriteElementString("OrderDiscount", null, order.OrderDiscount.ToString()); - xmlWriter.WriteElementString("CurrencyRate", null, order.CurrencyRate.ToString()); - xmlWriter.WriteElementString("CustomerCurrencyCode", null, order.CustomerCurrencyCode); - xmlWriter.WriteElementString("AffiliateId", null, order.AffiliateId.ToString()); - xmlWriter.WriteElementString("OrderStatusId", null, order.OrderStatusId.ToString()); - xmlWriter.WriteElementString("AllowStoringCreditCardNumber", null, order.AllowStoringCreditCardNumber.ToString()); - xmlWriter.WriteElementString("CardType", null, order.CardType); - xmlWriter.WriteElementString("CardName", null, order.CardName); - xmlWriter.WriteElementString("CardNumber", null, order.CardNumber); - xmlWriter.WriteElementString("MaskedCreditCardNumber", null, order.MaskedCreditCardNumber); - xmlWriter.WriteElementString("CardCvv2", null, order.CardCvv2); - xmlWriter.WriteElementString("CardExpirationMonth", null, order.CardExpirationMonth); - xmlWriter.WriteElementString("CardExpirationYear", null, order.CardExpirationYear); - - //codehint: sm-add begin - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitAccountHolder); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitAccountNumber); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitBankCode); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitBankName); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitBIC); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitCountry); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitIban); - //codehint: sm-add end - - xmlWriter.WriteElementString("PaymentMethodSystemName", null, order.PaymentMethodSystemName); - xmlWriter.WriteElementString("AuthorizationTransactionId", null, order.AuthorizationTransactionId); - xmlWriter.WriteElementString("AuthorizationTransactionCode", null, order.AuthorizationTransactionCode); - xmlWriter.WriteElementString("AuthorizationTransactionResult", null, order.AuthorizationTransactionResult); - xmlWriter.WriteElementString("CaptureTransactionId", null, order.CaptureTransactionId); - xmlWriter.WriteElementString("CaptureTransactionResult", null, order.CaptureTransactionResult); - xmlWriter.WriteElementString("SubscriptionTransactionId", null, order.SubscriptionTransactionId); - xmlWriter.WriteElementString("PurchaseOrderNumber", null, order.PurchaseOrderNumber); - xmlWriter.WriteElementString("PaymentStatusId", null, order.PaymentStatusId.ToString()); - xmlWriter.WriteElementString("PaidDateUtc", null, (order.PaidDateUtc == null) ? string.Empty : order.PaidDateUtc.Value.ToString()); - xmlWriter.WriteElementString("ShippingStatusId", null, order.ShippingStatusId.ToString()); - xmlWriter.WriteElementString("ShippingMethod", null, order.ShippingMethod); - xmlWriter.WriteElementString("ShippingRateComputationMethodSystemName", null, order.ShippingRateComputationMethodSystemName); - xmlWriter.WriteElementString("VatNumber", null, order.VatNumber); - xmlWriter.WriteElementString("Deleted", null, order.Deleted.ToString()); - xmlWriter.WriteElementString("CreatedOnUtc", null, order.CreatedOnUtc.ToString()); - xmlWriter.WriteElementString("UpdatedOnUtc", null, order.UpdatedOnUtc.ToString()); - xmlWriter.WriteElementString("RewardPointsRemaining", null, order.RewardPointsRemaining.HasValue ? order.RewardPointsRemaining.Value.ToString() : ""); - - //products - var orderItems = order.OrderItems; - if (orderItems.Count > 0) - { - xmlWriter.WriteStartElement("OrderItems"); - foreach (var orderItem in orderItems) - { - xmlWriter.WriteStartElement("OrderItem"); - xmlWriter.WriteElementString("Id", null, orderItem.Id.ToString()); - xmlWriter.WriteElementString("OrderItemGuid", null, orderItem.OrderItemGuid.ToString()); - xmlWriter.WriteElementString("ProductId", null, orderItem.ProductId.ToString()); - - xmlWriter.WriteElementString("ProductName", null, orderItem.Product.Name); - xmlWriter.WriteElementString("UnitPriceInclTax", null, orderItem.UnitPriceInclTax.ToString()); - xmlWriter.WriteElementString("UnitPriceExclTax", null, orderItem.UnitPriceExclTax.ToString()); - xmlWriter.WriteElementString("PriceInclTax", null, orderItem.PriceInclTax.ToString()); - xmlWriter.WriteElementString("PriceExclTax", null, orderItem.PriceExclTax.ToString()); - xmlWriter.WriteElementString("AttributeDescription", null, orderItem.AttributeDescription); - xmlWriter.WriteElementString("AttributesXml", null, orderItem.AttributesXml); - xmlWriter.WriteElementString("Quantity", null, orderItem.Quantity.ToString()); - xmlWriter.WriteElementString("DiscountAmountInclTax", null, orderItem.DiscountAmountInclTax.ToString()); - xmlWriter.WriteElementString("DiscountAmountExclTax", null, orderItem.DiscountAmountExclTax.ToString()); - xmlWriter.WriteElementString("DownloadCount", null, orderItem.DownloadCount.ToString()); - xmlWriter.WriteElementString("IsDownloadActivated", null, orderItem.IsDownloadActivated.ToString()); - xmlWriter.WriteElementString("LicenseDownloadId", null, orderItem.LicenseDownloadId.ToString()); - xmlWriter.WriteElementString("BundleData", null, orderItem.BundleData); - xmlWriter.WriteElementString("ProductCost", null, orderItem.ProductCost.ToString()); - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - } - - //shipments - var shipments = order.Shipments.OrderBy(x => x.CreatedOnUtc).ToList(); - if (shipments.Count > 0) - { - xmlWriter.WriteStartElement("Shipments"); - foreach (var shipment in shipments) - { - xmlWriter.WriteStartElement("Shipment"); - xmlWriter.WriteElementString("ShipmentId", null, shipment.Id.ToString()); - xmlWriter.WriteElementString("TrackingNumber", null, shipment.TrackingNumber); - xmlWriter.WriteElementString("TotalWeight", null, shipment.TotalWeight.HasValue ? shipment.TotalWeight.Value.ToString() : ""); - - xmlWriter.WriteElementString("ShippedDateUtc", null, shipment.ShippedDateUtc.HasValue ? - shipment.ShippedDateUtc.ToString() : ""); - xmlWriter.WriteElementString("DeliveryDateUtc", null, shipment.DeliveryDateUtc.HasValue ? - shipment.DeliveryDateUtc.Value.ToString() : ""); - xmlWriter.WriteElementString("CreatedOnUtc", null, shipment.CreatedOnUtc.ToString()); - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - } - - xmlWriter.WriteEndElement(); - xmlWriter.WriteEndDocument(); - xmlWriter.Close(); - return stringWriter.ToString(); - } - - /// - /// Export orders to XLSX - /// - /// Stream - /// Orders - public virtual void ExportOrdersToXlsx(Stream stream, IList orders) - { - if (stream == null) - throw new ArgumentNullException("stream"); - - // ok, we can run the real code of the sample now - using (var xlPackage = new ExcelPackage(stream)) - { - // uncomment this line if you want the XML written out to the outputDir - //xlPackage.DebugMode = true; - - // get handle to the existing worksheet - var worksheet = xlPackage.Workbook.Worksheets.Add("Orders"); - //Create Headers and format them - var properties = new string[] - { - //order properties - "OrderId", - "OrderGuid", - "CustomerId", - "OrderSubtotalInclTax", - "OrderSubtotalExclTax", - "OrderSubTotalDiscountInclTax", - "OrderSubTotalDiscountExclTax", - "OrderShippingInclTax", - "OrderShippingExclTax", - "PaymentMethodAdditionalFeeInclTax", - "PaymentMethodAdditionalFeeExclTax", - "TaxRates", - "OrderTax", - "OrderTotal", - "RefundedAmount", - "OrderDiscount", - "CurrencyRate", - "CustomerCurrencyCode", - "AffiliateId", - "OrderStatusId", - "PaymentMethodSystemName", - "PurchaseOrderNumber", - "PaymentStatusId", - "ShippingStatusId", - "ShippingMethod", - "ShippingRateComputationMethodSystemName", - "VatNumber", - "CreatedOnUtc", - "UpdatedOnUtc", - "RewardPointsRemaining", - //billing address - "BillingFirstName", - "BillingLastName", - "BillingEmail", - "BillingCompany", - "BillingCountry", - "BillingStateProvince", - "BillingCity", - "BillingAddress1", - "BillingAddress2", - "BillingZipPostalCode", - "BillingPhoneNumber", - "BillingFaxNumber", - //shipping address - "ShippingFirstName", - "ShippingLastName", - "ShippingEmail", - "ShippingCompany", - "ShippingCountry", - "ShippingStateProvince", - "ShippingCity", - "ShippingAddress1", - "ShippingAddress2", - "ShippingZipPostalCode", - "ShippingPhoneNumber", - "ShippingFaxNumber", - }; - for (int i = 0; i < properties.Length; i++) - { - worksheet.Cells[1, i + 1].Value = properties[i]; - worksheet.Cells[1, i + 1].Style.Fill.PatternType = ExcelFillStyle.Solid; - worksheet.Cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(184, 204, 228)); - worksheet.Cells[1, i + 1].Style.Font.Bold = true; - } - - - int row = 2; - foreach (var order in orders) - { - int col = 1; - - //order properties - worksheet.Cells[row, col].Value = order.GetOrderNumber(); - col++; - - worksheet.Cells[row, col].Value = order.OrderGuid; - col++; - - worksheet.Cells[row, col].Value = order.CustomerId; - col++; - - worksheet.Cells[row, col].Value = order.OrderSubtotalInclTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderSubtotalExclTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderSubTotalDiscountInclTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderSubTotalDiscountExclTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderShippingInclTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderShippingExclTax; - col++; - - worksheet.Cells[row, col].Value = order.PaymentMethodAdditionalFeeInclTax; - col++; - - worksheet.Cells[row, col].Value = order.PaymentMethodAdditionalFeeExclTax; - col++; - - worksheet.Cells[row, col].Value = order.TaxRates; - col++; - - worksheet.Cells[row, col].Value = order.OrderTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderTotal; - col++; - - worksheet.Cells[row, col].Value = order.RefundedAmount; - col++; - - worksheet.Cells[row, col].Value = order.OrderDiscount; - col++; - - worksheet.Cells[row, col].Value = order.CurrencyRate; - col++; - - worksheet.Cells[row, col].Value = order.CustomerCurrencyCode; - col++; - - worksheet.Cells[row, col].Value = order.AffiliateId; - col++; - - worksheet.Cells[row, col].Value = order.OrderStatusId; - col++; - - worksheet.Cells[row, col].Value = order.PaymentMethodSystemName; - col++; - - worksheet.Cells[row, col].Value = order.PurchaseOrderNumber; - col++; - - worksheet.Cells[row, col].Value = order.PaymentStatusId; - col++; - - worksheet.Cells[row, col].Value = order.ShippingStatusId; - col++; - - worksheet.Cells[row, col].Value = order.ShippingMethod; - col++; - - worksheet.Cells[row, col].Value = order.ShippingRateComputationMethodSystemName; - col++; - - worksheet.Cells[row, col].Value = order.VatNumber; - col++; - - worksheet.Cells[row, col].Value = order.CreatedOnUtc.ToOADate(); - col++; - - worksheet.Cells[row, col].Value = order.UpdatedOnUtc.ToOADate(); - col++; - - worksheet.Cells[row, col].Value = (order.RewardPointsRemaining.HasValue ? order.RewardPointsRemaining.Value.ToString() : ""); - col++; - - - //billing address - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.FirstName : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.LastName : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.Email : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.Company : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null && order.BillingAddress.Country != null ? order.BillingAddress.Country.Name : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null && order.BillingAddress.StateProvince != null ? order.BillingAddress.StateProvince.Name : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.City : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.Address1 : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.Address2 : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.ZipPostalCode : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.PhoneNumber : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.FaxNumber : ""; - col++; - - //shipping address - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.FirstName : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.LastName : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.Email : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.Company : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null && order.ShippingAddress.Country != null ? order.ShippingAddress.Country.Name : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null && order.ShippingAddress.StateProvince != null ? order.ShippingAddress.StateProvince.Name : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.City : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.Address1 : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.Address2 : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.ZipPostalCode : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.PhoneNumber : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.FaxNumber : ""; - col++; - - //next row - row++; - } - - - - - - - - - // we had better add some document properties to the spreadsheet - - // set some core property values - //var storeName = _storeInformationSettings.StoreName; - //var storeUrl = _storeInformationSettings.StoreUrl; - //xlPackage.Workbook.Properties.Title = string.Format("{0} orders", storeName); - //xlPackage.Workbook.Properties.Author = storeName; - //xlPackage.Workbook.Properties.Subject = string.Format("{0} orders", storeName); - //xlPackage.Workbook.Properties.Keywords = string.Format("{0} orders", storeName); - //xlPackage.Workbook.Properties.Category = "Orders"; - //xlPackage.Workbook.Properties.Comments = string.Format("{0} orders", storeName); - - // set some extended property values - //xlPackage.Workbook.Properties.Company = storeName; - //xlPackage.Workbook.Properties.HyperlinkBase = new Uri(storeUrl); - - // save the new spreadsheet - xlPackage.Save(); - } - } - - /// - /// Export customer list to XLSX - /// - /// Stream - /// Customers - public virtual void ExportCustomersToXlsx(Stream stream, IList customers) - { - if (stream == null) - throw new ArgumentNullException("stream"); - - // ok, we can run the real code of the sample now - using (var xlPackage = new ExcelPackage(stream)) - { - // uncomment this line if you want the XML written out to the outputDir - //xlPackage.DebugMode = true; - - // get handle to the existing worksheet - var worksheet = xlPackage.Workbook.Worksheets.Add("Customers"); - //Create Headers and format them - var properties = new string[] - { - "CustomerId", - "CustomerGuid", - "Email", - "Username", - "PasswordStr",//why can't we use 'Password' name? - "PasswordFormatId", - "PasswordSalt", - "LanguageId", - "CurrencyId", - "TaxDisplayTypeId", - "IsTaxExempt", - "VatNumber", - "VatNumberStatusId", - "TimeZoneId", - "AffiliateId", - "Active", - "IsGuest", - "IsRegistered", - "IsAdministrator", - "IsForumModerator", - "FirstName", - "LastName", - "Gender", - "Company", - "StreetAddress", - "StreetAddress2", - "ZipPostalCode", - "City", - "CountryId", - "StateProvinceId", - "Phone", - "Fax", - "AvatarPictureId", - "ForumPostCount", - "Signature", - }; - for (int i = 0; i < properties.Length; i++) - { - worksheet.Cells[1, i + 1].Value = properties[i]; - worksheet.Cells[1, i + 1].Style.Fill.PatternType = ExcelFillStyle.Solid; - worksheet.Cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(184, 204, 228)); - worksheet.Cells[1, i + 1].Style.Font.Bold = true; - } - - - int row = 2; - foreach (var customer in customers) - { - int col = 1; - - worksheet.Cells[row, col].Value = customer.Id; - col++; - - worksheet.Cells[row, col].Value = customer.CustomerGuid; - col++; - - worksheet.Cells[row, col].Value = customer.Email; - col++; - - worksheet.Cells[row, col].Value = customer.Username; - col++; - - worksheet.Cells[row, col].Value = customer.Password; - col++; - - worksheet.Cells[row, col].Value = customer.PasswordFormatId; - col++; - - worksheet.Cells[row, col].Value = customer.PasswordSalt; - col++; - - worksheet.Cells[row, col].Value = customer.IsTaxExempt; - col++; - - worksheet.Cells[row, col].Value = customer.AffiliateId; - col++; - - worksheet.Cells[row, col].Value = customer.Active; - col++; - - //roles - worksheet.Cells[row, col].Value = customer.IsGuest(); - col++; - - worksheet.Cells[row, col].Value = customer.IsRegistered(); - col++; - - worksheet.Cells[row, col].Value = customer.IsAdmin(); - col++; - - worksheet.Cells[row, col].Value = customer.IsForumModerator(); - col++; - - //attributes - var firstName = customer.GetAttribute(SystemCustomerAttributeNames.FirstName); - var lastName = customer.GetAttribute(SystemCustomerAttributeNames.LastName); - var gender = customer.GetAttribute(SystemCustomerAttributeNames.Gender); - var company = customer.GetAttribute(SystemCustomerAttributeNames.Company); - var streetAddress = customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress); - var streetAddress2 = customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress2); - var zipPostalCode = customer.GetAttribute(SystemCustomerAttributeNames.ZipPostalCode); - var city = customer.GetAttribute(SystemCustomerAttributeNames.City); - var countryId = customer.GetAttribute(SystemCustomerAttributeNames.CountryId); - var stateProvinceId = customer.GetAttribute(SystemCustomerAttributeNames.StateProvinceId); - var phone = customer.GetAttribute(SystemCustomerAttributeNames.Phone); - var fax = customer.GetAttribute(SystemCustomerAttributeNames.Fax); - var vatNumber = customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); - var vatNumberStatusId = customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); - var timeZoneId = customer.GetAttribute(SystemCustomerAttributeNames.TimeZoneId); - - var avatarPictureId = customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); - var forumPostCount = customer.GetAttribute(SystemCustomerAttributeNames.ForumPostCount); - var signature = customer.GetAttribute(SystemCustomerAttributeNames.Signature); - - worksheet.Cells[row, col].Value = firstName; - col++; - - worksheet.Cells[row, col].Value = lastName; - col++; - - worksheet.Cells[row, col].Value = gender; - col++; - - worksheet.Cells[row, col].Value = company; - col++; - - worksheet.Cells[row, col].Value = streetAddress; - col++; - - worksheet.Cells[row, col].Value = streetAddress2; - col++; - - worksheet.Cells[row, col].Value = zipPostalCode; - col++; - - worksheet.Cells[row, col].Value = city; - col++; - - worksheet.Cells[row, col].Value = countryId; - col++; - - worksheet.Cells[row, col].Value = stateProvinceId; - col++; - - worksheet.Cells[row, col].Value = phone; - col++; - - worksheet.Cells[row, col].Value = fax; - col++; - - worksheet.Cells[row, col].Value = vatNumber; - col++; - - worksheet.Cells[row, col].Value = vatNumberStatusId; - col++; - - worksheet.Cells[row, col].Value = avatarPictureId; - col++; - - worksheet.Cells[row, col].Value = timeZoneId; - col++; - - worksheet.Cells[row, col].Value = forumPostCount; - col++; - - worksheet.Cells[row, col].Value = signature; - col++; - - row++; - } - - - - - - - - - // we had better add some document properties to the spreadsheet - - // set some core property values - //var storeName = _storeInformationSettings.StoreName; - //var storeUrl = _storeInformationSettings.StoreUrl; - //xlPackage.Workbook.Properties.Title = string.Format("{0} customers", storeName); - //xlPackage.Workbook.Properties.Author = storeName; - //xlPackage.Workbook.Properties.Subject = string.Format("{0} customers", storeName); - //xlPackage.Workbook.Properties.Keywords = string.Format("{0} customers", storeName); - //xlPackage.Workbook.Properties.Category = "Customers"; - //xlPackage.Workbook.Properties.Comments = string.Format("{0} customers", storeName); - - // set some extended property values - //xlPackage.Workbook.Properties.Company = storeName; - //xlPackage.Workbook.Properties.HyperlinkBase = new Uri(storeUrl); - - // save the new spreadsheet - xlPackage.Save(); - } - } - - /// - /// Export customer list to xml - /// - /// Customers - /// Result in XML format - public virtual string ExportCustomersToXml(IList customers) - { - var sb = new StringBuilder(); - var stringWriter = new StringWriter(sb); - var xmlWriter = new XmlTextWriter(stringWriter); - xmlWriter.WriteStartDocument(); - xmlWriter.WriteStartElement("Customers"); - xmlWriter.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - - foreach (var customer in customers) - { - xmlWriter.WriteStartElement("Customer"); - xmlWriter.WriteElementString("CustomerId", null, customer.Id.ToString()); - xmlWriter.WriteElementString("CustomerGuid", null, customer.CustomerGuid.ToString()); - xmlWriter.WriteElementString("Email", null, customer.Email); - xmlWriter.WriteElementString("Username", null, customer.Username); - xmlWriter.WriteElementString("Password", null, customer.Password); - xmlWriter.WriteElementString("PasswordFormatId", null, customer.PasswordFormatId.ToString()); - xmlWriter.WriteElementString("PasswordSalt", null, customer.PasswordSalt); - xmlWriter.WriteElementString("IsTaxExempt", null, customer.IsTaxExempt.ToString()); - xmlWriter.WriteElementString("AffiliateId", null, customer.AffiliateId.ToString()); - xmlWriter.WriteElementString("Active", null, customer.Active.ToString()); - - - xmlWriter.WriteElementString("IsGuest", null, customer.IsGuest().ToString()); - xmlWriter.WriteElementString("IsRegistered", null, customer.IsRegistered().ToString()); - xmlWriter.WriteElementString("IsAdministrator", null, customer.IsAdmin().ToString()); - xmlWriter.WriteElementString("IsForumModerator", null, customer.IsForumModerator().ToString()); - - xmlWriter.WriteElementString("FirstName", null, customer.GetAttribute(SystemCustomerAttributeNames.FirstName)); - xmlWriter.WriteElementString("LastName", null, customer.GetAttribute(SystemCustomerAttributeNames.LastName)); - xmlWriter.WriteElementString("Gender", null, customer.GetAttribute(SystemCustomerAttributeNames.Gender)); - xmlWriter.WriteElementString("Company", null, customer.GetAttribute(SystemCustomerAttributeNames.Company)); - - xmlWriter.WriteElementString("CountryId", null, customer.GetAttribute(SystemCustomerAttributeNames.CountryId).ToString()); - xmlWriter.WriteElementString("StreetAddress", null, customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress)); - xmlWriter.WriteElementString("StreetAddress2", null, customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress2)); - xmlWriter.WriteElementString("ZipPostalCode", null, customer.GetAttribute(SystemCustomerAttributeNames.ZipPostalCode)); - xmlWriter.WriteElementString("City", null, customer.GetAttribute(SystemCustomerAttributeNames.City)); - xmlWriter.WriteElementString("CountryId", null, customer.GetAttribute(SystemCustomerAttributeNames.CountryId).ToString()); - xmlWriter.WriteElementString("StateProvinceId", null, customer.GetAttribute(SystemCustomerAttributeNames.StateProvinceId).ToString()); - xmlWriter.WriteElementString("Phone", null, customer.GetAttribute(SystemCustomerAttributeNames.Phone)); - xmlWriter.WriteElementString("Fax", null, customer.GetAttribute(SystemCustomerAttributeNames.Fax)); - xmlWriter.WriteElementString("VatNumber", null, customer.GetAttribute(SystemCustomerAttributeNames.VatNumber)); - xmlWriter.WriteElementString("VatNumberStatusId", null, customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId).ToString()); - xmlWriter.WriteElementString("TimeZoneId", null, customer.GetAttribute(SystemCustomerAttributeNames.TimeZoneId)); - - var newsletter = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(customer.Email); - bool subscribedToNewsletters = newsletter != null && newsletter.Active; - xmlWriter.WriteElementString("Newsletter", null, subscribedToNewsletters.ToString()); - - xmlWriter.WriteElementString("AvatarPictureId", null, customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId).ToString()); - xmlWriter.WriteElementString("ForumPostCount", null, customer.GetAttribute(SystemCustomerAttributeNames.ForumPostCount).ToString()); - xmlWriter.WriteElementString("Signature", null, customer.GetAttribute(SystemCustomerAttributeNames.Signature)); - - xmlWriter.WriteEndElement(); - } - - xmlWriter.WriteEndElement(); - xmlWriter.WriteEndDocument(); - xmlWriter.Close(); - return stringWriter.ToString(); - } - - - #endregion - } -} diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs deleted file mode 100644 index 5f05271673..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Orders; - -namespace SmartStore.Services.ExportImport -{ - /// - /// Export manager interface - /// - public interface IExportManager - { - /// - /// Export manufacturer list to xml - /// - /// Manufacturers - /// Result in XML format - string ExportManufacturersToXml(IList manufacturers); - - /// - /// Export category list to xml - /// - /// Result in XML format - string ExportCategoriesToXml(); - - /// - /// Export product list to xml - /// - /// Products - /// Result in XML format - string ExportProductsToXml(IList products); - - /// - /// Export products to XLSX - /// - /// Stream - /// Products - void ExportProductsToXlsx(Stream stream, IList products); - - /// - /// Export order list to xml - /// - /// Orders - /// Result in XML format - string ExportOrdersToXml(IList orders); - - /// - /// Export orders to XLSX - /// - /// Stream - /// Orders - void ExportOrdersToXlsx(Stream stream, IList orders); - - /// - /// Export customer list to XLSX - /// - /// Stream - /// Customers - void ExportCustomersToXlsx(Stream stream, IList customers); - - /// - /// Export customer list to xml - /// - /// Customers - /// Result in XML format - string ExportCustomersToXml(IList customers); - } -} diff --git a/src/Libraries/SmartStore.Services/ExportImport/IImportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/IImportManager.cs deleted file mode 100644 index 52dc196107..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/IImportManager.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using SmartStore.Core.Data; -using SmartStore.Core.Async; - -namespace SmartStore.Services.ExportImport -{ - /// - /// Import manager interface - /// - public interface IImportManager - { - Task ImportProductsFromExcelAsync( - Stream stream, - CancellationToken cancellationToken, - IProgress progress = null); - - /// - /// Dumps an instance to a string - /// - /// The result instance - /// The report - string CreateTextReport(ImportResult result); - } - - public static class IImportManagerExtensions - { - public static Task ImportProductsFromExcelAsync( - this IImportManager importManager, - Stream stream, - IProgress progress = null) - { - return importManager.ImportProductsFromExcelAsync(stream, CancellationToken.None, progress); - } - - public static ImportResult ImportProductsFromExcel(this IImportManager importManager, Stream stream) - { - //Func> fn = () => importManager.ImportProductsFromExcelAsync(stream); - //return fn.RunSync(); - return AsyncRunner.RunSync(() => importManager.ImportProductsFromExcelAsync(stream)); - } - } -} diff --git a/src/Libraries/SmartStore.Services/ExportImport/ImportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ImportManager.cs deleted file mode 100644 index 6abf6b3eeb..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/ImportManager.cs +++ /dev/null @@ -1,794 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Data; -using SmartStore.Services.Catalog; -using SmartStore.Core.Events; -using SmartStore.Services.Localization; -using SmartStore.Services.Media; -using SmartStore.Services.Seo; -using SmartStore.Utilities; -using System.Text; -using SmartStore.Core.Domain.Seo; -using SmartStore.Core.Domain.Media; - -namespace SmartStore.Services.ExportImport -{ - /// - /// Import manager - /// - public partial class ImportManager : IImportManager - { - private readonly IProductService _productService; - private readonly ICategoryService _categoryService; - private readonly IManufacturerService _manufacturerService; - private readonly IPictureService _pictureService; - private readonly IUrlRecordService _urlRecordService; - private readonly SeoSettings _seoSettings; - private readonly IEventPublisher _eventPublisher; - private readonly IRepository _rsProduct; - private readonly IRepository _rsProductCategory; - private readonly IRepository _rsProductManufacturer; - private readonly IRepository _rsPicture; - private readonly IRepository _rsProductPicture; - private readonly IRepository _rsUrlRecord; - private readonly ILanguageService _languageService; - private readonly ILocalizedEntityService _localizedEntityService; - - public ImportManager( - IProductService productService, - ICategoryService categoryService, - IManufacturerService manufacturerService, - IPictureService pictureService, - IUrlRecordService urlRecordService, - SeoSettings seoSettings, - IEventPublisher eventPublisher, - IRepository rsProduct, - IRepository rsProductCategory, - IRepository rsProductManufacturer, - IRepository rsPicture, - IRepository rsProductPicture, - IRepository rsUrlRecord, - ILanguageService languageService, - ILocalizedEntityService localizedEntityService) - { - this._productService = productService; - this._categoryService = categoryService; - this._manufacturerService = manufacturerService; - this._pictureService = pictureService; - this._urlRecordService = urlRecordService; - this._seoSettings = seoSettings; - this._eventPublisher = eventPublisher; - this._rsProduct = rsProduct; - this._rsProductCategory = rsProductCategory; - this._rsProductManufacturer = rsProductManufacturer; - this._rsProductPicture = rsProductPicture; - this._rsUrlRecord = rsUrlRecord; - this._rsPicture = rsPicture; - this._languageService = languageService; - this._localizedEntityService = localizedEntityService; - } - - public virtual string CreateTextReport(ImportResult result) - { - var sb = new StringBuilder(); - - using (var writer = new StringWriter(sb)) - { - writer.WriteLine("SUMMARY"); - writer.WriteLine("=================================================================================="); - writer.WriteLine("Started: {0}".FormatCurrent(result.StartDateUtc.ToLocalTime())); - writer.WriteLine("Finished: {0}{1}".FormatCurrent(result.EndDateUtc.ToLocalTime(), result.Cancelled ? " (cancelled by user)" : "")); - writer.WriteLine("Duration: {0}".FormatCurrent((result.EndDateUtc - result.StartDateUtc).ToString("g"))); - - writer.WriteLine(""); - writer.WriteLine("Total rows in source: {0}".FormatCurrent(result.TotalRecords)); - writer.WriteLine("Rows processed: {0}".FormatCurrent(result.AffectedRecords)); - writer.WriteLine("Products imported: {0}".FormatCurrent(result.NewRecords)); - writer.WriteLine("Products updated: {0}".FormatCurrent(result.ModifiedRecords)); - - writer.WriteLine(""); - writer.WriteLine("Warnings: {0}".FormatCurrent(result.Messages.Count(x => x.MessageType == ImportMessageType.Warning))); - writer.WriteLine("Errors: {0}".FormatCurrent(result.Messages.Count(x => x.MessageType == ImportMessageType.Error))); - - writer.WriteLine(""); - writer.WriteLine(""); - writer.WriteLine("MESSAGES"); - writer.WriteLine("=================================================================================="); - - foreach (var message in result.Messages) - { - string msg = string.Empty; - var prefix = new List(); - if (message.AffectedItem != null) - { - prefix.Add("Pos: " + message.AffectedItem.Position + 1); - } - if (message.AffectedField.HasValue()) - { - prefix.Add("Field: " + message.AffectedField); - } - - if (prefix.Any()) - { - msg = "[{0}] ".FormatCurrent(String.Join(", ", prefix)); - } - - msg += message.Message; - - writer.WriteLine("{0}: {1}".FormatCurrent(message.MessageType.ToString().ToUpper(), msg)); - } - } - - return sb.ToString(); - } - - /// - /// Import products from XLSX file - /// - /// Stream - public virtual async Task ImportProductsFromExcelAsync( - Stream stream, - CancellationToken cancellationToken, - IProgress progress = null) - { - Guard.ArgumentNotNull(() => stream); - - var t = await Task.Run(async () => { - - var result = new ImportResult(); - int saved = 0; - - if (progress != null) - progress.Report(new ImportProgressInfo { ElapsedTime = TimeSpan.Zero }); - - using (var scope = new DbContextScope(ctx: _rsProduct.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) - { - using (var segmenter = new DataSegmenter(stream)) - { - result.TotalRecords = segmenter.TotalRows; - - while (segmenter.ReadNextBatch() && !cancellationToken.IsCancellationRequested) - { - var batch = segmenter.CurrentBatch; - - // Perf: detach all entities - _rsProduct.Context.DetachAll(); - - // Update progress for calling thread - if (progress != null) - { - progress.Report(new ImportProgressInfo - { - TotalRecords = result.TotalRecords, - TotalProcessed = segmenter.CurrentSegmentFirstRowIndex - 1, - NewRecords = result.NewRecords, - ModifiedRecords = result.ModifiedRecords, - ElapsedTime = DateTime.UtcNow - result.StartDateUtc, - TotalWarnings = result.Messages.Count(x => x.MessageType == ImportMessageType.Warning), - TotalErrors = result.Messages.Count(x => x.MessageType == ImportMessageType.Error), - }); - } - - // =========================================================================== - // 1.) Import products - // =========================================================================== - try - { - saved = await ProcessProducts(batch, result); - } - catch (Exception ex) - { - 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).AsReadOnly(); - - // update result object - result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); - 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 (batch.Any(x => x.IsNew || (x.ContainsKey("SeName") || x.NameChanged))) - { - _rsProduct.Context.AutoDetectChangesEnabled = true; - await ProcessSlugs(batch, result); - _rsProduct.Context.AutoDetectChangesEnabled = false; - } - - // =========================================================================== - // 3.) Import Localizations - // =========================================================================== - try - { - await ProcessLocalizations(batch, result); - } - catch (Exception ex) - { - result.AddError(ex, segmenter.CurrentSegment, "ProcessLocalizations"); - } - - // =========================================================================== - // 4.) Import product category mappings - // =========================================================================== - if (batch.Any(x => x.ContainsKey("CategoryIds"))) - { - try - { - await ProcessProductCategories(batch, result); - } - catch (Exception ex) - { - result.AddError(ex, segmenter.CurrentSegment, "ProcessProductCategories"); - } - } - - // =========================================================================== - // 5.) Import product manufacturer mappings - // =========================================================================== - if (batch.Any(x => x.ContainsKey("ManufacturerIds"))) - { - try - { - await ProcessProductManufacturers(batch, result); - } - catch (Exception ex) - { - result.AddError(ex, segmenter.CurrentSegment, "ProcessProductManufacturers"); - } - } - - // =========================================================================== - // 6.) Import product picture mappings - // =========================================================================== - if (batch.Any(x => x.ContainsKey("Picture1") || x.ContainsKey("Picture2") || x.ContainsKey("Picture3"))) - { - try - { - ProcessProductPictures(batch, result); - } - catch (Exception ex) - { - result.AddError(ex, segmenter.CurrentSegment, "ProcessProductPictures"); - } - } - - } - } - } - - result.EndDateUtc = DateTime.UtcNow; - - if (cancellationToken.IsCancellationRequested) - { - result.Cancelled = true; - result.AddInfo("Import task was cancelled by user"); - } - - return result; - }); - - return t; - } - - private async Task ProcessProducts(ICollection> batch, ImportResult result) - { - _rsProduct.AutoCommitEnabled = false; - - Product lastInserted = null; - Product lastUpdated = null; - - foreach (var row in batch) - { - if (row.Count == 0) - continue; - - Product product = null; - - object key; - - // try get by int ID - if (row.TryGetValue("Id", out key) && key.ToString().ToInt() > 0) - { - product = _productService.GetProductById(key.ToString().ToInt()); - } - - // try get by SKU - if (product == null && row.TryGetValue("SKU", out key)) - { - product = _productService.GetProductBySku(key.ToString()); - } - - // try get by GTIN - if (product == null && row.TryGetValue("Gtin", out key)) - { - product = _productService.GetProductByGtin(key.ToString()); - } - - if (product == null) - { - // a Name is required with new products. - if (!row.ContainsKey("Name")) - { - result.AddError("The 'Name' field is required for new products. Skipping row.", row.GetRowInfo(), "Name"); - continue; - } - product = new Product(); - } - - row.Initialize(product, row["Name"].ToString()); - - if (!row.IsNew) - { - if (!product.Name.Equals(row["Name"].ToString(), StringComparison.OrdinalIgnoreCase)) - { - // Perf: use this later for SeName updates. - row.NameChanged = true; - } - } - - row.SetProperty(result, product, (x) => x.Sku); - row.SetProperty(result, product, (x) => x.Gtin); - row.SetProperty(result, product, (x) => x.ManufacturerPartNumber); - row.SetProperty(result, product, (x) => x.ProductTypeId, (int)ProductType.SimpleProduct); - row.SetProperty(result, product, (x) => x.ParentGroupedProductId); - row.SetProperty(result, product, (x) => x.VisibleIndividually, true); - row.SetProperty(result, product, (x) => x.Name); - row.SetProperty(result, product, (x) => x.ShortDescription); - row.SetProperty(result, product, (x) => x.FullDescription); - row.SetProperty(result, product, (x) => x.ProductTemplateId); - row.SetProperty(result, product, (x) => x.ShowOnHomePage); - row.SetProperty(result, product, (x) => x.MetaKeywords); - row.SetProperty(result, product, (x) => x.MetaDescription); - row.SetProperty(result, product, (x) => x.MetaTitle); - row.SetProperty(result, product, (x) => x.AllowCustomerReviews, true); - row.SetProperty(result, product, (x) => x.Published, true); - row.SetProperty(result, product, (x) => x.IsGiftCard); - row.SetProperty(result, product, (x) => x.GiftCardTypeId); - row.SetProperty(result, product, (x) => x.RequireOtherProducts); - row.SetProperty(result, product, (x) => x.RequiredProductIds); - row.SetProperty(result, product, (x) => x.AutomaticallyAddRequiredProducts); - row.SetProperty(result, product, (x) => x.IsDownload); - row.SetProperty(result, product, (x) => x.DownloadId); - row.SetProperty(result, product, (x) => x.UnlimitedDownloads, true); - row.SetProperty(result, product, (x) => x.MaxNumberOfDownloads, 10); - row.SetProperty(result, product, (x) => x.DownloadActivationTypeId, 1); - row.SetProperty(result, product, (x) => x.HasSampleDownload); - row.SetProperty(result, product, (x) => x.SampleDownloadId, (int?)null, ZeroToNull); - row.SetProperty(result, product, (x) => x.HasUserAgreement); - row.SetProperty(result, product, (x) => x.UserAgreementText); - row.SetProperty(result, product, (x) => x.IsRecurring); - row.SetProperty(result, product, (x) => x.RecurringCycleLength, 100); - row.SetProperty(result, product, (x) => x.RecurringCyclePeriodId); - row.SetProperty(result, product, (x) => x.RecurringTotalCycles, 10); - row.SetProperty(result, product, (x) => x.IsShipEnabled, true); - row.SetProperty(result, product, (x) => x.IsFreeShipping); - row.SetProperty(result, product, (x) => x.AdditionalShippingCharge); - row.SetProperty(result, product, (x) => x.IsEsd); - row.SetProperty(result, product, (x) => x.IsTaxExempt); - row.SetProperty(result, product, (x) => x.TaxCategoryId, 1); - row.SetProperty(result, product, (x) => x.ManageInventoryMethodId); - row.SetProperty(result, product, (x) => x.StockQuantity, 10000); - row.SetProperty(result, product, (x) => x.DisplayStockAvailability); - row.SetProperty(result, product, (x) => x.DisplayStockQuantity); - row.SetProperty(result, product, (x) => x.MinStockQuantity); - row.SetProperty(result, product, (x) => x.LowStockActivityId); - row.SetProperty(result, product, (x) => x.NotifyAdminForQuantityBelow, 1); - row.SetProperty(result, product, (x) => x.BackorderModeId); - row.SetProperty(result, product, (x) => x.AllowBackInStockSubscriptions); - row.SetProperty(result, product, (x) => x.OrderMinimumQuantity, 1); - row.SetProperty(result, product, (x) => x.OrderMaximumQuantity, 10000); - row.SetProperty(result, product, (x) => x.AllowedQuantities); - row.SetProperty(result, product, (x) => x.DisableBuyButton); - row.SetProperty(result, product, (x) => x.DisableWishlistButton); - row.SetProperty(result, product, (x) => x.AvailableForPreOrder); - row.SetProperty(result, product, (x) => x.CallForPrice); - row.SetProperty(result, product, (x) => x.Price); - row.SetProperty(result, product, (x) => x.OldPrice); - row.SetProperty(result, product, (x) => x.ProductCost); - row.SetProperty(result, product, (x) => x.SpecialPrice); - row.SetProperty(result, product, (x) => x.SpecialPriceStartDateTimeUtc, null, OADateToUtcDate); - row.SetProperty(result, product, (x) => x.SpecialPriceEndDateTimeUtc, null, OADateToUtcDate); - row.SetProperty(result, product, (x) => x.CustomerEntersPrice); - row.SetProperty(result, product, (x) => x.MinimumCustomerEnteredPrice); - row.SetProperty(result, product, (x) => x.MaximumCustomerEnteredPrice, 1000); - row.SetProperty(result, product, (x) => x.Weight); - row.SetProperty(result, product, (x) => x.Length); - row.SetProperty(result, product, (x) => x.Width); - row.SetProperty(result, product, (x) => x.Height); - row.SetProperty(result, product, (x) => x.DeliveryTimeId); - row.SetProperty(result, product, (x) => x.QuantityUnitId); - row.SetProperty(result, product, (x) => x.BasePriceEnabled); - row.SetProperty(result, product, (x) => x.BasePriceMeasureUnit); - row.SetProperty(result, product, (x) => x.BasePriceAmount); - row.SetProperty(result, product, (x) => x.BasePriceBaseAmount); - row.SetProperty(result, product, (x) => x.BundlePerItemPricing); - row.SetProperty(result, product, (x) => x.BundlePerItemShipping); - row.SetProperty(result, product, (x) => x.BundlePerItemShoppingCart); - row.SetProperty(result, product, (x) => x.BundleTitleText); - row.SetProperty(result, product, (x) => x.CreatedOnUtc, DateTime.UtcNow, OADateToUtcDate); - - product.UpdatedOnUtc = DateTime.UtcNow; - - if (row.IsTransient) - { - _rsProduct.Insert(product); - lastInserted = product; - } - else - { - _rsProduct.Update(product); - lastUpdated = product; - } - } - - // commit whole batch at once - var t = await _rsProduct.Context.SaveChangesAsync(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _eventPublisher.EntityInserted(lastInserted); - if (lastUpdated != null) - _eventPublisher.EntityUpdated(lastUpdated); - - //// ensure all products got imported before processing other stuff. - //t.Wait(); - - return t; - } - - private async Task ProcessSlugs(ICollection> batch, ImportResult result) - { - _rsUrlRecord.AutoCommitEnabled = false; - - var slugMap = new Dictionary(100); - Func slugLookup = ((s) => { - if (slugMap.ContainsKey(s)) - { - return slugMap[s]; - } - return (UrlRecord)null; - }); - - var entityName = typeof(Product).Name; - - foreach (var row in batch) - { - if (row.IsNew || row.NameChanged || row.ContainsKey("SeName")) - { - try - { - string seName = row.GetValue("SeName"); - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); - - UrlRecord urlRecord = null; - - if (row.IsNew) - { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord() - { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _rsUrlRecord.Insert(urlRecord); - } - else - { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } - - if (urlRecord != null) - { - // a new record was inserted to the store: keep track of it for this batch. - slugMap[seName] = urlRecord; - } - } - catch (Exception ex) - { - result.AddWarning(ex.Message, row.GetRowInfo(), "SeName"); - } - } - } - - // commit whole batch at once - var t = await _rsUrlRecord.Context.SaveChangesAsync(); - - return t; - } - - private async Task ProcessLocalizations(ICollection> batch, ImportResult result) - { - //_rsProductManufacturer.AutoCommitEnabled = false; - - //string lastInserted = null; - - var languages = _languageService.GetAllLanguages(true); - - foreach (var row in batch) - { - - Product product = null; - - //get product - try - { - product = _productService.GetProductById(row.Entity.Id); - } - catch (Exception ex) - { - result.AddWarning(ex.Message, row.GetRowInfo(), "ProcessLocalizations Product"); - } - - foreach (var lang in languages) - { - string localizedName = row.GetValue("Name[" + lang.UniqueSeoCode + "]"); - string localizedShortDescription = row.GetValue("ShortDescription[" + lang.UniqueSeoCode + "]"); - string localizedFullDescription = row.GetValue("FullDescription[" + lang.UniqueSeoCode + "]"); - - if (localizedName.HasValue()) - { - _localizedEntityService.SaveLocalizedValue(product, x => x.Name, localizedName, lang.Id); - } - if (localizedShortDescription.HasValue()) - { - _localizedEntityService.SaveLocalizedValue(product, x => x.ShortDescription, localizedShortDescription, lang.Id); - } - if (localizedFullDescription.HasValue()) - { - _localizedEntityService.SaveLocalizedValue(product, x => x.FullDescription, localizedFullDescription, lang.Id); - } - } - } - - // commit whole batch at once - var t = await _rsProductManufacturer.Context.SaveChangesAsync(); - - // Perf: notify only about LAST insertion and update - //if (lastInserted != null) - // _eventPublisher.EntityInserted(lastInserted); - - return t; - } - - - private async Task ProcessProductCategories(ICollection> batch, ImportResult result) - { - _rsProductCategory.AutoCommitEnabled = false; - - ProductCategory lastInserted = null; - - foreach (var row in batch) - { - string categoryIds = row.GetValue("CategoryIds"); - if (categoryIds.HasValue()) - { - try - { - foreach (var id in categoryIds.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToInt32(x.Trim()))) - { - if (_rsProductCategory.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.CategoryId == id).FirstOrDefault() == null) - { - // ensure that category exists - var category = _categoryService.GetCategoryById(id); - if (category != null) - { - var productCategory = new ProductCategory() - { - ProductId = row.Entity.Id, - CategoryId = category.Id, - IsFeaturedProduct = false, - DisplayOrder = 1 - }; - _rsProductCategory.Insert(productCategory); - lastInserted = productCategory; - } - } - } - } - catch (Exception ex) - { - result.AddWarning(ex.Message, row.GetRowInfo(), "CategoryIds"); - } - } - } - - // commit whole batch at once - var t = await _rsProductCategory.Context.SaveChangesAsync(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _eventPublisher.EntityInserted(lastInserted); - - return t; - } - - private async Task ProcessProductManufacturers(ICollection> batch, ImportResult result) - { - _rsProductManufacturer.AutoCommitEnabled = false; - - ProductManufacturer lastInserted = null; - - foreach (var row in batch) - { - string manufacturerIds = row.GetValue("ManufacturerIds"); - if (manufacturerIds.HasValue()) - { - try - { - foreach (var id in manufacturerIds.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToInt32(x.Trim()))) - { - if (_rsProductManufacturer.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.ManufacturerId == id).FirstOrDefault() == null) - { - // ensure that manufacturer exists - var manufacturer = _manufacturerService.GetManufacturerById(id); - if (manufacturer != null) - { - var productManufacturer = new ProductManufacturer() - { - ProductId = row.Entity.Id, - ManufacturerId = manufacturer.Id, - IsFeaturedProduct = false, - DisplayOrder = 1 - }; - _rsProductManufacturer.Insert(productManufacturer); - lastInserted = productManufacturer; - } - } - } - } - catch (Exception ex) - { - result.AddWarning(ex.Message, row.GetRowInfo(), "ManufacturerIds"); - } - } - } - - // commit whole batch at once - var t = await _rsProductManufacturer.Context.SaveChangesAsync(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _eventPublisher.EntityInserted(lastInserted); - - return t; - } - - private void ProcessProductPictures(ICollection> batch, ImportResult result) - { - // true, cause pictures must be saved and assigned an id - // prior adding a mapping. - _rsProductPicture.AutoCommitEnabled = true; - - ProductPicture lastInserted = null; - - foreach (var row in batch) - { - var pictures = new string[] - { - row.GetValue("Picture1"), - row.GetValue("Picture2"), - row.GetValue("Picture3") - }; - - int i = 0; - try - { - for (i = 0; i < pictures.Length; i++) - { - var picture = pictures[i]; - - if (picture.IsEmpty() || !File.Exists(picture)) - continue; - - var currentPictures = _rsProductPicture.Expand(_rsProductPicture.TableUntracked, x => x.Picture).Where(x => x.ProductId == row.Entity.Id).Select(x => x.Picture).ToList(); - var pictureBinary = FindEqualPicture(picture, currentPictures); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - // no equal picture found in sequence - var newPicture = _pictureService.InsertPicture(pictureBinary, "image/jpeg", _pictureService.GetPictureSeName(row.EntityDisplayName), true, true); - if (newPicture != null) - { - var mapping = new ProductPicture() - { - ProductId = row.Entity.Id, - PictureId = newPicture.Id, - DisplayOrder = 1, - }; - _rsProductPicture.Insert(mapping); - lastInserted = mapping; - } - } - else - { - result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "Picture" + (i + 1).ToString()); - } - } - } - catch (Exception ex) - { - result.AddWarning(ex.Message, row.GetRowInfo(), "Picture" + (i + 1).ToString()); - } - - } - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _eventPublisher.EntityInserted(lastInserted); - } - - private DateTime? OADateToUtcDate(object value) - { - double oaDate; - if (CommonHelper.TryConvert(value, out oaDate) && oaDate != 0) - { - return DateTime.FromOADate(Convert.ToDouble(oaDate)); - } - - return null; - } - - - private int? ZeroToNull(object value) - { - int result; - if (CommonHelper.TryConvert(value, out result) && result > 0) - { - return result; - } - - return (int?)null; - } - - /// - /// Finds an equal picture by comparing the binary buffer - /// - /// The picture to find a duplicate for - /// The sequence of product pictures to seek within for duplicates - /// The picture binary for path when no picture equals in the sequence, null otherwise. - private byte[] FindEqualPicture(string path, IEnumerable productPictures) - { - try - { - var myBuffer = File.ReadAllBytes(path); - - foreach (var picture in productPictures) - { - var otherBuffer = _pictureService.LoadPictureBinary(picture); - using (var myStream = new MemoryStream(myBuffer)) - { - using (var otherStream = new MemoryStream(otherBuffer)) - { - var equals = myStream.ContentsEqual(otherStream); - if (equals) - { - return null; - } - } - } - } - - return myBuffer; - } - catch - { - return null; - } - } - } -} diff --git a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs index 575c2948c8..216894b2d2 100644 --- a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs +++ b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs @@ -13,53 +13,85 @@ 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}".FormatWith(productAttributeId, attributeId); + return "product_attribute_{0}_{1}".FormatInvariant(productAttributeId, attributeId); else - return "product_attribute_{0}_{1}_{2}_{3}".FormatWith(productId, bundleItemId, productAttributeId, attributeId); + 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) { - string name = AttributeFormatedName(productAttributeId, attributeId, productId, bundleItemId); + var name = AttributeFormatedName(productAttributeId, attributeId, productId, bundleItemId); collection.Add(name, valueId.ToString()); } } /// - /// Converts attribute query data + /// Get selected attributes from query string /// - /// Name value collection - /// Attribute query data items with following structure: Product.Id, ProductAttribute.Id, Product_ProductAttribute_Mapping.Id, ProductVariantAttributeValue.Id + /// 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 ConvertAttributeQueryData(this NameValueCollection collection, List> queryData, int productId = 0) + public static void GetSelectedAttributes(this NameValueCollection collection, NameValueCollection queryString, List> attributes, int productId = 0) { - if (collection == null || queryData == null || queryData.Count <= 0) - return; + Guard.NotNull(() => collection); - var enm = queryData.Where(i => i.Count > 3); + // 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() }); - if (productId != 0) - enm = enm.Where(i => i[0] == productId); + 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); + } + } + } + } - foreach (var itm in enm) + if (attributes != null && attributes.Count > 0) { - string name = AttributeFormatedName(itm[1], itm[2], itm[0]); + var items = attributes.Where(i => i.Count > 3); + + if (productId != 0) + items = items.Where(i => i[0] == productId); - collection.Add(name, itm[3].ToString()); + 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, IList variantAttributes, - IProductAttributeParser productAttributeParser, ILocalizationService localizationService, IDownloadService downloadService, CatalogSettings catalogSettings, - HttpRequestBase request, List warnings, bool formatWithProductId = true, int bundleItemId = 0) + 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 ""; @@ -80,7 +112,7 @@ public static string CreateSelectedAttributesXml(this NameValueCollection collec var ctrlAttributes = collection[controlId]; if (ctrlAttributes.HasValue()) { - int selectedAttributeId = int.Parse(ctrlAttributes); + var selectedAttributeId = ctrlAttributes.SplitSafe(",").SafeGet(0).ToInt(); if (selectedAttributeId > 0) selectedAttributes = productAttributeParser.AddProductAttribute(selectedAttributes, attribute, selectedAttributeId.ToString()); } @@ -94,7 +126,7 @@ public static string CreateSelectedAttributesXml(this NameValueCollection collec { foreach (var item in cblAttributes.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { - int selectedAttributeId = int.Parse(item); + var selectedAttributeId = item.SplitSafe(",").SafeGet(0).ToInt(); if (selectedAttributeId > 0) selectedAttributes = productAttributeParser.AddProductAttribute(selectedAttributes, attribute, selectedAttributeId.ToString()); } @@ -142,31 +174,33 @@ public static string CreateSelectedAttributesXml(this NameValueCollection collec var download = downloadService.GetDownloadByGuid(downloadGuid); if (download != null) { + download.IsTransient = false; + downloadService.UpdateDownload(download); selectedAttributes = productAttributeParser.AddProductAttribute(selectedAttributes, attribute, download.DownloadGuid.ToString()); } } else { - var httpPostedFile = request.Files[controlId]; - if (httpPostedFile != null && httpPostedFile.FileName.HasValue()) + var postedFile = request.Files[controlId]; + if (postedFile != null && postedFile.FileName.HasValue()) { int fileMaxSize = catalogSettings.FileUploadMaximumSizeBytes; - if (httpPostedFile.ContentLength > fileMaxSize) + if (postedFile.ContentLength > fileMaxSize) { warnings.Add(string.Format(localizationService.GetResource("ShoppingCart.MaximumUploadedFileSize"), (int)(fileMaxSize / 1024))); } else { //save an uploaded file - var download = new Download() + var download = new Download { DownloadGuid = Guid.NewGuid(), UseDownloadUrl = false, DownloadUrl = "", - DownloadBinary = httpPostedFile.GetDownloadBits(), - ContentType = httpPostedFile.ContentType, - Filename = System.IO.Path.GetFileNameWithoutExtension(httpPostedFile.FileName), - Extension = System.IO.Path.GetExtension(httpPostedFile.FileName), + 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); diff --git a/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs b/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs index 59de0fa9c1..6bbd9756f9 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs @@ -33,10 +33,14 @@ public class FilterCriteria : IComparable [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 int ParentId { get; set; } public string NameLocalized { get; set; } public string ValueLocalized { get; set; } @@ -44,11 +48,13 @@ public string SqlName { get { - if (Entity == "Manufacturer" && !Name.Contains('.')) - return "{0}.{1}".FormatWith(Entity, Name); + if (Entity.IsCaseInsensitiveEqual("Manufacturer") && !Name.Contains('.')) + return "{0}.{1}".FormatInvariant(Entity, Name); + return Name; } } + public bool IsRange { get @@ -59,9 +65,9 @@ public bool IsRange int IComparable.CompareTo(object obj) { - FilterCriteria filter = (FilterCriteria)obj; + var filter = (FilterCriteria)obj; - int compare = string.Compare(this.Entity, filter.Entity, true); + var compare = string.Compare(this.Entity, filter.Entity, true); if (compare == 0) { @@ -72,20 +78,8 @@ int IComparable.CompareTo(object obj) } return compare; - - - //int compare = 0; - - //if (this.Name.HasValue() && filter.Name.HasValue()) - // compare = string.Compare(this.Name, filter.Name, true); - //else - // compare = string.Compare(this.Entity, filter.Entity, true); - - //if (compare != 0) - // return compare; - - //return string.Compare(this.Value, filter.Value, true); } + public override string ToString() { try diff --git a/src/Libraries/SmartStore.Services/Filter/FilterExtensions.cs b/src/Libraries/SmartStore.Services/Filter/FilterExtensions.cs index a466c8adeb..56f0655f26 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterExtensions.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterExtensions.cs @@ -17,6 +17,7 @@ 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); } @@ -35,33 +36,33 @@ public static string ToDescription(this FilterCriteria criteria) string valueLeft, valueRight; criteria.Value.SplitToPair(out valueLeft, out valueRight, "~"); - if (criteria.Entity == FilterService.ShortcutPrice) - return "{0} - {1}".FormatWith(FormatPrice(valueLeft), FormatPrice(valueRight)); + if (criteria.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutPrice)) + return "{0} - {1}".FormatInvariant(FormatPrice(valueLeft), FormatPrice(valueRight)); - return "{0} - {1}".FormatWith(valueLeft, valueRight); + return "{0} - {1}".FormatInvariant(valueLeft, valueRight); } - string value = (criteria.ValueLocalized.HasValue() ? criteria.ValueLocalized : criteria.Value); + var value = (criteria.ValueLocalized.HasValue() ? criteria.ValueLocalized : criteria.Value); - if (criteria.Entity == FilterService.ShortcutPrice) + if (criteria.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutPrice)) value = FormatPrice(criteria.Value); if (criteria.Operator == FilterOperator.Unequal) - return "≠ {0}".FormatWith(value); + return "≠ {0}".FormatInvariant(value); if (criteria.Operator == FilterOperator.Greater) - return "> {0}".FormatWith(value); + return "> {0}".FormatInvariant(value); if (criteria.Operator == FilterOperator.GreaterEqual) - return "≥ {0}".FormatWith(value); + return "≥ {0}".FormatInvariant(value); if (criteria.Operator == FilterOperator.Less) - return "< {0}".FormatWith(value); + return "< {0}".FormatInvariant(value); if (criteria.Operator == FilterOperator.LessEqual) - return "≤ {0}".FormatWith(value); + return "≤ {0}".FormatInvariant(value); if (criteria.Operator == FilterOperator.Contains) - return "{0} {1}".FormatWith(localize.GetResource("Products.Filter.Contains"), value); + return "{0} {1}".FormatInvariant(localize.GetResource("Products.Filter.Contains"), value); if (criteria.Operator == FilterOperator.StartsWith) - return "{0} {1}".FormatWith(localize.GetResource("Products.Filter.StartsWith"), value); + return "{0} {1}".FormatInvariant(localize.GetResource("Products.Filter.StartsWith"), value); if (criteria.Operator == FilterOperator.EndsWith) - return "{0} {1}".FormatWith(localize.GetResource("Products.Filter.EndsWith"), value); + return "{0} {1}".FormatInvariant(localize.GetResource("Products.Filter.EndsWith"), value); return value; } @@ -132,7 +133,8 @@ public static bool StringToPrice(this string[] range, int index, out decimal res result = 0; if (range != null && index < range.Length) { - string value = range[index].Trim(); + var value = range[index].Trim(); + return StringToPrice(value, out result); } return false; @@ -140,11 +142,11 @@ public static bool StringToPrice(this string[] range, int index, out decimal res public static string GetUrl(this FilterProductContext context, FilterCriteria criteriaAdd = null, FilterCriteria criteriaRemove = null) { - string url = "{0}?pagesize={1}&viewmode={2}".FormatWith(context.Path, context.PageSize, context.ViewMode); + var url = "{0}?pagesize={1}&viewmode={2}".FormatInvariant(context.Path, context.PageSize, context.ViewMode); if (context.OrderBy.HasValue) { - url = "{0}&orderby={1}".FormatWith(url, context.OrderBy.Value); + url = "{0}&orderby={1}".FormatInvariant(url, context.OrderBy.Value); } try @@ -159,10 +161,10 @@ public static string GetUrl(this FilterProductContext context, FilterCriteria cr if (criteriaAdd != null) criterias.Add(criteriaAdd); else - criterias.RemoveAll(c => c.Entity == criteriaRemove.Entity && c.Name == criteriaRemove.Name && c.Value == criteriaRemove.Value); + 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}".FormatWith(url, HttpUtility.UrlEncode(JsonConvert.SerializeObject(criterias))); + url = "{0}&filter={1}".FormatInvariant(url, HttpUtility.UrlEncode(JsonConvert.SerializeObject(criterias))); } } catch (Exception exc) @@ -177,7 +179,10 @@ public static bool IsActive(this FilterProductContext context, FilterCriteria cr { if (criteria != null && context.Criteria != null) { - return (context.Criteria.FirstOrDefault(c => c.Entity == criteria.Entity && c.Name == criteria.Name && c.Value == criteria.Value && !c.IsInactive) != 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/FilterOperator.cs b/src/Libraries/SmartStore.Services/Filter/FilterOperator.cs index 0e5dc79bf1..ed49bed2b5 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterOperator.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterOperator.cs @@ -1,13 +1,8 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Newtonsoft.Json; namespace SmartStore.Services.Filter { - /// codehint: sm-add public sealed class FilterOperator { private const string _equal = "="; @@ -24,7 +19,8 @@ public sealed class FilterOperator private readonly string _name; - private FilterOperator(string name) { + private FilterOperator(string name) + { this._name = name; } @@ -40,11 +36,14 @@ private FilterOperator(string name) { public static readonly FilterOperator RangeGreaterEqualLessEqual = new FilterOperator(_rangeGreaterEqualLessEqual); public static readonly FilterOperator RangeGreaterEqualLess = new FilterOperator(_rangeGreaterEqualLess); - public override string ToString() { + public override string ToString() + { return _name ?? "="; } - public static FilterOperator Parse(string op) { - switch (op) { + public static FilterOperator Parse(string op) + { + switch (op) + { case _equal: return FilterOperator.Equal; case _unequal: @@ -57,38 +56,46 @@ public static FilterOperator Parse(string op) { return FilterOperator.Less; case _lessEqual: return FilterOperator.LessEqual; - case _contains: - return FilterOperator.Contains; - case _startWith: - return FilterOperator.StartsWith; - case _endsWith: - return FilterOperator.EndsWith; 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; } } - } // class + } public class OperatorConverter : JsonConverter { - public override bool CanConvert(Type objectType) { + public override bool CanConvert(Type objectType) + { return objectType == typeof(FilterOperator); } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + 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) { + 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; } - } // class + } } diff --git a/src/Libraries/SmartStore.Services/Filter/FilterService.cs b/src/Libraries/SmartStore.Services/Filter/FilterService.cs index 66eaf99627..507b1f2fed 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterService.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterService.cs @@ -5,12 +5,12 @@ 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; -using SmartStore.Utilities; namespace SmartStore.Services.Filter { @@ -24,7 +24,7 @@ public partial class FilterService : IFilterService private readonly IRepository _productRepository; private readonly IRepository _productCategoryRepository; private readonly ILocalizedEntityService _localizedEntityService; - private readonly ICommonServices _commonServices; + private readonly ICommonServices _services; private IQueryable _products; @@ -34,7 +34,7 @@ public FilterService(IProductService productService, IRepository productRepository, IRepository productCategoryRepository, ILocalizedEntityService localizedEntityService, - ICommonServices commonServices) + ICommonServices services) { _productService = productService; _categoryService = categoryService; @@ -42,7 +42,7 @@ public FilterService(IProductService productService, _productRepository = productRepository; _productCategoryRepository = productCategoryRepository; _localizedEntityService = localizedEntityService; - _commonServices = commonServices; + _services = services; } public static string ShortcutPrice { get { return "_Price"; } } @@ -53,6 +53,7 @@ private string ValidateValue(string value, string alternativeValue) { if (value.HasValue() && !value.IsCaseInsensitiveEqual("null")) return value; + return alternativeValue; } @@ -78,16 +79,16 @@ private object FilterValueToObject(string value, string type) //if (curlyBracketFormatting) // return value.FormatWith("\"{0}\"", value.Replace("\"", "\"\"")); - Type t = Type.GetType("System.{0}".FormatWith(ValidateValue(type, _defaultType))); + Type t = Type.GetType("System.{0}".FormatInvariant(ValidateValue(type, _defaultType))); - var result = CommonHelper.GetTypeConverter(t).ConvertFromString(null, CultureInfo.InvariantCulture, value); + var result = TypeConverterFactory.GetConverter(t).ConvertFrom(CultureInfo.InvariantCulture, value); return result; } private bool IsShortcut(FilterSql context, FilterCriteria itm, ref int index) { - if (itm.Entity == ShortcutPrice) + if (itm.Entity.IsCaseInsensitiveEqual(ShortcutPrice)) { // TODO: where clause of special price not correct. product can appear in price and in special price range. @@ -118,7 +119,7 @@ private bool IsShortcut(FilterSql context, FilterCriteria itm, ref int index) context.Values.Add(DateTime.UtcNow); } } - else if (itm.Entity == ShortcutSpecAttribute) + else if (itm.Entity.IsCaseInsensitiveEqual(ShortcutSpecAttribute)) { context.WhereClause.AppendFormat("SpecificationAttributeOptionId {0} {1}", itm.Operator == null ? "=" : itm.Operator.ToString(), FormatParameterIndex(ref index)); @@ -163,10 +164,11 @@ private IQueryable AllProducts(List categoryIds) { if (_products == null) { - var searchContext = new ProductSearchContext() + var searchContext = new ProductSearchContext { + Query = _productRepository.TableUntracked, FeaturedProducts = (_catalogSettings.IncludeFeaturedProductsInNormalLists ? null : (bool?)false), - StoreId = _commonServices.StoreContext.CurrentStoreIdIfMultiStoreMode, + StoreId = _services.StoreContext.CurrentStoreIdIfMultiStoreMode, VisibleIndividuallyOnly = true }; @@ -191,17 +193,6 @@ join x in distinctIds on p.Id equals x _products = _productService.PrepareProductSearchQuery(searchContext); } - - //string.Join(", ", distinctIds.ToList()).Dump(); - - //_products - // .Select(x => new { x.Id, x.Name }) - // .ToList() - // .ForEach(x => { - // "{0} {1}".FormatWith(x.Id, x.Name).Dump(); - // }); - - //_products.ToString().Dump(true); } return _products; } @@ -262,19 +253,28 @@ from pm in p.ProductManufacturers var grouped = from m in manus - orderby m.DisplayOrder group m by m.Id into grp orderby grp.Key select new FilterCriteria { MatchCount = grp.Count(), - Value = grp.FirstOrDefault().Name + Value = grp.FirstOrDefault().Name, + DisplayOrder = grp.FirstOrDefault().DisplayOrder }; - grouped = grouped.OrderByDescending(m => m.MatchCount); + 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(); @@ -290,17 +290,20 @@ orderby grp.Key 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 - orderby sa.DisplayOrder select sa.SpecificationAttributeOption; if (attributeName.HasValue()) + { attributes = attributes.Where(a => a.SpecificationAttribute.Name == attributeName); + } var grouped = from a in attributes @@ -310,27 +313,55 @@ from a in attributes Name = g.FirstOrDefault().SpecificationAttribute.Name, Value = g.FirstOrDefault().Name, ID = g.Key.Id, - ParentId = g.FirstOrDefault().SpecificationAttribute.Id, - MatchCount = g.Count() + 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(); + } - var lst = grouped.OrderByDescending(a => a.MatchCount).ToList(); - int languageId = _commonServices.WorkContext.WorkingLanguage.Id; - - lst.ForEach(c => + criterias.ForEach(c => { c.Entity = ShortcutSpecAttribute; c.IsInactive = true; - if (c.ParentId != 0) - c.NameLocalized = _localizedEntityService.GetLocalizedValue(languageId, c.ParentId, "SpecificationAttribute", "Name"); + 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 lst; + return criterias; + } + + private void AddChildCategoryIds(List result, int categoryId) + { + var ids = _categoryService.GetAllCategoriesByParentCategoryId(categoryId).Select(x => x.Id); + + foreach (var id in ids) + { + if (!result.Contains(id)) + { + result.Add(id); + AddChildCategoryIds(result, id); + } + } } public virtual List Deserialize(string jsonData) @@ -352,12 +383,13 @@ 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() + var context = new FilterProductContext { Filter = filter, ParentCategoryID = categoryID, @@ -371,9 +403,18 @@ public virtual FilterProductContext CreateFilterProductContext(string filter, in if (_catalogSettings.ShowProductsFromSubcategories) { - context.CategoryIds.AddRange( - _categoryService.GetAllCategoriesByParentCategoryId(categoryID).Select(x => x.Id) - ); + AddChildCategoryIds(context.CategoryIds, categoryID); + } + + 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; @@ -458,6 +499,7 @@ public virtual bool ToWhereClause(FilterSql context, List findIn context.Criteria.Clear(); // ! context.Criteria = findIn.FindAll(match); + return ToWhereClause(context); } @@ -467,13 +509,13 @@ public virtual IQueryable ProductFilter(FilterProductContext context) var query = AllProducts(context.CategoryIds); // prices - if (ToWhereClause(sql, context.Criteria, c => !c.IsInactive && c.Entity == ShortcutPrice)) + 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 == "Manufacturer")) + if (ToWhereClause(sql, context.Criteria, c => !c.IsInactive && c.Entity.IsCaseInsensitiveEqual("Manufacturer"))) { var pmq = from p in query @@ -487,7 +529,7 @@ from pm in p.ProductManufacturers } // specification attribute - if (ToWhereClause(sql, context.Criteria, c => !c.IsInactive && (c.Entity == "SpecificationAttributeOption" || c.Entity == ShortcutSpecAttribute))) + if (ToWhereClause(sql, context.Criteria, c => !c.IsInactive && (c.Entity.IsCaseInsensitiveEqual("SpecificationAttributeOption") || c.Entity.IsCaseInsensitiveEqual(ShortcutSpecAttribute)))) { //var saq = ( // from p in query @@ -497,7 +539,7 @@ from pm in p.ProductManufacturers //query = saq.Select(sa => sa.Product); int countSameNameAttributes = sql.Criteria - .Where(c => c.Entity == ShortcutSpecAttribute) + .Where(c => c.Entity.IsCaseInsensitiveEqual(ShortcutSpecAttribute)) .GroupBy(c => c.Name) .Count(); @@ -552,10 +594,10 @@ join sa in saq on p.Id equals sa.Key public virtual void ProductFilterable(FilterProductContext context) { - if (context.Criteria.FirstOrDefault(c => c.Entity == FilterService.ShortcutPrice) == null) + if (context.Criteria.FirstOrDefault(c => c.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutPrice)) == null) context.Criteria.AddRange(ProductFilterablePrices(context)); - if (context.Criteria.FirstOrDefault(c => c.Name == "Name" && c.Entity == "Manufacturer") == null) + if (context.Criteria.FirstOrDefault(c => c.Name.IsCaseInsensitiveEqual("Name") && c.Entity.IsCaseInsensitiveEqual("Manufacturer")) == null) context.Criteria.AddRange(ProductFilterableManufacturer(context)); context.Criteria.AddRange(ProductFilterableSpecAttributes(context)); @@ -568,18 +610,18 @@ public virtual void ProductFilterableMultiSelect(FilterProductContext context, s if (criteriaMultiSelect != null) { - context.Criteria.RemoveAll(c => c.Name == criteriaMultiSelect.Name && c.Entity == criteriaMultiSelect.Entity); + context.Criteria.RemoveAll(c => c.Name.IsCaseInsensitiveEqual(criteriaMultiSelect.Name) && c.Entity.IsCaseInsensitiveEqual(criteriaMultiSelect.Entity)); - if (criteriaMultiSelect.Name == "Name" && criteriaMultiSelect.Entity == "Manufacturer") + if (criteriaMultiSelect.Name.IsCaseInsensitiveEqual("Name") && criteriaMultiSelect.Entity.IsCaseInsensitiveEqual("Manufacturer")) inactive = ProductFilterableManufacturer(context, true); - else if (criteriaMultiSelect.Entity == FilterService.ShortcutPrice) + else if (criteriaMultiSelect.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutPrice)) inactive = ProductFilterablePrices(context); - else if (criteriaMultiSelect.Entity == FilterService.ShortcutSpecAttribute) + else if (criteriaMultiSelect.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutSpecAttribute)) inactive = ProductFilterableSpecAttributes(context, criteriaMultiSelect.Name); } // filters WITHOUT the multiple selectable filters - string excludedFilter = Serialize(context.Criteria); + var excludedFilter = Serialize(context.Criteria); // filters WITH the multiple selectable filters (required for highlighting selected values) context.Criteria = Deserialize(context.Filter); diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index 6fed24aff9..5871f34ab6 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -6,6 +6,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; using SmartStore.Services.Common; using SmartStore.Services.Customers; @@ -19,13 +20,16 @@ namespace SmartStore.Services.Forums public partial class ForumService : IForumService { #region Constants - private const string FORUMGROUP_ALL_KEY = "SmartStore.forumgroup.all"; + + 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 + + #endregion #region Fields + private readonly IRepository _forumGroupRepository; private readonly IRepository _forumRepository; private readonly IRepository _forumTopicRepository; @@ -37,31 +41,14 @@ public partial class ForumService : IForumService private readonly ICacheManager _cacheManager; private readonly IGenericAttributeService _genericAttributeService; private readonly ICustomerService _customerService; - private readonly IWorkContext _workContext; private readonly IWorkflowMessageService _workflowMessageService; - private readonly IEventPublisher _eventPublisher; + private readonly IRepository _storeMappingRepository; + private readonly ICommonServices _services; #endregion #region Ctor - /// - /// Ctor - /// - /// Cache manager - /// Forum group repository - /// Forum repository - /// Forum topic repository - /// Forum post repository - /// Private message repository - /// Forum subscription repository - /// Forum settings - /// Customer repository - /// Generic attribute service - /// Customer service - /// Work context - /// Workflow message service - /// Event published public ForumService(ICacheManager cacheManager, IRepository forumGroupRepository, IRepository forumRepository, @@ -73,27 +60,29 @@ public ForumService(ICacheManager cacheManager, IRepository customerRepository, IGenericAttributeService genericAttributeService, ICustomerService customerService, - IWorkContext workContext, IWorkflowMessageService workflowMessageService, - IEventPublisher eventPublisher - ) + IRepository storeMappingRepository, + ICommonServices services) { - this._cacheManager = cacheManager; - this._forumGroupRepository = forumGroupRepository; - this._forumRepository = forumRepository; - this._forumTopicRepository = forumTopicRepository; - this._forumPostRepository = forumPostRepository; - this._forumPrivateMessageRepository = forumPrivateMessageRepository; - this._forumSubscriptionRepository = forumSubscriptionRepository; - this._forumSettings = forumSettings; - this._customerRepository = customerRepository; - this._genericAttributeService = genericAttributeService; - this._customerService = customerService; - this._workContext = workContext; - this._workflowMessageService = workflowMessageService; - _eventPublisher = eventPublisher; + _cacheManager = cacheManager; + _forumGroupRepository = forumGroupRepository; + _forumRepository = forumRepository; + _forumTopicRepository = forumTopicRepository; + _forumPostRepository = forumPostRepository; + _forumPrivateMessageRepository = forumPrivateMessageRepository; + _forumSubscriptionRepository = forumSubscriptionRepository; + _forumSettings = forumSettings; + _customerRepository = customerRepository; + _genericAttributeService = genericAttributeService; + _customerService = customerService; + _workflowMessageService = workflowMessageService; + _storeMappingRepository = storeMappingRepository; + _services = services; } - #endregion + + public DbQuerySettings QuerySettings { get; set; } + + #endregion #region Utilities @@ -268,7 +257,7 @@ public virtual void DeleteForumGroup(ForumGroup forumGroup) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityDeleted(forumGroup); + _services.EventPublisher.EntityDeleted(forumGroup); } /// @@ -288,14 +277,33 @@ public virtual ForumGroup GetForumGroupById(int forumGroupId) /// Gets all forum groups /// /// Forum groups - public virtual IList GetAllForumGroups() + public virtual IList GetAllForumGroups(bool showHidden = false) { - string key = string.Format(FORUMGROUP_ALL_KEY); + string key = string.Format(FORUMGROUP_ALL_KEY, showHidden); return _cacheManager.Get(key, () => { - var query = from fg in _forumGroupRepository.Table - orderby fg.DisplayOrder - select fg; + 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(); }); } @@ -318,7 +326,7 @@ public virtual void InsertForumGroup(ForumGroup forumGroup) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityInserted(forumGroup); + _services.EventPublisher.EntityInserted(forumGroup); } /// @@ -339,7 +347,7 @@ public virtual void UpdateForumGroup(ForumGroup forumGroup) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityUpdated(forumGroup); + _services.EventPublisher.EntityUpdated(forumGroup); } /// @@ -364,7 +372,7 @@ where queryTopicIds.Contains(fs.TopicId) { _forumSubscriptionRepository.Delete(fs); //event notification - _eventPublisher.EntityDeleted(fs); + _services.EventPublisher.EntityDeleted(fs); } //delete forum subscriptions (forum) @@ -375,7 +383,7 @@ where queryTopicIds.Contains(fs.TopicId) { _forumSubscriptionRepository.Delete(fs2); //event notification - _eventPublisher.EntityDeleted(fs2); + _services.EventPublisher.EntityDeleted(fs2); } //delete forum @@ -385,7 +393,7 @@ where queryTopicIds.Contains(fs.TopicId) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityDeleted(forum); + _services.EventPublisher.EntityDeleted(forum); } /// @@ -437,7 +445,7 @@ public virtual void InsertForum(Forum forum) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityInserted(forum); + _services.EventPublisher.EntityInserted(forum); } /// @@ -457,7 +465,7 @@ public virtual void UpdateForum(Forum forum) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityUpdated(forum); + _services.EventPublisher.EntityUpdated(forum); } /// @@ -486,7 +494,7 @@ public virtual void DeleteTopic(ForumTopic forumTopic) { _forumSubscriptionRepository.Delete(fs); //event notification - _eventPublisher.EntityDeleted(fs); + _services.EventPublisher.EntityDeleted(fs); } //update stats @@ -497,7 +505,7 @@ public virtual void DeleteTopic(ForumTopic forumTopic) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityDeleted(forumTopic); + _services.EventPublisher.EntityDeleted(forumTopic); } /// @@ -552,32 +560,33 @@ public virtual ForumTopic GetTopicById(int forumTopicId, bool increaseViews) /// Page index /// Page size /// Forum Topics - public virtual IPagedList GetAllTopics(int forumId, - int customerId, string keywords, ForumSearchType searchType, - int limitDays, int pageIndex, int pageSize) + public virtual IPagedList GetAllTopics(int forumId, int customerId, string keywords, ForumSearchType searchType, int limitDays, int pageIndex, int pageSize) { DateTime? limitDate = null; + if (limitDays > 0) { limitDate = DateTime.UtcNow.AddDays(-limitDays); } - 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) && - ( - // following line causes SqlCeException on SQLCE4 (comparing parameter to IS NULL in query) -works on SQL Server - // String.IsNullOrEmpty(keywords) || - ((searchType == ForumSearchType.All || searchType == ForumSearchType.TopicTitlesOnly) && ft.Subject.Contains(keywords)) || - ((searchType == ForumSearchType.All || searchType == ForumSearchType.PostTextOnly) && fp.Text.Contains(keywords))) && - (!limitDate.HasValue || limitDate.Value <= ft.LastPostTime) - 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 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; @@ -591,18 +600,34 @@ where query1.Contains(ft.Id) /// Forum Topics public virtual IList GetActiveTopics(int forumId, int count) { - var query1 = from ft in _forumTopicRepository.Table - where - (forumId == 0 || ft.ForumId == forumId) && - (ft.LastPostTime.HasValue) - select ft.Id; - - var query2 = from ft in _forumTopicRepository.Table - where query1.Contains(ft.Id) - orderby ft.LastPostTime descending - select ft; - - var forumTopics = query2.Take(count).ToList(); + 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(); return forumTopics; } @@ -628,14 +653,14 @@ public virtual void InsertTopic(ForumTopic forumTopic, bool sendNotifications) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityInserted(forumTopic); + _services.EventPublisher.EntityInserted(forumTopic); //send notifications if (sendNotifications) { var forum = forumTopic.Forum; var subscriptions = GetAllSubscriptions(0, forum.Id, 0, 0, int.MaxValue); - var languageId = _workContext.WorkingLanguage.Id; + var languageId = _services.WorkContext.WorkingLanguage.Id; foreach (var subscription in subscriptions) { @@ -646,8 +671,7 @@ public virtual void InsertTopic(ForumTopic forumTopic, bool sendNotifications) if (!String.IsNullOrEmpty(subscription.Customer.Email)) { - _workflowMessageService.SendNewForumTopicMessage(subscription.Customer, forumTopic, - forum, languageId); + _workflowMessageService.SendNewForumTopicMessage(subscription.Customer, forumTopic, forum, languageId); } } } @@ -670,7 +694,7 @@ public virtual void UpdateTopic(ForumTopic forumTopic) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityUpdated(forumTopic); + _services.EventPublisher.EntityUpdated(forumTopic); } /// @@ -685,7 +709,7 @@ public virtual ForumTopic MoveTopic(int forumTopicId, int newForumId) if (forumTopic == null) return forumTopic; - if (this.IsCustomerAllowedToMoveTopic(_workContext.CurrentCustomer, forumTopic)) + if (this.IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, forumTopic)) { int previousForumId = forumTopic.ForumId; var newForum = GetForumById(newForumId); @@ -725,7 +749,7 @@ public virtual void DeletePost(ForumPost forumPost) //delete topic if it was the first post bool deleteTopic = false; - ForumPost firstPost = forumTopic.GetFirstPost(this); + var firstPost = forumTopic.GetFirstPost(this); if (firstPost != null && firstPost.Id == forumPost.Id) { deleteTopic = true; @@ -753,7 +777,7 @@ public virtual void DeletePost(ForumPost forumPost) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityDeleted(forumPost); + _services.EventPublisher.EntityDeleted(forumPost); } @@ -786,11 +810,9 @@ public virtual ForumPost GetPostById(int forumPostId) /// Page index /// Page size /// Posts - public virtual IPagedList GetAllPosts(int forumTopicId, - int customerId, string keywords, int pageIndex, int pageSize) + public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, int pageIndex, int pageSize) { - return GetAllPosts(forumTopicId, customerId, keywords, true, - pageIndex, pageSize); + return GetAllPosts(forumTopicId, customerId, keywords, true, pageIndex, pageSize); } /// @@ -803,8 +825,7 @@ public virtual IPagedList GetAllPosts(int forumTopicId, /// Page index /// Page size /// Forum Posts - public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, - string keywords, bool ascSort, int pageIndex, int pageSize) + public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, bool ascSort, int pageIndex, int pageSize) { var query = _forumPostRepository.Table; if (forumTopicId > 0) @@ -860,20 +881,17 @@ public virtual void InsertPost(ForumPost forumPost, bool sendNotifications) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityInserted(forumPost); + _services.EventPublisher.EntityInserted(forumPost); //notifications if (sendNotifications) { var forum = forumTopic.Forum; - var subscriptions = GetAllSubscriptions(0, 0, - forumTopic.Id, 0, int.MaxValue); + var subscriptions = GetAllSubscriptions(0, 0, forumTopic.Id, 0, int.MaxValue); - var languageId = _workContext.WorkingLanguage.Id; + var languageId = _services.WorkContext.WorkingLanguage.Id; - int friendlyTopicPageIndex = CalculateTopicPageIndex(forumPost.TopicId, - _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10, - forumPost.Id) + 1; + int friendlyTopicPageIndex = CalculateTopicPageIndex(forumPost.TopicId, _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10, forumPost.Id) + 1; foreach (ForumSubscription subscription in subscriptions) { @@ -884,8 +902,7 @@ public virtual void InsertPost(ForumPost forumPost, bool sendNotifications) if (!String.IsNullOrEmpty(subscription.Customer.Email)) { - _workflowMessageService.SendNewForumPostMessage(subscription.Customer, forumPost, - forumTopic, forum, friendlyTopicPageIndex, languageId); + _workflowMessageService.SendNewForumPostMessage(subscription.Customer, forumPost, forumTopic, forum, friendlyTopicPageIndex, languageId); } } } @@ -909,7 +926,7 @@ public virtual void UpdatePost(ForumPost forumPost) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityUpdated(forumPost); + _services.EventPublisher.EntityUpdated(forumPost); } /// @@ -926,7 +943,7 @@ public virtual void DeletePrivateMessage(PrivateMessage privateMessage) _forumPrivateMessageRepository.Delete(privateMessage); //event notification - _eventPublisher.EntityDeleted(privateMessage); + _services.EventPublisher.EntityDeleted(privateMessage); } /// @@ -1005,7 +1022,7 @@ public virtual void InsertPrivateMessage(PrivateMessage privateMessage) _forumPrivateMessageRepository.Insert(privateMessage); //event notification - _eventPublisher.EntityInserted(privateMessage); + _services.EventPublisher.EntityInserted(privateMessage); var customerTo = _customerService.GetCustomerById(privateMessage.ToCustomerId); if (customerTo == null) @@ -1019,7 +1036,7 @@ public virtual void InsertPrivateMessage(PrivateMessage privateMessage) //Email notification if (_forumSettings.NotifyAboutPrivateMessages) { - _workflowMessageService.SendPrivateMessageNotification(customerTo, privateMessage, _workContext.WorkingLanguage.Id); + _workflowMessageService.SendPrivateMessageNotification(customerTo, privateMessage, _services.WorkContext.WorkingLanguage.Id); } } @@ -1036,13 +1053,13 @@ public virtual void UpdatePrivateMessage(PrivateMessage privateMessage) { _forumPrivateMessageRepository.Delete(privateMessage); //event notification - _eventPublisher.EntityDeleted(privateMessage); + _services.EventPublisher.EntityDeleted(privateMessage); } else { _forumPrivateMessageRepository.Update(privateMessage); //event notification - _eventPublisher.EntityUpdated(privateMessage); + _services.EventPublisher.EntityUpdated(privateMessage); } } @@ -1060,7 +1077,7 @@ public virtual void DeleteSubscription(ForumSubscription forumSubscription) _forumSubscriptionRepository.Delete(forumSubscription); //event notification - _eventPublisher.EntityDeleted(forumSubscription); + _services.EventPublisher.EntityDeleted(forumSubscription); } /// @@ -1092,22 +1109,23 @@ public virtual ForumSubscription GetSubscriptionById(int forumSubscriptionId) /// Page index /// Page size /// Forum subscriptions - public virtual IPagedList GetAllSubscriptions(int customerId, int forumId, - int topicId, int pageIndex, int pageSize) + public virtual IPagedList GetAllSubscriptions(int customerId, int forumId, int topicId, int pageIndex, int pageSize) { - var fsQuery = from fs in _forumSubscriptionRepository.Table - join c in _customerRepository.Table on fs.CustomerId equals c.Id - where - (customerId == 0 || fs.CustomerId == customerId) && - (forumId == 0 || fs.ForumId == forumId) && - (topicId == 0 || fs.TopicId == topicId) && - (c.Active && !c.Deleted) - select fs.SubscriptionGuid; - - var query = from fs in _forumSubscriptionRepository.Table - where fsQuery.Contains(fs.SubscriptionGuid) - orderby fs.CreatedOnUtc descending, fs.SubscriptionGuid descending - select fs; + var fsQuery = + from fs in _forumSubscriptionRepository.Table + join c in _customerRepository.Table on fs.CustomerId equals c.Id + where + (customerId == 0 || fs.CustomerId == customerId) && + (forumId == 0 || fs.ForumId == forumId) && + (topicId == 0 || fs.TopicId == topicId) && + (c.Active && !c.Deleted) + select fs.SubscriptionGuid; + + var query = + from fs in _forumSubscriptionRepository.Table + where fsQuery.Contains(fs.SubscriptionGuid) + orderby fs.CreatedOnUtc descending, fs.SubscriptionGuid descending + select fs; var forumSubscriptions = new PagedList(query, pageIndex, pageSize); return forumSubscriptions; @@ -1127,7 +1145,7 @@ public virtual void InsertSubscription(ForumSubscription forumSubscription) _forumSubscriptionRepository.Insert(forumSubscription); //event notification - _eventPublisher.EntityInserted(forumSubscription); + _services.EventPublisher.EntityInserted(forumSubscription); } /// @@ -1144,7 +1162,7 @@ public virtual void UpdateSubscription(ForumSubscription forumSubscription) _forumSubscriptionRepository.Update(forumSubscription); //event notification - _eventPublisher.EntityUpdated(forumSubscription); + _services.EventPublisher.EntityUpdated(forumSubscription); } /// @@ -1438,8 +1456,7 @@ public virtual bool IsCustomerAllowedToSubscribe(Customer customer) public virtual int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId) { int pageIndex = 0; - var forumPosts = GetAllPosts(forumTopicId, 0, - string.Empty, true, 0, int.MaxValue); + var forumPosts = GetAllPosts(forumTopicId, 0, string.Empty, true, 0, int.MaxValue); for (int i = 0; i < forumPosts.TotalCount; i++) { @@ -1454,6 +1471,7 @@ public virtual int CalculateTopicPageIndex(int forumTopicId, int pageSize, int p return pageIndex; } - #endregion + + #endregion } } diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index a0de486d1c..46e0f2f428 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -27,7 +27,7 @@ public partial interface IForumService /// Gets all forum groups /// /// Forum groups - IList GetAllForumGroups(); + IList GetAllForumGroups(bool showHidden = false); /// /// Inserts a forum group @@ -105,9 +105,7 @@ public partial interface IForumService /// Page index /// Page size /// Forum Topics - IPagedList GetAllTopics(int forumId, - int customerId, string keywords, ForumSearchType searchType, - int limitDays, int pageIndex, int pageSize); + IPagedList GetAllTopics(int forumId, int customerId, string keywords, ForumSearchType searchType, int limitDays, int pageIndex, int pageSize); /// /// Gets active forum topics diff --git a/src/Libraries/SmartStore.Services/Hooks/SlugSupportedPostDeleteHook.cs b/src/Libraries/SmartStore.Services/Hooks/SlugSupportedPostDeleteHook.cs new file mode 100644 index 0000000000..9c7c0cbb49 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/SlugSupportedPostDeleteHook.cs @@ -0,0 +1,31 @@ +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/SoftDeletablePreUpdateHook.cs b/src/Libraries/SmartStore.Services/Hooks/SoftDeletablePreUpdateHook.cs index ae87d21fa5..82e9a734cd 100644 --- a/src/Libraries/SmartStore.Services/Hooks/SoftDeletablePreUpdateHook.cs +++ b/src/Libraries/SmartStore.Services/Hooks/SoftDeletablePreUpdateHook.cs @@ -1,22 +1,22 @@ -using System; +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 Lazy _aclService; - private readonly Lazy _dbContext; + private readonly IComponentContext _ctx; - public SoftDeletablePreUpdateHook(Lazy aclService, Lazy dbContext) + public SoftDeletablePreUpdateHook(IComponentContext ctx) { - this._aclService = aclService; - this._dbContext = dbContext; + this._ctx = ctx; } public override void Hook(ISoftDeletable entity, HookEntityMetadata metadata) @@ -26,22 +26,48 @@ public override void Hook(ISoftDeletable entity, HookEntityMetadata metadata) if (baseEntity == null) return; - var aclEntity = baseEntity as IAclSupported; - if (aclEntity == null || !aclEntity.SubjectToAcl) + var dbContext = _ctx.Resolve(); + var modifiedProps = dbContext.GetModifiedProperties(baseEntity); + + if (!modifiedProps.ContainsKey("Deleted")) return; - var ctx = _dbContext.Value; - var modProps = ctx.GetModifiedProperties(baseEntity); - if (modProps.ContainsKey("Deleted")) + var entityType = baseEntity.GetUnproxiedType(); + + using (var scope = new DbContextScope(ctx: dbContext, autoCommit: false)) { - var shouldSetIdle = entity.Deleted; - var entityType = baseEntity.GetUnproxiedType(); + // 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 records = _aclService.Value.GetAclRecordsFor(entityType.Name, baseEntity.Id); - foreach (var record in records) + 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) { - record.IsIdle = shouldSetIdle; - _aclService.Value.UpdateAclRecord(record); + 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); + } + } } } } diff --git a/src/Libraries/SmartStore.Services/ICommonServices.cs b/src/Libraries/SmartStore.Services/ICommonServices.cs index cc9a048b54..f9b3043bf0 100644 --- a/src/Libraries/SmartStore.Services/ICommonServices.cs +++ b/src/Libraries/SmartStore.Services/ICommonServices.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; @@ -10,12 +9,18 @@ using SmartStore.Services.Security; using SmartStore.Services.Configuration; using SmartStore.Services.Stores; +using Autofac; +using SmartStore.Services.Helpers; namespace SmartStore.Services -{ - +{ public interface ICommonServices { + IComponentContext Container + { + get; + } + ICacheManager Cache { get; @@ -75,6 +80,43 @@ IStoreService StoreService { get; } + + IDateTimeHelper DateTimeHelper + { + get; + } } + public static class ICommonServicesExtensions + { + public static TService Resolve(this ICommonServices services) + { + return services.Container.Resolve(); + } + + public static TService Resolve(this ICommonServices services, object serviceKey) + { + return services.Container.ResolveKeyed(serviceKey); + } + + public static TService ResolveNamed(this ICommonServices services, string serviceName) + { + return services.Container.ResolveNamed(serviceName); + } + + public static object Resolve(this ICommonServices services, Type serviceType) + { + return services.Resolve(null, serviceType); + } + + public static object Resolve(this ICommonServices services, object serviceKey, Type serviceType) + { + return services.Container.ResolveKeyed(serviceKey, serviceType); + } + + public static object ResolveNamed(this ICommonServices services, string serviceName, Type serviceType) + { + return services.Container.ResolveNamed(serviceName, serviceType); + } + } } diff --git a/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs b/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs index ff2f1c872e..6195301c03 100644 --- a/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs +++ b/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs @@ -1,16 +1,16 @@ using System; using System.Collections.Generic; using System.Xml; -using SmartStore.Core.Data; +using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Plugins; namespace SmartStore.Services.Localization { - /// - /// Localization manager interface - /// - public partial interface ILocalizationService + /// + /// Localization manager interface + /// + public partial interface ILocalizationService { void ClearCache(); @@ -117,9 +117,10 @@ LocaleStringResource GetLocaleStringResourceByName(string resourceName, int lang /// Language /// XML document /// Prefix for resource key name - /// Specifies whether resource should be inserted or updated (or both) - /// Specifies whether user touched resources should also be updated - void ImportResourcesFromXml(Language language, + /// Specifies whether resource should be inserted or updated (or both) + /// Specifies whether user touched resources should also be updated + /// The number of processed (added or updated) resource entries + int ImportResourcesFromXml(Language language, XmlDocument xmlDocument, string rootKey = null, bool sourceIsPlugin = false, @@ -131,11 +132,14 @@ void ImportResourcesFromXml(Language language, /// /// codehint: sm-add /// Descriptor of the plugin - /// Load them into list rather than into database + /// Load them into the passed list rather than into database /// Specifies whether user touched resources should also be updated /// Import only files for particular languages - void ImportPluginResourcesFromXml(PluginDescriptor pluginDescriptor, - List forceToList = null, bool updateTouchedResources = true, IList filterLanguages = null); + void ImportPluginResourcesFromXml( + PluginDescriptor pluginDescriptor, + IList targetList = null, + bool updateTouchedResources = true, + IList filterLanguages = null); /// /// Flattens all nested LocaleResource child nodes into a new document diff --git a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs index 871afe9887..070d1fbfe5 100644 --- a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs @@ -62,12 +62,14 @@ public partial interface ILocalizedEntityService /// Key selector /// Locale value /// Language ID - void SaveLocalizedValue(T entity, + void SaveLocalizedValue( + T entity, Expression> keySelector, string localeValue, int languageId) where T : BaseEntity, ILocalizedEntity; - void SaveLocalizedValue(T entity, + void SaveLocalizedValue( + T entity, Expression> keySelector, TPropType localeValue, int languageId) where T : BaseEntity, ILocalizedEntity; diff --git a/src/Libraries/SmartStore.Services/Localization/LanguageService.cs b/src/Libraries/SmartStore.Services/Localization/LanguageService.cs index bc8e7d0999..db75218b54 100644 --- a/src/Libraries/SmartStore.Services/Localization/LanguageService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LanguageService.cs @@ -110,18 +110,19 @@ public virtual IList GetAllLanguages(bool showHidden = false, int stor { var query = _languageRepository.Table; if (!showHidden) - query = query.Where(l => l.Published); - query = query.OrderBy(l => l.DisplayOrder); + query = query.Where(x => x.Published); + query = query.OrderBy(x => x.DisplayOrder); return query.ToList(); }); - //store mapping + // store mapping if (storeId > 0) { languages = languages .Where(l => _storeMappingService.Authorize(l, storeId)) .ToList(); } + return languages; } diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs index b73e7660ed..a55b8ab84f 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs @@ -1,23 +1,19 @@ 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 Fasterflect; -using System.Xml; -using SmartStore.Core.Data; using SmartStore.Utilities; -using System.Collections.Concurrent; -using SmartStore.Core.ComponentModel; namespace SmartStore.Services.Localization { - public static class LocalizationExtentions + public static class LocalizationExtentions { - //private static readonly ConcurrentDictionary _compiledExpressions = new ConcurrentDictionary(); // --> MEM LEAK - /// /// Get localized property of an entity /// @@ -324,7 +320,13 @@ public static string GetLocalizedValue(this PluginDescriptor descriptor, ILocali string result = localizationService.GetResource(resourceName, languageId, false, "", true); if (String.IsNullOrEmpty(result) && returnDefaultValue) - result = descriptor.TryGetPropertyValue(propertyName) as string; + { + var fastProp = FastProperty.GetProperty(descriptor.GetType(), propertyName); + if (fastProp != null) + { + result = fastProp.GetValue(descriptor) as string; + } + } return result; } diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs index 872e2d7474..c6ac869c27 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs @@ -1,31 +1,30 @@ using System; +using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Xml; -using System.Xml.Linq; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; -using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Events; -using SmartStore.Data; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -using System.Text.RegularExpressions; -using System.Collections.Concurrent; -using System.Web.Mvc; -using System.Collections; +using SmartStore.Core.Localization; +using System.Globalization; namespace SmartStore.Services.Localization { - /// - /// Provides information about localization - /// - public partial class LocalizationService : ILocalizationService + /// + /// Provides information about localization + /// + public partial class LocalizationService : ILocalizationService { #region Constants private const string LOCALESTRINGRESOURCES_ALL_KEY = "SmartStore.lsr.all-{0}"; @@ -175,6 +174,7 @@ orderby lsr.ResourceName if (localeStringResource == null && logIfNotFound) _logger.Warning(string.Format("Resource string ({0}) not found. Language ID = {1}", resourceName, languageId)); + return localeStringResource; } @@ -273,6 +273,7 @@ orderby l.ResourceName 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) { @@ -418,180 +419,272 @@ public virtual string ExportResourcesToXml(Language language) return stringWriter.ToString(); } - /// - /// Import language resources from XML file - /// - /// Language - /// XML document - /// Prefix for resource key name - public virtual void ImportResourcesFromXml( - Language language, - XmlDocument xmlDocument, - string rootKey = null, - bool sourceIsPlugin = false, - ImportModeFlags mode = ImportModeFlags.Insert | ImportModeFlags.Update, - bool updateTouchedResources = false) - { - var autoCommit = _lsrRepository.AutoCommitEnabled; - var validateOnSave = _lsrRepository.Context.ValidateOnSaveEnabled; - var autoDetectChanges = _lsrRepository.Context.AutoDetectChangesEnabled; - var proxyCreation = _lsrRepository.Context.ProxyCreationEnabled; + public virtual void ImportPluginResourcesFromXml( + PluginDescriptor pluginDescriptor, + IList targetList = null, + bool updateTouchedResources = true, + IList filterLanguages = null) + { + var directory = new DirectoryInfo(Path.Combine(pluginDescriptor.OriginalAssemblyFile.Directory.FullName, "Localization")); - try - { - _lsrRepository.Context.ValidateOnSaveEnabled = false; - _lsrRepository.Context.AutoDetectChangesEnabled = false; - _lsrRepository.Context.ProxyCreationEnabled = false; + if (!directory.Exists) + return; - var toAdd = new List(); - var toUpdate = new List(); - var nodes = xmlDocument.SelectNodes(@"//Language/LocaleResource"); + if (targetList == null && updateTouchedResources) + { + DeleteLocaleStringResources(pluginDescriptor.ResourceRootKey); + } - foreach (var xel in nodes.Cast()) - { + var unprocessedLanguages = new List(); - string name = xel.GetAttribute("Name").TrimSafe(); - string value = ""; - var valueNode = xel.SelectSingleNode("Value"); - if (valueNode != null) - value = valueNode.InnerText; + var defaultLanguageId = _languageService.GetDefaultLanguageId(); + var languages = filterLanguages ?? _languageService.GetAllLanguages(true); - if (String.IsNullOrEmpty(name)) - continue; + string code = null; + foreach (var language in languages) + { + code = ImportPluginResourcesForLanguage( + language, + null, + directory, + pluginDescriptor.ResourceRootKey, + targetList, + updateTouchedResources, + false); + + if (code == null) + { + unprocessedLanguages.Add(language); + } + } - if (rootKey.HasValue()) - { - if (!xel.GetAttributeText("AppendRootKey").IsCaseInsensitiveEqual("false")) - name = "{0}.{1}".FormatWith(rootKey, name); - } + if (filterLanguages == null && unprocessedLanguages.Count > 0) + { + // There were unprocessed languages (no corresponding resource file could be found). + // In order for GetResource() to be able to gracefully fallback to the default language's resources, + // we need to import resources for the current default language.... + var processedLanguages = languages.Except(unprocessedLanguages).ToList(); + if (!processedLanguages.Any(x => x.Id == defaultLanguageId)) + { + // ...but only if no resource file could be mapped to the default language before, + // namely because in this case the following operation would be redundant. + var defaultLanguage = _languageService.GetLanguageById(_languageService.GetDefaultLanguageId()); + if (defaultLanguage != null) + { + ImportPluginResourcesForLanguage( + defaultLanguage, + "en-us", + directory, + pluginDescriptor.ResourceRootKey, + targetList, + updateTouchedResources, + true); + } + } + } + } - // do not use "Insert"/"Update" methods because they clear cache - // let's bulk insert - var resource = language.LocaleStringResources.Where(x => x.ResourceName.Equals(name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - if (resource != null) - { - if (mode.IsSet(ImportModeFlags.Update)) - { - if (updateTouchedResources || !resource.IsTouched.GetValueOrDefault()) - { - resource.ResourceValue = value; - resource.IsTouched = null; - toUpdate.Add(resource); - } - } - } - else - { - if (mode.IsSet(ImportModeFlags.Insert)) - { - toAdd.Add( - new LocaleStringResource() - { - LanguageId = language.Id, - ResourceName = name, - ResourceValue = value, - IsFromPlugin = sourceIsPlugin - }); - } - } - } + /// + /// 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, + DirectoryInfo directory, + string resourceRootKey, + IList targetList, + bool updateTouchedResources, + bool canFallBackToAnyResourceFile) + { + var fileNamePattern = "resources.{0}.xml"; - _lsrRepository.AutoCommitEnabled = true; - _lsrRepository.InsertRange(toAdd, 500); - toAdd.Clear(); + var codeCandidates = GetResourceFileCodeCandidates( + fileCode ?? language.LanguageCulture, + directory, + canFallBackToAnyResourceFile); - _lsrRepository.AutoCommitEnabled = false; - toUpdate.Each(x => - { - _lsrRepository.Update(x); - }); - - _lsrRepository.Context.SaveChanges(); - toUpdate.Clear(); + string path = null; + string code = null; - //clear cache - _cacheManager.RemoveByPattern(LOCALESTRINGRESOURCES_PATTERN_KEY); - } - catch (Exception ex) - { - throw ex; - } - finally - { - _lsrRepository.AutoCommitEnabled = autoCommit; - _lsrRepository.Context.ValidateOnSaveEnabled = validateOnSave; - _lsrRepository.Context.AutoDetectChangesEnabled = autoDetectChanges; - _lsrRepository.Context.ProxyCreationEnabled = proxyCreation; - } + foreach (var candidate in codeCandidates) + { + var pathCandidate = Path.Combine(directory.FullName, fileNamePattern.FormatInvariant(candidate)); + if (File.Exists(pathCandidate)) + { + code = candidate; + path = pathCandidate; + break; + } + } - } + if (code != null) + { + var doc = new XmlDocument(); - /// - /// Import plugin resources from xml files in plugin's localization directory. - /// - /// Descriptor of the plugin - /// Load them into list rather than into database - /// Specifies whether user touched resources should also be updated - /// Import only files for particular languages - public virtual void ImportPluginResourcesFromXml(PluginDescriptor pluginDescriptor, - List forceToList = null, bool updateTouchedResources = true, IList filterLanguages = null) - { - string pluginDir = pluginDescriptor.OriginalAssemblyFile.Directory.FullName; - string localizationDir = Path.Combine(pluginDir, "Localization"); + doc.Load(path); + doc = FlattenResourceFile(doc); - if (!System.IO.Directory.Exists(localizationDir)) - return; + if (targetList == null) + { + ImportResourcesFromXml(language, doc, resourceRootKey, true, updateTouchedResources: updateTouchedResources); + } + else + { + var nodes = doc.SelectNodes(@"//Language/LocaleResource"); + foreach (XmlNode node in nodes) + { + var valueNode = node.SelectSingleNode("Value"); + var res = new LocaleStringResource() + { + ResourceName = node.Attributes["Name"].InnerText.Trim(), + ResourceValue = (valueNode == null ? "" : valueNode.InnerText), + LanguageId = language.Id, + IsFromPlugin = true + }; - if (forceToList == null && updateTouchedResources) - DeleteLocaleStringResources(pluginDescriptor.ResourceRootKey); + if (res.ResourceName.HasValue()) + { + targetList.Add(res); + } + } + } + } - var languages = _languageService.GetAllLanguages(true); - var doc = new XmlDocument(); + return code; + } - foreach (var filePath in System.IO.Directory.EnumerateFiles(localizationDir, "*.xml")) + private IEnumerable GetResourceFileCodeCandidates(string code, DirectoryInfo directory, bool canFallBackToAnyResourceFile) + { + // exact match (de-DE) + yield return code; + + // neutral culture (de) + var ci = CultureInfo.GetCultureInfo(code); + if (ci.Parent != null && !ci.IsNeutralCulture) { - Match match = Regex.Match(Path.GetFileName(filePath), Regex.Escape("resources.") + "(.*?)" + Regex.Escape(".xml")); - string languageCode = match.Groups[1].Value; + code = ci.Parent.Name; + yield return code; + } + + var rgFileName = new Regex("^resources.(.+?).xml$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - Language language = languages.Where(l => l.LanguageCulture.IsCaseInsensitiveEqual(languageCode)).FirstOrDefault(); - if (language != null) + // any other region with same language (de-*) + foreach (var fi in directory.EnumerateFiles("resources.{0}-*.xml".FormatInvariant(code), SearchOption.TopDirectoryOnly)) + { + code = rgFileName.Match(fi.Name).Groups[1].Value; + if (LocalizationHelper.IsValidCultureCode(code)) { - language = _languageService.GetLanguageById(language.Id); + yield return code; + yield break; } + } - if (languageCode.HasValue() && language != null) + if (canFallBackToAnyResourceFile) + { + foreach (var fi in directory.EnumerateFiles("resources.*.xml", SearchOption.TopDirectoryOnly)) { - if (filterLanguages != null && !filterLanguages.Any(x => x.Id == language.Id)) + code = rgFileName.Match(fi.Name).Groups[1].Value; + if (LocalizationHelper.IsValidCultureCode(code)) { + yield return code; + yield break; + } + } + } + } + + public virtual int ImportResourcesFromXml( + Language language, + XmlDocument xmlDocument, + string rootKey = null, + bool sourceIsPlugin = false, + ImportModeFlags mode = ImportModeFlags.Insert | ImportModeFlags.Update, + bool updateTouchedResources = false) + { + using (var scope = new DbContextScope(autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false, forceNoTracking: true, hooksEnabled: false)) + { + var toAdd = new List(); + var toUpdate = new List(); + var nodes = xmlDocument.SelectNodes(@"//Language/LocaleResource"); + + var resources = language.LocaleStringResources.ToDictionarySafe(x => x.ResourceName, StringComparer.OrdinalIgnoreCase); + + LocaleStringResource resource; + + foreach (var xel in nodes.Cast()) + { + string name = xel.GetAttribute("Name").TrimSafe(); + string value = ""; + var valueNode = xel.SelectSingleNode("Value"); + if (valueNode != null) + value = valueNode.InnerText; + + if (String.IsNullOrEmpty(name)) continue; + + if (rootKey.HasValue()) + { + if (!xel.GetAttributeText("AppendRootKey").IsCaseInsensitiveEqual("false")) + name = "{0}.{1}".FormatWith(rootKey, name); } - doc.Load(filePath); - doc = FlattenResourceFile(doc); + resource = null; - if (forceToList == null) + // do not use "Insert"/"Update" methods because they clear cache + // let's bulk insert + //var resource = language.LocaleStringResources.Where(x => x.ResourceName.Equals(name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); + if (resources.TryGetValue(name, out resource)) { - ImportResourcesFromXml(language, doc, pluginDescriptor.ResourceRootKey, true, updateTouchedResources: updateTouchedResources); + if (mode.HasFlag(ImportModeFlags.Update)) + { + if (updateTouchedResources || !resource.IsTouched.GetValueOrDefault()) + { + if (value != resource.ResourceValue) + { + resource.ResourceValue = value; + resource.IsTouched = null; + toUpdate.Add(resource); + } + } + } } else { - var nodes = doc.SelectNodes(@"//Language/LocaleResource"); - foreach (XmlNode node in nodes) + if (mode.HasFlag(ImportModeFlags.Insert)) { - var valueNode = node.SelectSingleNode("Value"); - var res = new LocaleStringResource() - { - ResourceName = node.Attributes["Name"].InnerText.Trim(), - ResourceValue = (valueNode == null ? "" : valueNode.InnerText), - LanguageId = language.Id, - IsFromPlugin = true - }; - - if (res.ResourceName.HasValue()) - forceToList.Add(res); + toAdd.Add( + new LocaleStringResource + { + LanguageId = language.Id, + ResourceName = name, + ResourceValue = value, + IsFromPlugin = sourceIsPlugin + }); } } } + + //_lsrRepository.AutoCommitEnabled = true; + + if (toAdd.Any() || toUpdate.Any()) + { + _lsrRepository.InsertRange(toAdd); + toAdd.Clear(); + + _lsrRepository.UpdateRange(toUpdate); + toUpdate.Clear(); + + int num = _lsrRepository.Context.SaveChanges(); + + // clear cache + _cacheManager.RemoveByPattern(LOCALESTRINGRESOURCES_PATTERN_KEY); + + return num; + } + + return 0; } } diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs index 0aad0675e5..f765e0d580 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs @@ -7,6 +7,7 @@ using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Localization; +using System.Collections.Concurrent; namespace SmartStore.Services.Localization { @@ -15,104 +16,76 @@ namespace SmartStore.Services.Localization /// public partial class LocalizedEntityService : ILocalizedEntityService { - #region Constants - - private const string LOCALIZEDPROPERTY_KEY = "SmartStore.localizedproperty.value-{0}-{1}-{2}-{3}"; - private const string LOCALIZEDPROPERTY_ENTITYID_KEY = "SmartStore.localizedproperty.entityid-{0}-{1}-{2}"; - private const string LOCALIZEDPROPERTY_PATTERN_KEY = "SmartStore.localizedproperty."; - - #endregion - - #region Fields + private const string LOCALIZEDPROPERTY_ALL_KEY = "SmartStore.localizedproperty.all"; private readonly IRepository _localizedPropertyRepository; private readonly ICacheManager _cacheManager; + private readonly LocalizationSettings _localizationSettings; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Localized property repository - public LocalizedEntityService(ICacheManager cacheManager, - IRepository localizedPropertyRepository) + public LocalizedEntityService( + ICacheManager cacheManager, + IRepository localizedPropertyRepository, + LocalizationSettings localizationSettings) { this._cacheManager = cacheManager; this._localizedPropertyRepository = localizedPropertyRepository; + this._localizationSettings = localizationSettings; } - #endregion - - #region Methods - - /// - /// Deletes a localized property - /// - /// Localized property - public virtual void DeleteLocalizedProperty(LocalizedProperty localizedProperty) - { - if (localizedProperty == null) - throw new ArgumentNullException("localizedProperty"); - - _localizedPropertyRepository.Delete(localizedProperty); - - //cache - _cacheManager.RemoveByPattern(LOCALIZEDPROPERTY_PATTERN_KEY); - } - - /// - /// Gets a localized property - /// - /// Localized property identifier - /// Localized property - public virtual LocalizedProperty GetLocalizedPropertyById(int localizedPropertyId) - { - if (localizedPropertyId == 0) - return null; - - var localizedProperty = _localizedPropertyRepository.GetById(localizedPropertyId); - return localizedProperty; - } - - /// - /// Find localized value - /// - /// Language identifier - /// Entity identifier - /// Locale key group - /// Locale key - /// Found localized value - public virtual string GetLocalizedValue(int languageId, int entityId, string localeKeyGroup, string localeKey) - { - string key = string.Format(LOCALIZEDPROPERTY_KEY, languageId, entityId, localeKeyGroup, localeKey); - return _cacheManager.Get(key, () => - { - var query = from lp in _localizedPropertyRepository.Table - where lp.EntityId == entityId && - lp.LocaleKey == localeKey && - lp.LocaleKeyGroup == localeKeyGroup && - lp.LanguageId == languageId - select lp.LocaleValue; - var localeValue = query.FirstOrDefault(); - //little hack here. nulls aren't cacheable so set it to "" - if (localeValue == null) - localeValue = ""; - return localeValue; - }); - } + protected virtual ConcurrentDictionary GetAllProperties() + { + var result = _cacheManager.Get(LOCALIZEDPROPERTY_ALL_KEY, () => + { + if (_localizationSettings.LoadAllLocalizedPropertiesOnStartup) + { + var props = _localizedPropertyRepository.TableUntracked.ToDictionarySafe( + x => GenerateKey(x.LanguageId, x.LocaleKeyGroup, x.LocaleKey, x.EntityId), + x => x.LocaleValue.EmptyNull()); + return new ConcurrentDictionary(props); + } + else + { + return new ConcurrentDictionary(); + } + }); + + return result; + } + + 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 (!props.TryGetValue(key, out val)) + { + return string.Empty; + } + } + else + { + 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 val; + } - /// - /// Gets localized properties - /// - /// Entity identifier - /// Locale key group - /// Localized properties public virtual IList GetLocalizedProperties(int entityId, string localeKeyGroup) { - if (string.IsNullOrEmpty(localeKeyGroup)) + if (localeKeyGroup.IsEmpty()) return new List(); var query = from lp in _localizedPropertyRepository.Table @@ -120,49 +93,92 @@ orderby lp.Id where lp.EntityId == entityId && lp.LocaleKeyGroup == localeKeyGroup select lp; + var props = query.ToList(); return props; } - /// - /// Inserts a localized property - /// - /// Localized property - public virtual void InsertLocalizedProperty(LocalizedProperty localizedProperty) - { - if (localizedProperty == null) - throw new ArgumentNullException("localizedProperty"); + protected virtual LocalizedProperty GetLocalizedProperty(int languageId, int entityId, string localeKeyGroup, string localeKey) + { + var query = from lp in _localizedPropertyRepository.Table + where + lp.EntityId == entityId && + lp.LocaleKey == localeKey && + lp.LocaleKeyGroup == localeKeyGroup && + lp.LanguageId == languageId + select lp; - _localizedPropertyRepository.Insert(localizedProperty); + return query.FirstOrDefault(); + } - //cache - _cacheManager.RemoveByPattern(LOCALIZEDPROPERTY_PATTERN_KEY); + public virtual void InsertLocalizedProperty(LocalizedProperty localizedProperty) + { + Guard.ArgumentNotNull(() => localizedProperty); + + try + { + // db + _localizedPropertyRepository.Insert(localizedProperty); + + // cache + var key = GenerateKey(localizedProperty); + var val = localizedProperty.LocaleValue.EmptyNull(); + GetAllProperties().AddOrUpdate( + key, + val, + (k, v) => val); + } + catch { } } - /// - /// Updates the localized property - /// - /// Localized property public virtual void UpdateLocalizedProperty(LocalizedProperty localizedProperty) { - if (localizedProperty == null) - throw new ArgumentNullException("localizedProperty"); - - _localizedPropertyRepository.Update(localizedProperty); - - //cache - _cacheManager.RemoveByPattern(LOCALIZEDPROPERTY_PATTERN_KEY); - } - - /// - /// Save localized value - /// - /// Type - /// Entity - /// Key selector - /// Locale value - /// Language ID - public virtual void SaveLocalizedValue(T entity, + Guard.ArgumentNotNull(() => localizedProperty); + + try + { + // db + _localizedPropertyRepository.Update(localizedProperty); + + // cache + var key = GenerateKey(localizedProperty); + var val = localizedProperty.LocaleValue.EmptyNull(); + GetAllProperties().AddOrUpdate( + key, + val, + (k, v) => val); + } + catch { } + } + + public virtual void DeleteLocalizedProperty(LocalizedProperty localizedProperty) + { + Guard.ArgumentNotNull(() => localizedProperty); + + try + { + // cache + var key = GenerateKey(localizedProperty); + string val = null; + GetAllProperties().TryRemove(key, out val); + + // db + _localizedPropertyRepository.Delete(localizedProperty); + } + catch { } + } + + public virtual LocalizedProperty GetLocalizedPropertyById(int localizedPropertyId) + { + if (localizedPropertyId == 0) + return null; + + var localizedProperty = _localizedPropertyRepository.GetById(localizedPropertyId); + return localizedProperty; + } + + public virtual void SaveLocalizedValue( + T entity, Expression> keySelector, string localeValue, int languageId) where T : BaseEntity, ILocalizedEntity @@ -170,16 +186,14 @@ public virtual void SaveLocalizedValue(T entity, SaveLocalizedValue(entity, keySelector, localeValue, languageId); } - public virtual void SaveLocalizedValue(T entity, + public virtual void SaveLocalizedValue( + T entity, Expression> keySelector, TPropType localeValue, int languageId) where T : BaseEntity, ILocalizedEntity { - if (entity == null) - throw new ArgumentNullException("entity"); - - if (languageId == 0) - throw new ArgumentOutOfRangeException("languageId", "Language ID should not be 0"); + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotZero(languageId, "languageId"); var member = keySelector.Body as MemberExpression; if (member == null) @@ -200,32 +214,33 @@ public virtual void SaveLocalizedValue(T entity, string localeKeyGroup = typeof(T).Name; string localeKey = propInfo.Name; - var props = GetLocalizedProperties(entity.Id, localeKeyGroup); - var prop = props.FirstOrDefault(lp => lp.LanguageId == languageId && - lp.LocaleKey.Equals(localeKey, StringComparison.InvariantCultureIgnoreCase)); //should be culture invariant + var prop = GetLocalizedProperty(languageId, entity.Id, localeKeyGroup, localeKey); string localeValueStr = localeValue.Convert(); if (prop != null) { - if (string.IsNullOrWhiteSpace(localeValueStr)) + if (localeValueStr.IsEmpty()) { - //delete + // delete DeleteLocalizedProperty(prop); } else { - //update - prop.LocaleValue = localeValueStr; - UpdateLocalizedProperty(prop); + // update + if (prop.LocaleValue != localeValueStr) + { + prop.LocaleValue = localeValueStr; + UpdateLocalizedProperty(prop); + } } } else { - if (!string.IsNullOrWhiteSpace(localeValueStr)) + if (localeValueStr.HasValue()) { - //insert - prop = new LocalizedProperty() + // insert + prop = new LocalizedProperty { EntityId = entity.Id, LanguageId = languageId, @@ -238,6 +253,14 @@ public virtual void SaveLocalizedValue(T entity, } } - #endregion + private string GenerateKey(LocalizedProperty prop) + { + return GenerateKey(prop.LanguageId, prop.LocaleKeyGroup, prop.LocaleKey, prop.EntityId); + } + + private string GenerateKey(int languageId, string localeKeyGroup, string localeKey, int entityId) + { + return "{0}.{1}.{2}.{3}".FormatInvariant(languageId, localeKeyGroup, localeKey, entityId); + } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Logging/CustomerActivityService.cs b/src/Libraries/SmartStore.Services/Logging/CustomerActivityService.cs index c84c5f7526..61ba8f21ad 100644 --- a/src/Libraries/SmartStore.Services/Logging/CustomerActivityService.cs +++ b/src/Libraries/SmartStore.Services/Logging/CustomerActivityService.cs @@ -1,64 +1,49 @@ using System; -using System.Collections.Generic; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using SmartStore.Core; -using SmartStore.Core.Caching; using SmartStore.Core.Data; -using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Logging; -using SmartStore.Data; using SmartStore.Core.Logging; namespace SmartStore.Services.Logging { - /// - /// Customer activity service - /// - public class CustomerActivityService : ICustomerActivityService + /// + /// Customer activity service + /// + public class CustomerActivityService : ICustomerActivityService { - #region Fields + #region Fields - /// - /// Cache manager - /// - private readonly ICacheManager _cacheManager; - private readonly IRepository _activityLogRepository; + private const int _deleteNumberOfEntries = 1000; + + private readonly IRepository _activityLogRepository; private readonly IRepository _activityLogTypeRepository; - private readonly IWorkContext _workContext; + private readonly IRepository _customerRepository; + private readonly IWorkContext _workContext; private readonly IDbContext _dbContext; - private readonly IDataProvider _dataProvider; - private readonly CommonSettings _commonSettings; - // codehint: sm-add + private readonly static object s_lock = new object(); private readonly static ConcurrentDictionary s_logTypes = new ConcurrentDictionary(); - #endregion - #region Ctor - /// - /// Ctor - /// - /// Cache manager - /// Activity log repository - /// Activity log type repository - /// Work context - /// DB context> - /// WeData provider - /// Common settings - public CustomerActivityService(ICacheManager cacheManager, + #endregion + + #region Ctor + + public CustomerActivityService( IRepository activityLogRepository, IRepository activityLogTypeRepository, - IWorkContext workContext, - IDbContext dbContext, IDataProvider dataProvider, CommonSettings commonSettings) + IRepository customerRepository, + IWorkContext workContext, + IDbContext dbContext) { - this._cacheManager = cacheManager; this._activityLogRepository = activityLogRepository; this._activityLogTypeRepository = activityLogTypeRepository; + this._customerRepository = customerRepository; this._workContext = workContext; this._dbContext = dbContext; - this._dataProvider = dataProvider; - this._commonSettings = commonSettings; } #endregion @@ -218,27 +203,55 @@ public virtual void DeleteActivity(ActivityLog activityLog) _activityLogRepository.Delete(activityLog); } - /// - /// Gets all activity log items - /// - /// Log item creation from; null to load all customers - /// Log item creation to; null to load all customers - /// Customer identifier; null to load all customers - /// Activity log type identifier - /// Page index - /// Page size - /// Activity log collection - public virtual IPagedList GetAllActivities(DateTime? createdOnFrom, - DateTime? createdOnTo, int? customerId, int activityLogTypeId, - int pageIndex, int pageSize) + /// + /// Gets all activity log items + /// + /// Log item creation from; null to load all customers + /// Log item creation to; null to load all customers + /// Customer identifier; null to load all customers + /// Activity log type identifier + /// Page index + /// Page size + /// Customer email + /// Customer system name + /// Activity log collection + public virtual IPagedList GetAllActivities( + DateTime? createdOnFrom, + DateTime? createdOnTo, + int? customerId, + int activityLogTypeId, + int pageIndex, + int pageSize, + string email = null, + bool? customerSystemAccount = null) { var query = _activityLogRepository.Table; + + if (email.HasValue() || customerSystemAccount.HasValue) + { + var queryCustomers = _customerRepository.Table; + + if (email.HasValue()) + queryCustomers = queryCustomers.Where(x => x.Email == email); + + if (customerSystemAccount.HasValue) + queryCustomers = queryCustomers.Where(x => x.IsSystemAccount == customerSystemAccount.Value); + + query = + from al in _activityLogRepository.Table + join c in queryCustomers on al.CustomerId equals c.Id + select al; + } + if (createdOnFrom.HasValue) query = query.Where(al => createdOnFrom.Value <= al.CreatedOnUtc); + if (createdOnTo.HasValue) query = query.Where(al => createdOnTo.Value >= al.CreatedOnUtc); + if (activityLogTypeId > 0) query = query.Where(al => activityLogTypeId == al.ActivityLogTypeId); + if (customerId.HasValue) query = query.Where(al => customerId.Value == al.CustomerId); @@ -266,29 +279,54 @@ public virtual ActivityLog GetActivityById(int activityLogId) return activityLog; } - /// - /// Clears activity log - /// - public virtual void ClearAllActivities() - { - if (_commonSettings.UseStoredProceduresIfSupported && _dataProvider.StoredProceduresSupported) - { - //although it's not a stored procedure we use it to ensure that a database supports them - //we cannot wait until EF team has it implemented - http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1015357-batch-cud-support + public virtual IList GetActivityByIds(int[] activityLogIds) + { + if (activityLogIds == null || activityLogIds.Length == 0) + return new List(); + var query = _activityLogRepository.Table + .Where(x => activityLogIds.Contains(x.Id)) + .OrderByDescending(x => x.CreatedOnUtc); - //do all databases support "Truncate command"? - //TODO: do not hard-code the table name - _dbContext.ExecuteSqlCommand("TRUNCATE TABLE [ActivityLog]"); - } - else - { - var activityLog = _activityLogRepository.Table.ToList(); - foreach (var activityLogItem in activityLog) - _activityLogRepository.Delete(activityLogItem); - } - } - #endregion + return query.ToList(); + } + /// + /// Clears activity log + /// + public virtual void ClearAllActivities() + { + try + { + _dbContext.ExecuteSqlCommand("TRUNCATE TABLE [ActivityLog]"); + } + catch + { + try + { + for (int i = 0; i < 100000; ++i) + { + if (_dbContext.ExecuteSqlCommand("Delete Top ({0}) From [ActivityLog]", false, null, _deleteNumberOfEntries) < _deleteNumberOfEntries) + break; + } + } + catch { } + + try + { + _dbContext.ExecuteSqlCommand("DBCC CHECKIDENT('ActivityLog', RESEED, 0)"); + } + catch + { + try + { + _dbContext.ExecuteSqlCommand("Alter Table [ActivityLog] Alter Column [Id] Identity(1,1)"); + } + catch { } + } + } + } + + #endregion } } diff --git a/src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs b/src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs index d952288f74..8387de3969 100644 --- a/src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs +++ b/src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs @@ -7,6 +7,7 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Logging; using SmartStore.Core.Logging; +using SmartStore.Data; namespace SmartStore.Services.Logging { @@ -22,7 +23,7 @@ public partial class DefaultLogger : ILogger private readonly IRepository _logRepository; private readonly IWebHelper _webHelper; private readonly IDbContext _dbContext; - private readonly IDataProvider _dataProvider; + private readonly IWorkContext _workContext; private readonly IList _entries = new List(); @@ -30,12 +31,12 @@ public partial class DefaultLogger : ILogger #region Ctor - public DefaultLogger(IRepository logRepository, IWebHelper webHelper, IDbContext dbContext, IDataProvider dataProvider) + public DefaultLogger(IRepository logRepository, IWebHelper webHelper, IDbContext dbContext, IWorkContext workContext) { this._logRepository = logRepository; this._webHelper = webHelper; this._dbContext = dbContext; - this._dataProvider = dataProvider; + this._workContext = workContext; } #endregion @@ -93,14 +94,7 @@ public virtual void ClearLog() } } - if (DataSettings.Current.IsSqlServer) - { - try - { - _dbContext.ExecuteSqlCommand("DBCC SHRINKDATABASE(0)", true); - } - catch { } - } + _dbContext.ShrinkDatabase(); } public virtual void ClearLog(DateTime toUtc, LogLevel logLevel) @@ -115,10 +109,7 @@ public virtual void ClearLog(DateTime toUtc, LogLevel logLevel) break; } - if (DataSettings.Current.IsSqlServer) - { - _dbContext.ExecuteSqlCommand("DBCC SHRINKDATABASE(0)", true); - } + _dbContext.ShrinkDatabase(); } catch { } } @@ -128,9 +119,9 @@ public virtual IPagedList GetAllLogs(DateTime? fromUtc, DateTime? toUtc, st var query = _logRepository.Table; if (fromUtc.HasValue) - query = query.Where(l => fromUtc.Value <= l.CreatedOnUtc); + query = query.Where(l => fromUtc.Value <= l.CreatedOnUtc || fromUtc.Value <= l.UpdatedOnUtc); if (toUtc.HasValue) - query = query.Where(l => toUtc.Value >= l.CreatedOnUtc); + query = query.Where(l => toUtc.Value >= l.CreatedOnUtc || toUtc.Value >= l.UpdatedOnUtc); if (logLevel.HasValue) { int logLevelId = (int)logLevel.Value; @@ -138,7 +129,8 @@ public virtual IPagedList GetAllLogs(DateTime? fromUtc, DateTime? toUtc, st } if (!String.IsNullOrEmpty(message)) query = query.Where(l => l.ShortMessage.Contains(message) || l.FullMessage.Contains(message)); - query = query.OrderByDescending(l => l.CreatedOnUtc); + + query = query.OrderByDescending(l => l.UpdatedOnUtc).ThenByDescending(l => l.CreatedOnUtc); if (minFrequency > 0) query = query.Where(l => l.Frequency >= minFrequency); @@ -208,6 +200,7 @@ public void Flush() string ipAddress = ""; string pageUrl = ""; string referrerUrl = ""; + var currentCustomer = _workContext.CurrentCustomer; try { @@ -217,9 +210,7 @@ public void Flush() } catch { } - _logRepository.AutoCommitEnabled = false; - - using (var scope = new DbContextScope(autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) + using (var scope = new DbContextScope(autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { foreach (var context in _entries) { @@ -257,7 +248,7 @@ public void Flush() ShortMessage = shortMessage, FullMessage = fullMessage, IpAddress = ipAddress, - Customer = context.Customer, + Customer = context.Customer ?? currentCustomer, PageUrl = pageUrl, ReferrerUrl = referrerUrl, CreatedOnUtc = DateTime.UtcNow, @@ -273,7 +264,7 @@ public void Flush() log.LogLevel = context.LogLevel; log.IpAddress = ipAddress; - log.Customer = context.Customer; + log.Customer = context.Customer ?? currentCustomer; log.PageUrl = pageUrl; log.ReferrerUrl = referrerUrl; log.UpdatedOnUtc = DateTime.UtcNow; @@ -295,8 +286,6 @@ public void Flush() catch { } } - _logRepository.AutoCommitEnabled = true; - _entries.Clear(); } diff --git a/src/Libraries/SmartStore.Services/Media/DownloadService.cs b/src/Libraries/SmartStore.Services/Media/DownloadService.cs index 71117a3bcd..b7f03930e8 100644 --- a/src/Libraries/SmartStore.Services/Media/DownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/DownloadService.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; @@ -23,13 +26,7 @@ public partial class DownloadService : IDownloadService #region Ctor - /// - /// Ctor - /// - /// Download repository - /// - public DownloadService(IRepository downloadRepository, - IEventPublisher eventPubisher) + public DownloadService(IRepository downloadRepository, IEventPublisher eventPubisher) { _downloadRepository = downloadRepository; _eventPubisher = eventPubisher; @@ -39,11 +36,6 @@ public DownloadService(IRepository downloadRepository, #region Methods - /// - /// Gets a download - /// - /// Download identifier - /// Download public virtual Download GetDownloadById(int downloadId) { if (downloadId == 0) @@ -53,11 +45,25 @@ public virtual Download GetDownloadById(int downloadId) return download; } - /// - /// Gets a download by GUID - /// - /// Download GUID - /// Download + public virtual IList GetDownloadsByIds(int[] downloadIds) + { + if (downloadIds == null || downloadIds.Length == 0) + return new List(); + + var query = from dl in _downloadRepository.Table + where downloadIds.Contains(dl.Id) + select dl; + + 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 sortQuery.ToList(); + } + public virtual Download GetDownloadByGuid(Guid downloadGuid) { if (downloadGuid == Guid.Empty) @@ -70,10 +76,6 @@ public virtual Download GetDownloadByGuid(Guid downloadGuid) return order; } - /// - /// Deletes a download - /// - /// Download public virtual void DeleteDownload(Download download) { if (download == null) @@ -84,39 +86,28 @@ public virtual void DeleteDownload(Download download) _eventPubisher.EntityDeleted(download); } - /// - /// Inserts a download - /// - /// Download public virtual void InsertDownload(Download download) { if (download == null) throw new ArgumentNullException("download"); + download.UpdatedOnUtc = DateTime.UtcNow; _downloadRepository.Insert(download); _eventPubisher.EntityInserted(download); } - /// - /// Updates the download - /// - /// Download public virtual void UpdateDownload(Download download) { if (download == null) throw new ArgumentNullException("download"); + download.UpdatedOnUtc = DateTime.UtcNow; _downloadRepository.Update(download); _eventPubisher.EntityUpdated(download); } - /// - /// Gets a value indicating whether download is allowed - /// - /// Order item to check - /// True if download is allowed; otherwise, false. public virtual bool IsDownloadAllowed(OrderItem orderItem) { if (orderItem == null) @@ -182,11 +173,6 @@ public virtual bool IsDownloadAllowed(OrderItem orderItem) return false; } - /// - /// Gets a value indicating whether license download is allowed - /// - /// Order item to check - /// True if license download is allowed; otherwise, false. public virtual bool IsLicenseDownloadAllowed(OrderItem orderItem) { if (orderItem == null) diff --git a/src/Libraries/SmartStore.Services/Media/Extensions.cs b/src/Libraries/SmartStore.Services/Media/Extensions.cs deleted file mode 100644 index fcc6269049..0000000000 --- a/src/Libraries/SmartStore.Services/Media/Extensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.IO; -using System.Web; - -namespace SmartStore.Services.Media -{ - /// - /// Extensions - /// - public static class Extensions - { - /// - /// Gets the download binary array - /// - /// Posted file - /// Download binary array - public static byte[] GetDownloadBits(this HttpPostedFileBase postedFile) - { - Stream fs = postedFile.InputStream; - int size = postedFile.ContentLength; - byte[] binary = new byte[size]; - fs.Read(binary, 0, size); - return binary; - } - - /// - /// Gets the picture binary array - /// - /// Posted file - /// Picture binary array - public static byte[] GetPictureBits(this HttpPostedFileBase postedFile) - { - Stream fs = postedFile.InputStream; - int size = postedFile.ContentLength; - byte[] img = new byte[size]; - fs.Read(img, 0, size); - return img; - } - } -} diff --git a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs index 73afed9a32..80faf7f50a 100644 --- a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using SmartStore.Core; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; @@ -16,6 +19,13 @@ public partial interface IDownloadService /// Download Download GetDownloadById(int downloadId); + /// + /// Gets downloads by identifiers + /// + /// Download identifiers + /// List of download entities + IList GetDownloadsByIds(int[] downloadIds); + /// /// Gets a download by GUID /// diff --git a/src/Libraries/SmartStore.Services/Media/IPictureService.cs b/src/Libraries/SmartStore.Services/Media/IPictureService.cs index 74b2aba0da..ce9f231349 100644 --- a/src/Libraries/SmartStore.Services/Media/IPictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/IPictureService.cs @@ -5,10 +5,10 @@ namespace SmartStore.Services.Media { - /// - /// Picture service interface - /// - public partial interface IPictureService + /// + /// Picture service interface + /// + public partial interface IPictureService { /// /// Validates input picture dimensions and prevents that the image size exceeds global max size @@ -17,6 +17,24 @@ public partial interface IPictureService /// MIME type /// Picture binary or throws an exception byte[] ValidatePicture(byte[] pictureBinary); + + /// + /// Finds an equal picture by comparing the binary buffer + /// + /// 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); + + /// + /// Finds an equal picture by comparing the binary buffer + /// + /// Binary picture data + /// 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(byte[] pictureBinary, IEnumerable pictures, out int equalPictureId); /// /// Gets the loaded picture binary depending on picture storage settings @@ -118,16 +136,24 @@ string GetPictureUrl(Picture picture, /// Pictures IList GetPicturesByProductId(int productId, int recordsToReturn = 0); - /// - /// Inserts a 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 - /// Picture - Picture InsertPicture(byte[] pictureBinary, string mimeType, string seoFilename, bool isNew, bool validateBinary = true); + /// + /// Gets pictures by picture identifier + /// + /// Picture identifier + /// Pictures + IList GetPicturesByIds(int[] pictureIds); + + /// + /// Inserts a picture + /// + /// The picture binary + /// The picture MIME type + /// The SEO filename + /// A value indicating whether the picture is new + /// 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); /// /// Updates the picture diff --git a/src/Libraries/SmartStore.Services/Media/ImageCache.cs b/src/Libraries/SmartStore.Services/Media/ImageCache.cs index 6d36b9adc2..23e88f2295 100644 --- a/src/Libraries/SmartStore.Services/Media/ImageCache.cs +++ b/src/Libraries/SmartStore.Services/Media/ImageCache.cs @@ -117,7 +117,7 @@ public virtual void DeleteCachedImages() { foreach (var file in _cacheRootDir.GetFiles()) { - if (!file.Name.IsCaseInsensitiveEqual("placeholder")) + if (!file.Name.IsCaseInsensitiveEqual("placeholder") && !file.Name.IsCaseInsensitiveEqual("placeholder.txt")) { file.Delete(); } @@ -137,11 +137,14 @@ public virtual void DeleteCachedImages() public void CacheStatistics(out long fileCount, out long totalSize) { var allFiles = _cacheRootDir.GetFiles("*.*", SearchOption.AllDirectories); - fileCount = allFiles.Count(); + var allImageFiles = allFiles.Where(x => !x.Name.IsCaseInsensitiveEqual("placeholder") && !x.Name.IsCaseInsensitiveEqual("placeholder.txt")); + + fileCount = allImageFiles.Count(); + if (fileCount == 0) totalSize = 0; else - totalSize = allFiles.Sum(x => x.Length); + totalSize = allImageFiles.Sum(x => x.Length); } #region Utils diff --git a/src/Libraries/SmartStore.Services/Media/MediaExtensions.cs b/src/Libraries/SmartStore.Services/Media/MediaExtensions.cs new file mode 100644 index 0000000000..b776fd8de7 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/MediaExtensions.cs @@ -0,0 +1,187 @@ +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.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); + + var propName = downloadIdProp.ExtractMemberInfo().Name; + int currentDownloadId = downloadIdProp.Compile().Invoke(entity); + var rs = EngineContext.Current.Resolve>(); + + UpdateTransientStateForEntityInternal(entity, propName, currentDownloadId, rs, null, save); + } + + public static void UpdateDownloadTransientStateFor(TEntity entity, Expression> downloadIdProp, bool save = false) where TEntity : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotNull(() => downloadIdProp); + + var propName = downloadIdProp.ExtractMemberInfo().Name; + int currentDownloadId = downloadIdProp.Compile().Invoke(entity).GetValueOrDefault(); + var rs = EngineContext.Current.Resolve>(); + + UpdateTransientStateForEntityInternal(entity, propName, currentDownloadId, rs, null, save); + } + + public static void UpdatePictureTransientStateFor(TEntity entity, Expression> pictureIdProp, bool save = false) where TEntity : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotNull(() => pictureIdProp); + + var propName = pictureIdProp.ExtractMemberInfo().Name; + int currentPictureId = pictureIdProp.Compile().Invoke(entity); + var rs = EngineContext.Current.Resolve>(); + + UpdateTransientStateForEntityInternal(entity, propName, currentPictureId, rs, GetPictureDeleteAction(), save); + } + + public static void UpdatePictureTransientStateFor(TEntity entity, Expression> pictureIdProp, bool save = false) where TEntity : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotNull(() => pictureIdProp); + + var propName = pictureIdProp.ExtractMemberInfo().Name; + int currentPictureId = pictureIdProp.Compile().Invoke(entity).GetValueOrDefault(); + var rs = EngineContext.Current.Resolve>(); + + UpdateTransientStateForEntityInternal(entity, propName, currentPictureId, rs, GetPictureDeleteAction(), save); + } + + public static void UpdateDownloadTransientState(int? prevDownloadId, int? currentDownloadId, bool save = false) + { + var rs = EngineContext.Current.Resolve>(); + UpdateTransientStateCore(prevDownloadId.GetValueOrDefault(), currentDownloadId.GetValueOrDefault(), rs, null, save); + } + + public static void UpdateDownloadTransientState(int prevDownloadId, int currentDownloadId, bool save = false) + { + var rs = EngineContext.Current.Resolve>(); + UpdateTransientStateCore(prevDownloadId, currentDownloadId, rs, null, save); + } + + public static void UpdatePictureTransientState(int? prevPictureId, int? currentPictureId, bool save = false) + { + var rs = EngineContext.Current.Resolve>(); + UpdateTransientStateCore(prevPictureId.GetValueOrDefault(), currentPictureId.GetValueOrDefault(), rs, GetPictureDeleteAction(), save); + } + + public static void UpdatePictureTransientState(int prevPictureId, int currentPictureId, bool save = false) + { + var rs = EngineContext.Current.Resolve>(); + UpdateTransientStateCore(prevPictureId, currentPictureId, rs, GetPictureDeleteAction(), save); + } + + private static Action GetPictureDeleteAction() + { + Action deleteAction = pic => + { + var svc = EngineContext.Current.Resolve(); + svc.DeletePicture((Picture)pic); + }; + + return deleteAction; + } + + internal static void UpdateTransientStateForEntityInternal( + TEntity entity, + string propName, + int currentMediaId, + IRepository rs, + 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)) + { + prevMediaId = ((int?)obj).GetValueOrDefault(); + } + + UpdateTransientStateCore(prevMediaId, currentMediaId, rs, deleteAction, save); + } + + internal static void UpdateTransientStateCore( + int prevMediaId, + int currentMediaId, + IRepository rs, + Action deleteAction, + bool save) + where TMedia : BaseEntity + { + var autoCommit = rs.AutoCommitEnabled; + rs.AutoCommitEnabled = false; + + try + { + TMedia media = null; + bool shouldSave = false; + + bool isModified = prevMediaId != currentMediaId; + + if (currentMediaId > 0 && isModified) + { + // new entity with a media or media replaced + media = rs.GetById(currentMediaId); + if (media != null) + { + var transient = media as ITransient; + if (transient != null) + { + transient.IsTransient = false; + shouldSave = true; + } + } + } + + if (prevMediaId > 0 && isModified) + { + // ID changed, so delete old record + media = rs.GetById(prevMediaId); + if (media != null) + { + if (deleteAction == null) + { + rs.Delete(media); + } + else + { + deleteAction(media); + } + shouldSave = true; + } + } + + if (save && shouldSave) + { + rs.Context.SaveChanges(); + } + } + finally + { + rs.AutoCommitEnabled = autoCommit; + } + } + + + } + +} diff --git a/src/Libraries/SmartStore.Services/Media/PictureService.cs b/src/Libraries/SmartStore.Services/Media/PictureService.cs index 41308b2ba4..563993234f 100644 --- a/src/Libraries/SmartStore.Services/Media/PictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/PictureService.cs @@ -1,24 +1,19 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; using System.IO; using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ImageResizer; using SmartStore.Core; -using SmartStore.Core.IO; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; -using SmartStore.Services.Configuration; using SmartStore.Core.Events; +using SmartStore.Core.IO; using SmartStore.Core.Logging; -using SmartStore.Services.Seo; -using ImageResizer; -using ImageResizer.Configuration; -using System.Threading; -using System.Threading.Tasks; -using System.Text; +using SmartStore.Services.Configuration; using SmartStore.Utilities; namespace SmartStore.Services.Media @@ -56,16 +51,7 @@ public partial class PictureService : IPictureService #region Ctor - /// - /// Ctor - /// - /// Picture repository - /// Product picture repository - /// Setting service - /// Web helper - /// Logger - /// Event publisher - /// Media settings + public PictureService( IRepository pictureRepository, IRepository productPictureRepository, @@ -133,12 +119,25 @@ protected virtual void DeletePictureOnFileSystem(Picture picture) } } - /// - /// Validates input picture dimensions and prevents that the image size exceeds global max size - /// - /// Picture binary - /// MIME type - /// Picture binary or throws an exception + private string GetDefaultImageFileName(PictureType defaultPictureType = PictureType.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"); + break; + default: + defaultImageFileName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.jpg"); + break; + } + + return defaultImageFileName; + } + public virtual byte[] ValidatePicture(byte[] pictureBinary) { Size originalSize = this.GetPictureSize(pictureBinary); @@ -155,46 +154,48 @@ public virtual byte[] ValidatePicture(byte[] pictureBinary) } } - private string GetDefaultImageFileName(PictureType defaultPictureType = PictureType.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"); - break; - default: - defaultImageFileName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.jpg"); - break; - } + #endregion - return defaultImageFileName; - } + #region Methods - #endregion + public byte[] FindEqualPicture(string path, IEnumerable pictures, out int equalPictureId) + { + return FindEqualPicture(File.ReadAllBytes(path), pictures, out equalPictureId); + } - #region Methods + public byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable pictures, out int equalPictureId) + { + equalPictureId = 0; + try + { + foreach (var picture in pictures) + { + var otherPictureBinary = LoadPictureBinary(picture); - /// - /// Get picture SEO friendly name - /// - /// Name - /// Result - public virtual string GetPictureSeName(string name) + using (var myStream = new MemoryStream(pictureBinary)) + using (var otherStream = new MemoryStream(otherPictureBinary)) + { + if (myStream.ContentsEqual(otherStream)) + { + equalPictureId = picture.Id; + return null; + } + } + } + + return pictureBinary; + } + catch + { + return null; + } + } + + public virtual string GetPictureSeName(string name) { return SeoHelper.GetSeName(name, true, false); } - /// - /// Get a picture local path - /// - /// Picture instance - /// The target picture size (longest side) - /// A value indicating whether the default picture is shown - /// public virtual string GetThumbLocalPath(Picture picture, int targetSize = 0, bool showDefaultPicture = true) { // 'GetPictureUrl' takes care of creating the thumb when not created already @@ -229,13 +230,6 @@ public virtual string GetThumbLocalPath(Picture picture, int targetSize = 0, boo } - /// - /// 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 public virtual string GetDefaultPictureUrl(int targetSize = 0, PictureType defaultPictureType = PictureType.Entity, string storeLocation = null) { string defaultImageFileName = GetDefaultImageFileName(defaultPictureType); @@ -345,22 +339,11 @@ private byte[] LoadPictureFromFile(int pictureId, string mimeType, out string fi return File.ReadAllBytes(filePath); } - /// - /// Gets the loaded picture binary depending on picture storage settings - /// - /// Picture - /// Picture binary public virtual byte[] LoadPictureBinary(Picture picture) { return LoadPictureBinary(picture, this.StoreInDb); } - /// - /// Gets the loaded picture binary depending on picture storage settings - /// - /// Picture - /// Load from database; otherwise, from file system - /// Picture binary public virtual byte[] LoadPictureBinary(Picture picture, bool fromDb) { if (picture == null) @@ -420,15 +403,6 @@ internal Size GetPictureSize(byte[] pictureBinary) return size; } - /// - /// 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 public virtual string GetPictureUrl( int pictureId, int targetSize = 0, @@ -440,15 +414,6 @@ public virtual string GetPictureUrl( return GetPictureUrl(picture, targetSize, showDefaultPicture, storeLocation, defaultPictureType); } - /// - /// 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 public virtual string GetPictureUrl( Picture picture, int targetSize = 0, @@ -538,37 +503,27 @@ public virtual Picture GetPictureById(int pictureId) return picture; } - /// - /// Deletes a picture - /// - /// Picture public virtual void DeletePicture(Picture picture) { if (picture == null) throw new ArgumentNullException("picture"); - //delete thumbs + // delete thumbs _imageCache.DeleteCachedImages(picture); - //delete from file system + // delete from file system if (!this.StoreInDb) { DeletePictureOnFileSystem(picture); } - //delete from database + // delete from database _pictureRepository.Delete(picture); - //event notification + // event notification _eventPublisher.EntityDeleted(picture); } - /// - /// Gets a collection of pictures - /// - /// Current page - /// Items on each page - /// Paged list of pictures public virtual IPagedList GetPictures(int pageIndex, int pageSize) { var query = from p in _pictureRepository.Table @@ -578,12 +533,6 @@ orderby p.Id descending return pics; } - /// - /// Gets pictures by product identifier - /// - /// Product identifier - /// Number of records to return. 0 if you want to get all items - /// Pictures public virtual IList GetPicturesByProductId(int productId, int recordsToReturn = 0) { if (productId == 0) @@ -602,16 +551,17 @@ orderby pp.DisplayOrder return pics; } - /// - /// Inserts a 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 - /// Picture - public virtual Picture InsertPicture(byte[] pictureBinary, string mimeType, string seoFilename, bool isNew, bool validateBinary = true) + public virtual IList GetPicturesByIds(int[] pictureIds) + { + Guard.ArgumentNotNull(() => pictureIds); + + var query = _pictureRepository.Table + .Where(x => pictureIds.Contains(x.Id)); + + return query.ToList(); + } + + 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); @@ -628,6 +578,8 @@ public virtual Picture InsertPicture(byte[] pictureBinary, string mimeType, stri picture.MimeType = mimeType; picture.SeoFilename = seoFilename; picture.IsNew = isNew; + picture.IsTransient = isTransient; + picture.UpdatedOnUtc = DateTime.UtcNow; _pictureRepository.Insert(picture); @@ -642,16 +594,6 @@ public virtual Picture InsertPicture(byte[] pictureBinary, string mimeType, stri return picture; } - /// - /// 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 public virtual Picture UpdatePicture(int pictureId, byte[] pictureBinary, string mimeType, string seoFilename, bool isNew, bool validateBinary = true) { mimeType = mimeType.EmptyNull().Truncate(20); @@ -676,6 +618,7 @@ public virtual Picture UpdatePicture(int pictureId, byte[] pictureBinary, string picture.MimeType = mimeType; picture.SeoFilename = seoFilename; picture.IsNew = isNew; + picture.UpdatedOnUtc = DateTime.UtcNow; _pictureRepository.Update(picture); @@ -690,22 +633,15 @@ public virtual Picture UpdatePicture(int pictureId, byte[] pictureBinary, string return picture; } - /// - /// Updates a SEO filename of a picture - /// - /// The picture identifier - /// The SEO filename - /// Picture 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 + // update if it has been changed if (seoFilename != picture.SeoFilename) { - //update picture picture = UpdatePicture(picture.Id, LoadPictureBinary(picture), picture.MimeType, seoFilename, true, false); } return picture; @@ -715,10 +651,6 @@ public virtual Picture SetSeoFilename(int pictureId, string seoFilename) #region Properties - - /// - /// Gets or sets a value indicating whether the images should be stored in data base. - /// public virtual bool StoreInDb { get @@ -747,14 +679,10 @@ protected int MovePictures(bool toDb) var affectedFiles = new List(1000); var ctx = _pictureRepository.Context; - - _pictureRepository.AutoCommitEnabled = false; - var failed = false; - int i = 0; - using (var scope = new DbContextScope(ctx: ctx, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) + using (var scope = new DbContextScope(ctx: ctx, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { using (var tx = ctx.BeginTransaction()) { @@ -769,7 +697,7 @@ protected int MovePictures(bool toDb) if (pictures != null) { // detach all entities from previous page to save memory - pictures.Each(x => ctx.Detach(x)); + ctx.DetachEntities(pictures); // breathe pictures.Clear(); @@ -786,7 +714,7 @@ protected int MovePictures(bool toDb) if (!toDb) { - if (picture.PictureBinary.Length > 0) + if (picture.PictureBinary != null && picture.PictureBinary.Length > 0) { // save picture as file SavePictureInFile(picture.Id, picture.PictureBinary, picture.MimeType, out filePath); @@ -812,6 +740,7 @@ protected int MovePictures(bool toDb) } // explicitly attach modified entity to context, because we disabled AutoCommit + picture.UpdatedOnUtc = DateTime.UtcNow; _pictureRepository.Update(picture); i++; @@ -831,12 +760,11 @@ protected int MovePictures(bool toDb) tx.Rollback(); _settingService.SetSetting("Media.Images.StoreInDB", !toDb); _notifier.Error(ex.Message); + _logger.Error(ex); } } } - _pictureRepository.AutoCommitEnabled = true; - if (affectedFiles.Count > 0) { if ((toDb && !failed) || (!toDb && failed)) @@ -855,13 +783,9 @@ protected int MovePictures(bool toDb) } // shrink database (only when DB > FS and success) - if (!toDb && !failed && DataSettings.Current.IsSqlServer) + if (!toDb && !failed) { - try - { - ctx.ExecuteSqlCommand("DBCC SHRINKDATABASE(0)", true); - } - catch { } + ctx.ShrinkDatabase(); } } diff --git a/src/Libraries/SmartStore.Services/Media/TransientMediaClearTask.cs b/src/Libraries/SmartStore.Services/Media/TransientMediaClearTask.cs new file mode 100644 index 0000000000..b67c04fbe5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/TransientMediaClearTask.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Media; +using SmartStore.Services.Tasks; + +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) + /// + public partial class TransientMediaClearTask : ITask + { + private readonly IPictureService _pictureService; + private readonly IRepository _pictureRepository; + private readonly IRepository _downloadRepository; + + public TransientMediaClearTask( + IPictureService pictureService, + IRepository pictureRepository, + IRepository downloadRepository) + { + this._pictureService = pictureService; + this._pictureRepository = pictureRepository; + this._downloadRepository = downloadRepository; + } + + public void Execute(TaskExecutionContext ctx) + { + // 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; + + try + { + using (var scope = new DbContextScope(autoDetectChanges: false, validateOnSave: false, hooksEnabled: false)) + { + var pictures = _pictureRepository.Table.Where(x => x.IsTransient && x.UpdatedOnUtc < olderThan).ToList(); + foreach (var picture in pictures) + { + _pictureService.DeletePicture(picture); + } + + _pictureRepository.Context.SaveChanges(); + + if (DataSettings.Current.IsSqlServer) + { + try + { + _pictureRepository.Context.ExecuteSqlCommand("DBCC SHRINKDATABASE(0)", true); + } + catch { } + } + } + } + finally + { + _pictureRepository.AutoCommitEnabled = autoCommit; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/EmailAccountService.cs b/src/Libraries/SmartStore.Services/Messages/EmailAccountService.cs index ebf71742dc..639c0391cb 100644 --- a/src/Libraries/SmartStore.Services/Messages/EmailAccountService.cs +++ b/src/Libraries/SmartStore.Services/Messages/EmailAccountService.cs @@ -2,36 +2,37 @@ 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; namespace SmartStore.Services.Messages { - public partial class EmailAccountService:IEmailAccountService + public partial class EmailAccountService : IEmailAccountService { - private readonly IRepository _emailAccountRepository; + 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 ICacheManager _cacheManager; - /// - /// Ctor - /// - /// Email account repository - /// - /// Event published - public EmailAccountService(IRepository emailAccountRepository, - EmailAccountSettings emailAccountSettings, IEventPublisher eventPublisher) + private EmailAccount _defaultEmailAccount; + + public EmailAccountService( + IRepository emailAccountRepository, + EmailAccountSettings emailAccountSettings, + IEventPublisher eventPublisher, + ICacheManager cacheManager /* request */) { - _emailAccountRepository = emailAccountRepository; - _emailAccountSettings = emailAccountSettings; - _eventPublisher = eventPublisher; + this._emailAccountRepository = emailAccountRepository; + this._emailAccountSettings = emailAccountSettings; + this._eventPublisher = eventPublisher; + this._cacheManager = cacheManager; } - /// - /// Inserts an email account - /// - /// Email account public virtual void InsertEmailAccount(EmailAccount emailAccount) { if (emailAccount == null) @@ -57,14 +58,12 @@ public virtual void InsertEmailAccount(EmailAccount emailAccount) _emailAccountRepository.Insert(emailAccount); - //event notification + _cacheManager.RemoveByPattern(EMAILACCOUNT_PATTERN_KEY); + _defaultEmailAccount = null; + _eventPublisher.EntityInserted(emailAccount); } - /// - /// Updates an email account - /// - /// Email account public virtual void UpdateEmailAccount(EmailAccount emailAccount) { if (emailAccount == null) @@ -90,14 +89,12 @@ public virtual void UpdateEmailAccount(EmailAccount emailAccount) _emailAccountRepository.Update(emailAccount); - //event notification + _cacheManager.RemoveByPattern(EMAILACCOUNT_PATTERN_KEY); + _defaultEmailAccount = null; + _eventPublisher.EntityUpdated(emailAccount); } - /// - /// Deletes an email account - /// - /// Email account public virtual void DeleteEmailAccount(EmailAccount emailAccount) { if (emailAccount == null) @@ -108,28 +105,38 @@ public virtual void DeleteEmailAccount(EmailAccount emailAccount) _emailAccountRepository.Delete(emailAccount); - //event notification + _cacheManager.RemoveByPattern(EMAILACCOUNT_PATTERN_KEY); + _defaultEmailAccount = null; + _eventPublisher.EntityDeleted(emailAccount); } - /// - /// Gets an email account by identifier - /// - /// The email account identifier - /// Email account public virtual EmailAccount GetEmailAccountById(int emailAccountId) { if (emailAccountId == 0) return null; - - var emailAccount = _emailAccountRepository.GetById(emailAccountId); - return emailAccount; + + string key = string.Format(EMAILACCOUNT_BY_ID_KEY, emailAccountId); + return _cacheManager.Get(key, () => + { + return _emailAccountRepository.GetById(emailAccountId); + }); } - /// - /// Gets all email accounts - /// - /// Email accounts list + public virtual EmailAccount GetDefaultEmailAccount() + { + if (_defaultEmailAccount == null) + { + _defaultEmailAccount = GetEmailAccountById(_emailAccountSettings.DefaultEmailAccountId); + if (_defaultEmailAccount == null) + { + _defaultEmailAccount = GetAllEmailAccounts().FirstOrDefault(); + } + } + + return _defaultEmailAccount; + } + public virtual IList GetAllEmailAccounts() { var query = from ea in _emailAccountRepository.Table diff --git a/src/Libraries/SmartStore.Services/Messages/EventPublisherExtensions.cs b/src/Libraries/SmartStore.Services/Messages/EventPublisherExtensions.cs index ada12006c8..681cb588b6 100644 --- a/src/Libraries/SmartStore.Services/Messages/EventPublisherExtensions.cs +++ b/src/Libraries/SmartStore.Services/Messages/EventPublisherExtensions.cs @@ -1,4 +1,5 @@ -using SmartStore.Core; +using System.Collections.Generic; +using SmartStore.Core; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Events; @@ -26,12 +27,12 @@ public static void PublishNewsletterUnsubscribe(this IEventPublisher eventPublis eventPublisher.Publish(new EmailUnsubscribedEvent(email)); } - public static void EntityTokensAdded(this IEventPublisher eventPublisher, T entity, System.Collections.Generic.IList tokens) where T : BaseEntity + 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, System.Collections.Generic.IList tokens) + public static void MessageTokensAdded(this IEventPublisher eventPublisher, MessageTemplate message, IList tokens) { eventPublisher.Publish(new MessageTokensAddedEvent(message, tokens)); } diff --git a/src/Libraries/SmartStore.Services/Messages/IEmailAccountService.cs b/src/Libraries/SmartStore.Services/Messages/IEmailAccountService.cs index 5f03d721bd..6b3df6f6eb 100644 --- a/src/Libraries/SmartStore.Services/Messages/IEmailAccountService.cs +++ b/src/Libraries/SmartStore.Services/Messages/IEmailAccountService.cs @@ -30,6 +30,12 @@ public partial interface IEmailAccountService /// Email account EmailAccount GetEmailAccountById(int emailAccountId); + /// + /// Gets the default email account + /// + /// Emil account + EmailAccount GetDefaultEmailAccount(); + /// /// Gets all email accounts /// diff --git a/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs b/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs index b70bcdd0bf..3cf3bd7563 100644 --- a/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs @@ -3,6 +3,7 @@ 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; @@ -11,13 +12,13 @@ namespace SmartStore.Services.Messages { - public partial interface IMessageTokenProvider + public partial interface IMessageTokenProvider { void AddStoreTokens(IList tokens, Store store); - void AddOrderTokens(IList tokens, Order order, int languageId); + void AddOrderTokens(IList tokens, Order order, Language language); - void AddShipmentTokens(IList tokens, Shipment shipment, int languageId); + void AddShipmentTokens(IList tokens, Shipment shipment, Language language); void AddOrderNoteTokens(IList tokens, OrderNote orderNote); @@ -37,9 +38,9 @@ public partial interface IMessageTokenProvider void AddNewsCommentTokens(IList tokens, NewsComment newsComment); - void AddProductTokens(IList tokens, Product product, int languageId); + void AddProductTokens(IList tokens, Product product, Language language); - void AddForumTokens(IList tokens, Forum forum); + void AddForumTokens(IList tokens, Forum forum, Language language); void AddForumTopicTokens(IList tokens, ForumTopic forumTopic, int? friendlyForumTopicPageIndex = null, int? appendedPostIdentifierAnchor = null); @@ -54,13 +55,10 @@ void AddForumTopicTokens(IList tokens, ForumTopic forumTopic, string[] GetListOfAllowedTokens(); - //codehint: sm-add begin void AddBankConnectionTokens(IList tokens); void AddCompanyTokens(IList tokens); void AddContactDataTokens(IList tokens); - //codehint: sm-add end - } } diff --git a/src/Libraries/SmartStore.Services/Messages/INewsLetterSubscriptionService.cs b/src/Libraries/SmartStore.Services/Messages/INewsLetterSubscriptionService.cs index 851a4d952d..a579ff6859 100644 --- a/src/Libraries/SmartStore.Services/Messages/INewsLetterSubscriptionService.cs +++ b/src/Libraries/SmartStore.Services/Messages/INewsLetterSubscriptionService.cs @@ -1,21 +1,11 @@ using System; -using System.IO; using SmartStore.Core; using SmartStore.Core.Domain.Messages; -using SmartStore.Core.Data; namespace SmartStore.Services.Messages { - public partial interface INewsLetterSubscriptionService - { - - /// - /// Executes a bulk import of subscriber data from a CSV source - /// - /// The input file stream - /// The import result - ImportResult ImportSubscribers(Stream stream); - + public partial interface INewsLetterSubscriptionService + { /// /// Inserts a newsletter subscription /// @@ -37,12 +27,21 @@ public partial interface INewsLetterSubscriptionService /// if set to true [publish subscription events]. void DeleteNewsLetterSubscription(NewsLetterSubscription newsLetterSubscription, bool publishSubscriptionEvents = true); - /// - /// Gets a newsletter subscription by newsletter subscription identifier - /// - /// The newsletter subscription identifier - /// NewsLetter subscription - NewsLetterSubscription GetNewsLetterSubscriptionById(int newsLetterSubscriptionId); + /// + /// Adds or deletes a newsletter subscription + /// + /// true add subscription, false delete + /// Email address + /// Store identifier + /// true added subscription, false removed subscription, null did nothing + bool? AddNewsLetterSubscriptionFor(bool add, string email, int storeId); + + /// + /// Gets a newsletter subscription by newsletter subscription identifier + /// + /// The newsletter subscription identifier + /// NewsLetter subscription + NewsLetterSubscription GetNewsLetterSubscriptionById(int newsLetterSubscriptionId); /// /// Gets a newsletter subscription by newsletter subscription GUID @@ -70,4 +69,4 @@ public partial interface INewsLetterSubscriptionService /// NewsLetterSubscription entity list IPagedList GetAllNewsLetterSubscriptions(string email, int pageIndex, int pageSize, bool showHidden = false, int storeId = 0); } -} +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Messages/IQueuedEmailService.cs b/src/Libraries/SmartStore.Services/Messages/IQueuedEmailService.cs index 2df4541e0b..12baf20719 100644 --- a/src/Libraries/SmartStore.Services/Messages/IQueuedEmailService.cs +++ b/src/Libraries/SmartStore.Services/Messages/IQueuedEmailService.cs @@ -25,6 +25,12 @@ public partial interface IQueuedEmailService /// Queued email void DeleteQueuedEmail(QueuedEmail queuedEmail); + /// + /// Deletes all queued emails + /// + /// The count of deleted entries + int DeleteAllQueuedEmails(); + /// /// Gets a queued email by identifier /// @@ -42,19 +48,28 @@ public partial interface IQueuedEmailService /// /// Search queued emails /// - /// From Email - /// To Email - /// The start time - /// The end time - /// A value indicating whether to load only not sent emails - /// Maximum send tries - /// A value indicating whether we should sort queued email descending; otherwise, ascending. - /// Page index - /// Page size + /// An object containing the query criteria /// Email item collection - IPagedList SearchEmails(string fromEmail, - string toEmail, DateTime? startTime, DateTime? endTime, - bool loadNotSentItemsOnly, int maxSendTries, - bool loadNewest, int pageIndex, int pageSize); + IPagedList SearchEmails(SearchEmailsQuery query); + + /// + /// Sends a queued email + /// + /// Queued email + /// Whether the operation succeeded + bool SendEmail(QueuedEmail queuedEmail); + + /// + /// Gets a queued email attachment by identifier + /// + /// Queued email attachment identifier + /// Queued email attachment + QueuedEmailAttachment GetQueuedEmailAttachmentById(int id); + + /// + /// Deleted a queued email attachment + /// + /// Queued email attachment + void DeleteQueuedEmailAttachment(QueuedEmailAttachment qea); } } diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs new file mode 100644 index 0000000000..979051d1d6 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs @@ -0,0 +1,154 @@ +using System; +using System.Linq; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Messages; +using SmartStore.Services.DataExchange.Import; + +namespace SmartStore.Services.Messages.Importer +{ + public class NewsLetterSubscriptionImporter : IEntityImporter + { + private readonly ICommonServices _services; + private readonly IRepository _subscriptionRepository; + + public NewsLetterSubscriptionImporter( + ICommonServices services, + IRepository subscriptionRepository) + { + _services = services; + _subscriptionRepository = subscriptionRepository; + } + + public static string[] SupportedKeyFields + { + get + { + return new string[] { "Email" }; + } + } + + public static string[] DefaultKeyFields + { + get + { + return new string[] { "Email" }; + } + } + + public void Execute(ImportExecuteContext context) + { + var utcNow = DateTime.UtcNow; + var currentStoreId = _services.StoreContext.CurrentStore.Id; + + using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) + { + var segmenter = context.DataSegmenter; + + context.Result.TotalRecords = segmenter.TotalRows; + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var batch = segmenter.GetCurrentBatch(); + + // Perf: detach all entities + _subscriptionRepository.Context.DetachEntities(false); + + context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + foreach (var row in batch) + { + try + { + var active = true; + var email = row.GetDataValue("Email"); + var storeId = row.GetDataValue("StoreId"); + + if (storeId == 0) + { + storeId = currentStoreId; + } + + if (row.HasDataValue("Active") && row.TryGetDataValue("Active", out active)) + { + } + else + { + active = true; // default + } + + if (email.IsEmpty()) + { + context.Result.AddWarning("Skipped empty email address", row.GetRowInfo(), "Email"); + continue; + } + + if (email.Length > 255) + { + context.Result.AddWarning("Skipped email address '{0}'. It exceeds the maximum allowed length of 255".FormatInvariant(email), row.GetRowInfo(), "Email"); + continue; + } + + if (!email.IsEmail()) + { + context.Result.AddWarning("Skipped invalid email address '{0}'".FormatInvariant(email), row.GetRowInfo(), "Email"); + continue; + } + + NewsLetterSubscription subscription = null; + + foreach (var keyName in context.KeyFieldNames) + { + switch (keyName) + { + case "Email": + subscription = _subscriptionRepository.Table + .OrderBy(x => x.Id) + .FirstOrDefault(x => x.Email == email && x.StoreId == storeId); + break; + } + + if (subscription != null) + break; + } + + if (subscription == null) + { + if (context.UpdateOnly) + { + ++context.Result.SkippedRecords; + continue; + } + + subscription = new NewsLetterSubscription + { + Active = active, + CreatedOnUtc = utcNow, + Email = email, + NewsLetterSubscriptionGuid = Guid.NewGuid(), + StoreId = storeId + }; + + _subscriptionRepository.Insert(subscription); + context.Result.NewRecords++; + } + else + { + subscription.Active = active; + + _subscriptionRepository.Update(subscription); + context.Result.ModifiedRecords++; + } + } + catch (Exception exception) + { + context.Result.AddError(exception.ToAllMessages(), row.GetRowInfo()); + } + } // for + + _subscriptionRepository.Context.SaveChanges(); + } // while + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs b/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs index 92ae48a637..65d8fe735c 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs @@ -45,7 +45,8 @@ public partial class MessageTemplateService: IMessageTemplateService /// Store mapping service /// Message template repository /// Event published - public MessageTemplateService(ICacheManager cacheManager, + public MessageTemplateService( + ICacheManager cacheManager, IRepository storeMappingRepository, ILanguageService languageService, ILocalizedEntityService localizedEntityService, @@ -209,7 +210,7 @@ public virtual MessageTemplate CopyMessageTemplate(MessageTemplate messageTempla if (messageTemplate == null) throw new ArgumentNullException("messageTemplate"); - var mtCopy = new MessageTemplate() + var mtCopy = new MessageTemplate { Name = messageTemplate.Name, BccEmailAddresses = messageTemplate.BccEmailAddresses, @@ -218,25 +219,26 @@ public virtual MessageTemplate CopyMessageTemplate(MessageTemplate messageTempla IsActive = messageTemplate.IsActive, EmailAccountId = messageTemplate.EmailAccountId, LimitedToStores = messageTemplate.LimitedToStores + // INFO: we do not copy attachments }; InsertMessageTemplate(mtCopy); var languages = _languageService.GetAllLanguages(true); - //localization + // localization foreach (var lang in languages) { var bccEmailAddresses = messageTemplate.GetLocalized(x => x.BccEmailAddresses, lang.Id, false, false); - if (!String.IsNullOrEmpty(bccEmailAddresses)) + if (bccEmailAddresses.HasValue()) _localizedEntityService.SaveLocalizedValue(mtCopy, x => x.BccEmailAddresses, bccEmailAddresses, lang.Id); var subject = messageTemplate.GetLocalized(x => x.Subject, lang.Id, false, false); - if (!String.IsNullOrEmpty(subject)) + if (subject.HasValue()) _localizedEntityService.SaveLocalizedValue(mtCopy, x => x.Subject, subject, lang.Id); var body = messageTemplate.GetLocalized(x => x.Body, lang.Id, false, false); - if (!String.IsNullOrEmpty(body)) + if (body.HasValue()) _localizedEntityService.SaveLocalizedValue(mtCopy, x => x.Body, subject, lang.Id); var emailAccountId = messageTemplate.GetLocalized(x => x.EmailAccountId, lang.Id, false, false); @@ -244,7 +246,7 @@ public virtual MessageTemplate CopyMessageTemplate(MessageTemplate messageTempla _localizedEntityService.SaveLocalizedValue(mtCopy, x => x.EmailAccountId, emailAccountId, lang.Id); } - //store mapping + // store mapping var selectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(messageTemplate); foreach (var id in selectedStoreIds) { diff --git a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs b/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs index 5b11022c3d..d0ff917259 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Linq.Expressions; using System.Text; using System.Web; using SmartStore.Core; @@ -10,19 +11,23 @@ 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.Core.Events; using SmartStore.Services.Forums; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; @@ -30,10 +35,8 @@ using SmartStore.Services.Orders; using SmartStore.Services.Payments; using SmartStore.Services.Seo; +using SmartStore.Services.Stores; using SmartStore.Services.Topics; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Plugins; -using System.Linq.Expressions; namespace SmartStore.Services.Messages { @@ -52,7 +55,7 @@ public partial class MessageTokenProvider : IMessageTokenProvider private readonly IStoreContext _storeContext; private readonly IDownloadService _downloadService; private readonly IOrderService _orderService; - private readonly IPaymentService _paymentService; + private readonly IProviderManager _providerManager; private readonly IProductAttributeParser _productAttributeParser; private readonly StoreInformationSettings _storeSettings; private readonly MessageTemplatesSettings _templatesSettings; @@ -67,25 +70,34 @@ public partial class MessageTokenProvider : IMessageTokenProvider private readonly ShoppingCartSettings _shoppingCartSettings; private readonly IDeliveryTimeService _deliveryTimeService; private readonly IQuantityUnitService _quantityUnitService; - - #endregion + private readonly IUrlRecordService _urlRecordService; + private readonly IStoreService _storeService; + private readonly IGenericAttributeService _attrService; + private readonly IPictureService _pictureService; + private readonly MediaSettings _mediaSettings; + + #endregion - #region Ctor + #region Ctor - public MessageTokenProvider(ILanguageService languageService, + 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, IPaymentService paymentService, + 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) + IDeliveryTimeService deliveryTimeService, IQuantityUnitService quantityUnitService, + IUrlRecordService urlRecordService, IStoreService storeService, + IGenericAttributeService attrService, + IPictureService pictureService, + MediaSettings mediaSettings) { this._languageService = languageService; this._localizationService = localizationService; @@ -98,7 +110,7 @@ public MessageTokenProvider(ILanguageService languageService, this._storeContext = storeContext; this._downloadService = downloadService; this._orderService = orderService; - this._paymentService = paymentService; + this._providerManager = providerManager; this._productAttributeParser = productAttributeParser; this._storeSettings = storeSettings; this._templatesSettings = templatesSettings; @@ -113,33 +125,90 @@ public MessageTokenProvider(ILanguageService languageService, 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 + #endregion - #region Utilities + #region Utilities - /// - /// Convert a collection to a HTML table - /// - /// Order - /// Language identifier - /// HTML table of products - protected virtual string ProductListToHtmlTable(Order order, int languageId) - { - var result = ""; + protected virtual Picture GetPictureFor(Product product, string attributesXml) + { + Picture picture = null; - var language = _languageService.GetLanguageById(languageId); + 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(); - sb.AppendLine(""); + 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", languageId))); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Product(s).Price", languageId))); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Product(s).Quantity", languageId))); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Product(s).Total", languageId))); + + 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(); @@ -152,11 +221,7 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId) DeliveryTime deliveryTime = null; - // merging attribute combination data required? - if (_catalogSettings.ShowProductSku || (_shoppingCartSettings.ShowDeliveryTimes && product.IsShipEnabled)) - { - product.MergeWithCombination(orderItem.AttributesXml, _productAttributeParser); - } + product.MergeWithCombination(orderItem.AttributesXml, _productAttributeParser); if (_shoppingCartSettings.ShowDeliveryTimes && product.IsShipEnabled) { @@ -164,16 +229,29 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId) } sb.AppendLine(string.Format("", _templatesSettings.Color2)); - //product name - string productName = product.GetLocalized(x => x.Name, languageId); + + var productName = product.GetLocalized(x => x.Name, language.Id); + var productUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.AttributesXml, product.Id, product.GetSeName()); + + sb.AppendLine(""); @@ -228,9 +306,9 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId) sb.AppendLine(string.Format("", unitPriceStr)); var quantityUnit = _quantityUnitService.GetQuantityUnitById(product.QuantityUnitId); + sb.AppendLine(string.Format("", - orderItem.Quantity, - quantityUnit == null ? "" : quantityUnit.GetLocalized(x => x.Name))); + orderItem.Quantity, quantityUnit == null ? "" : quantityUnit.GetLocalized(x => x.Name))); string priceStr = string.Empty; switch (order.CustomerTaxDisplayType) @@ -252,6 +330,7 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId) sb.AppendLine(""); } + #endregion #region Checkout Attributes @@ -259,8 +338,6 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId) if (!String.IsNullOrEmpty(order.CheckoutAttributeDescription)) { sb.AppendLine(""); } @@ -278,6 +355,7 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId) string cusTaxTotal = string.Empty; string cusDiscount = string.Empty; string cusTotal = string.Empty; + //subtotal, shipping, payment method fee switch (order.CustomerTaxDisplayType) { @@ -376,41 +454,34 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId) var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); cusTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); - - - //subtotal - //sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.SubTotal", languageId), cusSubTotal)); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.SubTotal", languageId), cusSubTotal)); + sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.SubTotal", language.Id), cusSubTotal)); //discount (applied to order subtotal) if (dislaySubTotalDiscount) { - //sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.SubTotalDiscount", languageId), cusSubTotalDiscount)); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.SubTotalDiscount", languageId), cusSubTotalDiscount)); + sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.SubTotalDiscount", language.Id), cusSubTotalDiscount)); } //shipping if (dislayShipping) { - //sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.Shipping", languageId), cusShipTotal)); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Shipping", languageId), cusShipTotal)); + sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Shipping", language.Id), cusShipTotal)); } //payment method fee if (displayPaymentMethodFee) { - string paymentMethodFeeTitle = _localizationService.GetResource("Messages.Order.PaymentMethodAdditionalFee", languageId); - //sb.AppendLine(string.Format("", _templatesSettings.Color3, paymentMethodFeeTitle, cusPaymentMethodAdditionalFee)); - sb.AppendLine(string.Format("", paymentMethodFeeTitle, cusPaymentMethodAdditionalFee)); + string paymentMethodFeeTitle = _localizationService.GetResource("Messages.Order.PaymentMethodAdditionalFee", language.Id); + + sb.AppendLine(string.Format("", paymentMethodFeeTitle, cusPaymentMethodAdditionalFee)); } //tax if (displayTax) { - //sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.Tax", languageId), cusTaxTotal)); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Tax", languageId), cusTaxTotal)); + sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Tax", language.Id), cusTaxTotal)); } if (displayTaxRates) { @@ -418,45 +489,50 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId) { 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("", _templatesSettings.Color3, taxRate, taxValue)); - sb.AppendLine(string.Format("", taxRate, taxValue)); + + sb.AppendLine(string.Format("", taxRate, taxValue)); } } //discount if (dislayDiscount) { - //sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.TotalDiscount", languageId), cusDiscount)); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.TotalDiscount", languageId), cusDiscount)); + 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", languageId), HttpUtility.HtmlEncode(gcuh.GiftCard.GiftCardCouponCode)); + 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); - //sb.AppendLine(string.Format("", _templatesSettings.Color3, giftCardText, giftCardAmount)); - sb.AppendLine(string.Format("", giftCardText, giftCardAmount)); + + 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", languageId), -order.RedeemedRewardPointsEntry.Points); + 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("", _templatesSettings.Color3, rpTitle, rpAmount)); - sb.AppendLine(string.Format("", rpTitle, rpAmount)); + + sb.AppendLine(string.Format("", rpTitle, rpAmount)); } //total - sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.OrderTotal", languageId), cusTotal)); - #endregion + sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.OrderTotal", language.Id), cusTotal)); + + #endregion sb.AppendLine("
{0}{0}{0}{0}
{0}{0}{0}{0}
"); - sb.AppendLine("" + HttpUtility.HtmlEncode(productName)); - //add download link - if (_downloadService.IsDownloadAllowed(orderItem)) + 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}", _webHelper.GetStoreLocation(false), orderItem.OrderItemGuid); - string downloadLink = string.Format("{1}", downloadUrl, _localizationService.GetResource("Messages.Order.Product(s).Download", languageId)); + 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(")"); @@ -186,7 +264,7 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId) sb.AppendLine("
"); sb.AppendLine("
"); - sb.AppendLine("" + _localizationService.GetResource("Products.DeliveryTime") + ""); + sb.AppendLine("" + _localizationService.GetResource("Products.DeliveryTime", language.Id) + ""); sb.AppendLine(""); sb.AppendLine("" + deliveryTimeName + ""); sb.AppendLine("
"); @@ -204,7 +282,7 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId) if (!String.IsNullOrEmpty(product.Sku)) { sb.AppendLine("
"); - sb.AppendLine(string.Format(_localizationService.GetResource("Messages.Order.Product(s).SKU", languageId), HttpUtility.HtmlEncode(product.Sku))); + sb.AppendLine(string.Format(_localizationService.GetResource("Messages.Order.Product(s).SKU", language.Id), HttpUtility.HtmlEncode(product.Sku))); } } sb.AppendLine("
{0}{0} {1}
 "); - //codehint: sm-edit - //sb.AppendLine(order.CheckoutAttributeDescription); sb.AppendLine(HtmlUtils.ConvertPlainTextToTable(HtmlUtils.ConvertHtmlToPlainText(order.CheckoutAttributeDescription))); sb.AppendLine("
 {1} {2}
 {0} {1}
 {0} {1}
 {1} {2}
 {0} {1}
 {0} {1}
 {1} {2}
 {0} {1}
 {0} {1}
 {1} {2}
 {0} {1}
 {0} {1}
 {1} {2}
 {0} {1}
 {0} {1}
 {1} {2}
 {0} {1}
 {0} {1}
 {1} {2}
 {0} {1}
 {0} {1}
 {1} {2}
 {0} {1}
 {0}
{1}
{2}
 {1} {2}
 {0} {1}
 {0} {1}
 {1}{2}
 {1}{2}
"); - result = sb.ToString(); - return result; - } + + return sb.ToString(); + } /// /// Convert a collection to a HTML table @@ -464,17 +540,17 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId) /// Shipment /// Language identifier /// HTML table of products - protected virtual string ProductListToHtmlTable(Shipment shipment, int languageId) + protected virtual string ProductListToHtmlTable(Shipment shipment, Language language) { - var result = ""; - 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", languageId))); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Product(s).Quantity", languageId))); + 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(); @@ -490,12 +566,15 @@ protected virtual string ProductListToHtmlTable(Shipment shipment, int languageI continue; sb.AppendLine(string.Format("", _templatesSettings.Color2)); - //product name - string productName = product.GetLocalized(x => x.Name, languageId); - sb.AppendLine(""); @@ -517,14 +596,14 @@ protected virtual string ProductListToHtmlTable(Shipment shipment, int languageI sb.AppendLine(""); } + #endregion sb.AppendLine("
{0}{0}{0}{0}
" + HttpUtility.HtmlEncode(productName)); - //attributes - if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) + var productName = product.GetLocalized(x => x.Name, language.Id); + var productUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.AttributesXml, product.Id, product.GetSeName()); + + sb.AppendLine(""); + sb.AppendLine("{1}".FormatInvariant(productUrl, HttpUtility.HtmlEncode(productName))); + + //attributes + if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) { sb.AppendLine("
"); sb.AppendLine(orderItem.AttributeDescription); @@ -508,7 +587,7 @@ protected virtual string ProductListToHtmlTable(Shipment shipment, int languageI if (!String.IsNullOrEmpty(product.Sku)) { sb.AppendLine("
"); - sb.AppendLine(string.Format(_localizationService.GetResource("Messages.Order.Product(s).SKU", languageId), HttpUtility.HtmlEncode(product.Sku))); + sb.AppendLine(string.Format(_localizationService.GetResource("Messages.Order.Product(s).SKU", language.Id), HttpUtility.HtmlEncode(product.Sku))); } } sb.AppendLine("
"); - result = sb.ToString(); - return result; - } - //codehint: sm-add + return sb.ToString(); + } + protected virtual string TopicToHtml(string systemName, int languageId) { var result = ""; @@ -557,7 +636,7 @@ protected virtual string GetSupplierIdentification() sb.AppendLine(""); sb.AppendLine(""); - sb.AppendLine(" + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - + + + + + + + + + + + + - - + + +
"); + sb.AppendLine(""); sb.AppendLine(String.Format("{0}
", _companyInfoSettings.CompanyName )); @@ -600,7 +679,7 @@ protected virtual string GetSupplierIdentification() sb.AppendLine("
"); - sb.AppendLine(""); + sb.AppendLine(""); if (!String.IsNullOrEmpty(_storeContext.CurrentStore.Url)) { @@ -621,7 +700,7 @@ protected virtual string GetSupplierIdentification() sb.AppendLine(""); - sb.AppendLine(""); + sb.AppendLine(""); if (!String.IsNullOrEmpty(_bankConnectionSettings.Bankname)) { @@ -669,15 +748,11 @@ 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.GetEmailAccountById(_emailAccountSettings.DefaultEmailAccountId); - if (defaultEmailAccount == null) - defaultEmailAccount = _emailAccountService.GetAllEmailAccounts().FirstOrDefault(); + var defaultEmailAccount = _emailAccountService.GetDefaultEmailAccount(); tokens.Add(new Token("Store.SupplierIdentification", GetSupplierIdentification(), true)); tokens.Add(new Token("Store.Email", defaultEmailAccount.Email)); } - //codehint: sm-add begin - public virtual void AddCompanyTokens(IList tokens) { tokens.Add(new Token("Company.CompanyName", _companyInfoSettings.CompanyName)); @@ -720,28 +795,26 @@ public virtual void AddContactDataTokens(IList tokens) tokens.Add(new Token("Contact.ContactEmailAddress", _contactDataSettings.ContactEmailAddress)); } - //codehint: sm-add end - - public virtual void AddOrderTokens(IList tokens, Order order, int languageId) + public virtual void AddOrderTokens(IList tokens, Order order, Language language) { - tokens.Add(new Token("Order.OrderNumber", order.GetOrderNumber())); + 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.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.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.ShippingFirstName", order.ShippingAddress != null ? order.ShippingAddress.FirstName : "")); @@ -757,14 +830,22 @@ public virtual void AddOrderTokens(IList tokens, Order order, int languag 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) : "")); - var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); - var paymentMethodName = paymentMethod != null ? GetLocalizedValue(paymentMethod.Metadata, "FriendlyName", x => x.FriendlyName) : order.PaymentMethodSystemName; - tokens.Add(new Token("Order.PaymentMethod", paymentMethodName)); + 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, languageId), true)); + tokens.Add(new Token("Order.Product(s)", ProductListToHtmlTable(order, language), true)); tokens.Add(new Token("Order.CustomerComment", order.CustomerOrderComment, true)); - var language = _languageService.GetLanguageById(languageId); if (language != null && !String.IsNullOrEmpty(language.LanguageCulture)) { DateTime createdOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, TimeZoneInfo.Utc, _dateTimeHelper.GetCustomerTimeZone(order.Customer)); @@ -775,11 +856,11 @@ public virtual void AddOrderTokens(IList tokens, Order order, int languag 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) + // 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", languageId), true)); - tokens.Add(new Token("Order.ConditionsOfUse", TopicToHtml("ConditionsOfUse", languageId), 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); @@ -802,11 +883,11 @@ private string GetLocalizedValue(ProviderMetadata metadata, string propertyName, return result; } - public virtual void AddShipmentTokens(IList tokens, Shipment shipment, int languageId) + 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, languageId), true)); + 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 @@ -846,14 +927,27 @@ public virtual void AddReturnRequestTokens(IList tokens, ReturnRequest re public virtual void AddGiftCardTokens(IList tokens, GiftCard giftCard) { - tokens.Add(new Token("GiftCard.SenderName", giftCard.SenderName)); + 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)); + tokens.Add(new Token("GiftCard.CouponCode", giftCard.GiftCardCouponCode)); - var giftCardMesage = !String.IsNullOrWhiteSpace(giftCard.Message) ? + var giftCardMesage = !String.IsNullOrWhiteSpace(giftCard.Message) ? HtmlUtils.FormatText(giftCard.Message, false, true, false, false, false, false) : ""; tokens.Add(new Token("GiftCard.Message", giftCardMesage, true)); @@ -864,13 +958,13 @@ public virtual void AddGiftCardTokens(IList tokens, GiftCard giftCard) public virtual void AddCustomerTokens(IList tokens, Customer customer) { - tokens.Add(new Token("Customer.Email", customer.Email)); + 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(), @@ -928,20 +1022,36 @@ public virtual void AddNewsCommentTokens(IList tokens, NewsComment newsCo _eventPublisher.EntityTokensAdded(newsComment, tokens); } - public virtual void AddProductTokens(IList tokens, Product product, int languageId) + 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", product.GetLocalized(x => x.Name, languageId))); - tokens.Add(new Token("Product.ShortDescription", product.GetLocalized(x => x.ShortDescription, languageId), true)); + 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())); - - // TODO: add a method for getting URL (use routing because it handles all SEO friendly URLs) - var productUrl = string.Format("{0}{1}", _webHelper.GetStoreLocation(false), product.GetSeName()); tokens.Add(new Token("Product.ProductURLForCustomer", productUrl, true)); - //event notification - _eventPublisher.EntityTokensAdded(product, tokens); + 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, @@ -971,12 +1081,12 @@ public virtual void AddForumPostTokens(IList tokens, ForumPost forumPost) _eventPublisher.EntityTokensAdded(forumPost, tokens); } - public virtual void AddForumTokens(IList tokens, Forum forum) + 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()); + 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.Name)); + tokens.Add(new Token("Forums.ForumName", forum.GetLocalized(x => x.Name, language.Id))); //event notification _eventPublisher.EntityTokensAdded(forum, tokens); @@ -993,7 +1103,15 @@ public virtual void AddPrivateMessageTokens(IList tokens, PrivateMessage public virtual void AddBackInStockTokens(IList tokens, BackInStockSubscription subscription) { - tokens.Add(new Token("BackInStockSubscription.ProductName", subscription.Product.Name)); + 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); @@ -1013,7 +1131,6 @@ public virtual string[] GetListOfCampaignAllowedTokens() "%NewsLetterSubscription.Email%", "%NewsLetterSubscription.ActivationUrl%", "%NewsLetterSubscription.DeactivationUrl%", - //codehint: sm-add "%Store.SupplierIdentification%", }; return allowedTokens.ToArray(); @@ -1067,7 +1184,9 @@ public virtual string[] GetListOfAllowedTokens() "%Product.ShortDescription%", "%Product.ProductURLForCustomer%", "%Product.StockQuantity%", - "%RecurringPayment.ID%", + "%Product.AdditionalShippingCharge%", + "%Product.Thumbnail%", + "%RecurringPayment.ID%", "%Shipment.ShipmentNumber%", "%Shipment.TrackingNumber%", "%Shipment.Product(s)%", @@ -1084,8 +1203,9 @@ public virtual string[] GetListOfAllowedTokens() "%GiftCard.SenderEmail%", "%GiftCard.RecipientName%", "%GiftCard.RecipientEmail%", - "%GiftCard.Amount%", - "%GiftCard.CouponCode%", + "%GiftCard.Amount%", + "%GiftCard.RemainingAmount%", + "%GiftCard.CouponCode%", "%GiftCard.Message%", "%Customer.Email%", "%Customer.Username%", diff --git a/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs b/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs index 74bd6a41c3..dde1d076c9 100644 --- a/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs +++ b/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs @@ -1,167 +1,25 @@ using System; -using System.Collections.Generic; -using System.IO; using System.Linq; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Messages; -using SmartStore.Data; using SmartStore.Core.Events; -using SmartStore.Services.Stores; namespace SmartStore.Services.Messages { - - public class NewsLetterSubscriptionService : INewsLetterSubscriptionService + public class NewsLetterSubscriptionService : INewsLetterSubscriptionService { private readonly IEventPublisher _eventPublisher; private readonly IDbContext _context; private readonly IRepository _subscriptionRepository; - private readonly IStoreService _storeService; public NewsLetterSubscriptionService(IDbContext context, IRepository subscriptionRepository, - IEventPublisher eventPublisher, - IStoreService storeService) + IEventPublisher eventPublisher) { _context = context; _subscriptionRepository = subscriptionRepository; _eventPublisher = eventPublisher; - _storeService = storeService; - } - - public ImportResult ImportSubscribers(Stream stream) - { - Guard.ArgumentNotNull(() => stream); - - var result = new ImportResult(); - var toAdd = new List(); - var toUpdate = new List(); - var autoCommit = _subscriptionRepository.AutoCommitEnabled; - var validateOnSave = _subscriptionRepository.Context.ValidateOnSaveEnabled; - var autoDetectChanges = _subscriptionRepository.Context.AutoDetectChangesEnabled; - var proxyCreation = _subscriptionRepository.Context.ProxyCreationEnabled; - - try - { - using (var reader = new StreamReader(stream)) - { - _subscriptionRepository.Context.ValidateOnSaveEnabled = false; - _subscriptionRepository.Context.AutoDetectChangesEnabled = false; - _subscriptionRepository.Context.ProxyCreationEnabled = false; - - while (!reader.EndOfStream) - { - string line = reader.ReadLine(); - if (line.IsEmpty()) - { - continue; - } - string[] tmp = line.Split(','); - - var email = ""; - bool isActive = true; - int storeId = 0; - - // parse - if (tmp.Length == 1) - { - // "email" only - email = tmp[0].Trim(); - } - else if (tmp.Length == 2) - { - // "email" and "active" fields specified - email = tmp[0].Trim(); - isActive = Boolean.Parse(tmp[1].Trim()); - } - else if (tmp.Length == 3) - { - email = tmp[0].Trim(); - isActive = Boolean.Parse(tmp[1].Trim()); - storeId = int.Parse(tmp[2].Trim()); - } - else - { - throw new SmartException("Wrong file format (expected comma separated entries 'Email' and optionally 'IsActive')"); - } - - result.TotalRecords++; - - if (email.Length > 255) - { - result.AddWarning("The emal address '{0}' exceeds the maximun allowed length of 255.".FormatInvariant(email)); - continue; - } - - if (!email.IsEmail()) - { - result.AddWarning("'{0}' is not a valid email address.".FormatInvariant(email)); - continue; - } - - if (storeId == 0) - { - storeId = _storeService.GetAllStores().First().Id; - } - - // import - var subscription = (from nls in _subscriptionRepository.Table - where nls.Email == email && nls.StoreId == storeId - orderby nls.Id - select nls).FirstOrDefault(); - - if (subscription != null) - { - subscription.Active = isActive; - - toUpdate.Add(subscription); - result.ModifiedRecords++; - } - else - { - subscription = new NewsLetterSubscription() - { - Active = isActive, - CreatedOnUtc = DateTime.UtcNow, - Email = email, - NewsLetterSubscriptionGuid = Guid.NewGuid(), - StoreId = storeId - }; - - toAdd.Add(subscription); - result.NewRecords++; - } - } - } - - // insert new subscribers - _subscriptionRepository.AutoCommitEnabled = true; - _subscriptionRepository.InsertRange(toAdd, 500); - toAdd.Clear(); - - // update modified subscribers - _subscriptionRepository.AutoCommitEnabled = false; - toUpdate.Each(x => - { - _subscriptionRepository.Update(x); - }); - _subscriptionRepository.Context.SaveChanges(); - toUpdate.Clear(); - } - catch (Exception ex) - { - throw ex; - } - finally - { - _subscriptionRepository.AutoCommitEnabled = autoCommit; - _subscriptionRepository.Context.ValidateOnSaveEnabled = validateOnSave; - _subscriptionRepository.Context.AutoDetectChangesEnabled = autoDetectChanges; - _subscriptionRepository.Context.ProxyCreationEnabled = proxyCreation; - } - - return result; } /// @@ -255,7 +113,8 @@ 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"); + if (newsLetterSubscription == null) + throw new ArgumentNullException("newsLetterSubscription"); _subscriptionRepository.Delete(newsLetterSubscription); @@ -266,12 +125,52 @@ public virtual void DeleteNewsLetterSubscription(NewsLetterSubscription newsLett _eventPublisher.EntityDeleted(newsLetterSubscription); } - /// - /// Gets a newsletter subscription by newsletter subscription identifier - /// - /// The newsletter subscription identifier - /// NewsLetter subscription - public virtual NewsLetterSubscription GetNewsLetterSubscriptionById(int newsLetterSubscriptionId) + public virtual bool? AddNewsLetterSubscriptionFor(bool add, string email, int storeId) + { + bool? result = null; + + if (email.IsEmail()) + { + var newsletter = GetNewsLetterSubscriptionByEmail(email, storeId); + if (newsletter != null) + { + if (add) + { + newsletter.Active = true; + UpdateNewsLetterSubscription(newsletter); + result = true; + } + else + { + DeleteNewsLetterSubscription(newsletter); + result = false; + } + } + else + { + if (add) + { + InsertNewsLetterSubscription(new NewsLetterSubscription + { + NewsLetterSubscriptionGuid = Guid.NewGuid(), + Email = email, + Active = true, + CreatedOnUtc = DateTime.UtcNow, + StoreId = storeId + }); + result = true; + } + } + } + return result; + } + + /// + /// Gets a newsletter subscription by newsletter subscription identifier + /// + /// The newsletter subscription identifier + /// NewsLetter subscription + public virtual NewsLetterSubscription GetNewsLetterSubscriptionById(int newsLetterSubscriptionId) { if (newsLetterSubscriptionId == 0) return null; diff --git a/src/Libraries/SmartStore.Services/Messages/QueuedEmailService.cs b/src/Libraries/SmartStore.Services/Messages/QueuedEmailService.cs index 62c693cee1..b03c247f39 100644 --- a/src/Libraries/SmartStore.Services/Messages/QueuedEmailService.cs +++ b/src/Libraries/SmartStore.Services/Messages/QueuedEmailService.cs @@ -1,33 +1,47 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Net.Mail; +using System.IO; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Email; using SmartStore.Core.Events; +using SmartStore.Core.Logging; +using SmartStore.Services.Localization; +using SmartStore.Utilities; +using System.Web; +using SmartStore.Core.Localization; namespace SmartStore.Services.Messages { public partial class QueuedEmailService : IQueuedEmailService { private readonly IRepository _queuedEmailRepository; - private readonly IEventPublisher _eventPublisher; - - /// - /// Ctor - /// - /// Queued email repository - /// Event published - public QueuedEmailService(IRepository queuedEmailRepository, IEventPublisher eventPublisher) + private readonly IRepository _queuedEmailAttachmentRepository; + private readonly IEmailSender _emailSender; + private readonly ICommonServices _services; + + public QueuedEmailService( + IRepository queuedEmailRepository, + IRepository queuedEmailAttachmentRepository, + IEmailSender emailSender, + ICommonServices services) { - _queuedEmailRepository = queuedEmailRepository; - _eventPublisher = eventPublisher; + this._queuedEmailRepository = queuedEmailRepository; + this._queuedEmailAttachmentRepository = queuedEmailAttachmentRepository; + this._emailSender = emailSender; + this._services = services; + + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; } - /// - /// Inserts a queued email - /// - /// Queued email + public Localizer T { get; set; } + public ILogger Logger { get; set; } + public virtual void InsertQueuedEmail(QueuedEmail queuedEmail) { if (queuedEmail == null) @@ -36,13 +50,9 @@ public virtual void InsertQueuedEmail(QueuedEmail queuedEmail) _queuedEmailRepository.Insert(queuedEmail); //event notification - _eventPublisher.EntityInserted(queuedEmail); + _services.EventPublisher.EntityInserted(queuedEmail); } - /// - /// Updates a queued email - /// - /// Queued email public virtual void UpdateQueuedEmail(QueuedEmail queuedEmail) { if (queuedEmail == null) @@ -51,13 +61,9 @@ public virtual void UpdateQueuedEmail(QueuedEmail queuedEmail) _queuedEmailRepository.Update(queuedEmail); //event notification - _eventPublisher.EntityUpdated(queuedEmail); + _services.EventPublisher.EntityUpdated(queuedEmail); } - /// - /// Deleted a queued email - /// - /// Queued email public virtual void DeleteQueuedEmail(QueuedEmail queuedEmail) { if (queuedEmail == null) @@ -66,14 +72,15 @@ public virtual void DeleteQueuedEmail(QueuedEmail queuedEmail) _queuedEmailRepository.Delete(queuedEmail); //event notification - _eventPublisher.EntityDeleted(queuedEmail); + _services.EventPublisher.EntityDeleted(queuedEmail); } - /// - /// Gets a queued email by identifier - /// - /// Queued email identifier - /// Queued email + public virtual int DeleteAllQueuedEmails() + { + // do not delete e-mails which are about to be sent + return _queuedEmailRepository.DeleteAll(x => x.SentOnUtc.HasValue || x.SentTries >= 3); + } + public virtual QueuedEmail GetQueuedEmailById(int queuedEmailId) { if (queuedEmailId == 0) @@ -81,14 +88,8 @@ public virtual QueuedEmail GetQueuedEmailById(int queuedEmailId) var queuedEmail = _queuedEmailRepository.GetById(queuedEmailId); return queuedEmail; - } - /// - /// Get queued emails by identifiers - /// - /// queued email identifiers - /// Queued emails public virtual IList GetQueuedEmailsByIds(int[] queuedEmailIds) { if (queuedEmailIds == null || queuedEmailIds.Length == 0) @@ -97,9 +98,12 @@ public virtual IList GetQueuedEmailsByIds(int[] queuedEmailIds) var query = from qe in _queuedEmailRepository.Table where queuedEmailIds.Contains(qe.Id) select qe; + var queuedEmails = query.ToList(); - //sort by passed identifiers + + // sort by passed identifiers var sortedQueuedEmails = new List(); + foreach (int id in queuedEmailIds) { var queuedEmail = queuedEmails.Find(x => x.Id == id); @@ -109,46 +113,195 @@ where queuedEmailIds.Contains(qe.Id) return sortedQueuedEmails; } - /// - /// Gets all queued emails - /// - /// From Email - /// To Email - /// The start time - /// The end time - /// A value indicating whether to load only not sent emails - /// Maximum send tries - /// A value indicating whether we should sort queued email descending; otherwise, ascending. - /// Page index - /// Page size - /// Email item list - public virtual IPagedList SearchEmails(string fromEmail, - string toEmail, DateTime? startTime, DateTime? endTime, - bool loadNotSentItemsOnly, int maxSendTries, - bool loadNewest, int pageIndex, int pageSize) + public virtual IPagedList SearchEmails(SearchEmailsQuery query) { - fromEmail = (fromEmail ?? String.Empty).Trim(); - toEmail = (toEmail ?? String.Empty).Trim(); + Guard.ArgumentNotNull(() => query); - var query = _queuedEmailRepository.Table; - if (!String.IsNullOrEmpty(fromEmail)) - query = query.Where(qe => qe.From.Contains(fromEmail)); - if (!String.IsNullOrEmpty(toEmail)) - query = query.Where(qe => qe.To.Contains(toEmail)); - if (startTime.HasValue) - query = query.Where(qe => qe.CreatedOnUtc >= startTime); - if (endTime.HasValue) - query = query.Where(qe => qe.CreatedOnUtc <= endTime); - if (loadNotSentItemsOnly) - query = query.Where(qe => !qe.SentOnUtc.HasValue); - query = query.Where(qe => qe.SentTries < maxSendTries); - query = query.OrderByDescending(qe => qe.Priority); - query = loadNewest ? - ((IOrderedQueryable)query).ThenByDescending(qe => qe.CreatedOnUtc) : - ((IOrderedQueryable)query).ThenBy(qe => qe.CreatedOnUtc); - - var queuedEmails = new PagedList(query, pageIndex, pageSize); + var q = _queuedEmailRepository.Table; + + if (query.Expand.HasValue()) + { + var expands = query.Expand.Split(','); + foreach (var expand in expands) + { + q = q.Expand(expand.Trim()); + } + } + + if (query.From.HasValue()) + q = q.Where(qe => qe.From.Contains(query.From.Trim())); + + if (query.To.HasValue()) + q = q.Where(qe => qe.To.Contains(query.To.Trim())); + + if (query.StartTime.HasValue) + q = q.Where(qe => qe.CreatedOnUtc >= query.StartTime); + + if (query.EndTime.HasValue) + q = q.Where(qe => qe.CreatedOnUtc <= query.EndTime); + + if (query.UnsentOnly) + q = q.Where(qe => !qe.SentOnUtc.HasValue); + + if (query.SendManually.HasValue) + q = q.Where(qe => qe.SendManually == query.SendManually.Value); + + q = q.Where(qe => qe.SentTries < query.MaxSendTries); + + q = q.OrderByDescending(qe => qe.Priority); + + q = query.OrderByLatest ? + ((IOrderedQueryable)q).ThenByDescending(qe => qe.CreatedOnUtc) : + ((IOrderedQueryable)q).ThenBy(qe => qe.CreatedOnUtc); + + var queuedEmails = new PagedList(q, query.PageIndex, query.PageSize); return queuedEmails; } - } + + public virtual bool SendEmail(QueuedEmail queuedEmail) + { + var result = false; + + try + { + var smtpContext = new SmtpContext(queuedEmail.EmailAccount); + var msg = ConvertEmail(queuedEmail); + + _emailSender.SendEmail(smtpContext, msg); + + queuedEmail.SentOnUtc = DateTime.UtcNow; + result = true; + } + catch (Exception exc) + { + Logger.Error(string.Concat(T("Admin.Common.ErrorSendingEmail"), ": ", exc.Message), exc); + } + finally + { + queuedEmail.SentTries = queuedEmail.SentTries + 1; + UpdateQueuedEmail(queuedEmail); + } + + return result; + } + + private void AddEmailAddresses(string addresses, ICollection target) + { + var arr = addresses.IsEmpty() ? null : addresses.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + if (arr != null && arr.Length > 0) + { + target.AddRange(arr.Where(x => x.Trim().HasValue()).Select(x => new EmailAddress(x))); + } + } + + internal EmailMessage ConvertEmail(QueuedEmail qe) + { + // 'internal' for testing purposes + + var msg = new EmailMessage( + new EmailAddress(qe.To, qe.ToName), + qe.Subject, + qe.Body, + new EmailAddress(qe.From, qe.FromName)); + + if (qe.ReplyTo.HasValue()) + { + msg.ReplyTo.Add(new EmailAddress(qe.ReplyTo, qe.ReplyToName)); + } + + AddEmailAddresses(qe.CC, msg.Cc); + AddEmailAddresses(qe.Bcc, msg.Bcc); + + if (qe.Attachments != null && qe.Attachments.Count > 0) + { + foreach (var qea in qe.Attachments) + { + Attachment attachment = null; + + if (qea.StorageLocation == EmailAttachmentStorageLocation.Blob) + { + var data = qea.Data; + if (data != null && data.Length > 0) + { + attachment = new Attachment(data.ToStream(), qea.Name, qea.MimeType); + } + } + else if (qea.StorageLocation == EmailAttachmentStorageLocation.Path) + { + var path = qea.Path; + if (path.HasValue()) + { + if (path[0] == '~' || path[0] == '/') + { + path = CommonHelper.MapPath(VirtualPathUtility.ToAppRelative(path), false); + } + if (File.Exists(path)) + { + attachment = new Attachment(path, qea.MimeType); + attachment.Name = qea.Name; + } + } + } + else if (qea.StorageLocation == EmailAttachmentStorageLocation.FileReference) + { + var file = qea.File; + if (file != null && file.UseDownloadUrl == false && file.DownloadBinary != null && file.DownloadBinary.Length > 0) + { + attachment = new Attachment(file.DownloadBinary.ToStream(), file.Filename + file.Extension, file.ContentType); + } + } + + if (attachment != null) + { + msg.Attachments.Add(attachment); + } + } + } + + return msg; + } + + #region Attachments + + public virtual QueuedEmailAttachment GetQueuedEmailAttachmentById(int id) + { + if (id == 0) + return null; + + var qea = _queuedEmailAttachmentRepository.GetById(id); + return qea; + } + + public virtual void DeleteQueuedEmailAttachment(QueuedEmailAttachment qea) + { + if (qea == null) + throw new ArgumentNullException("qea"); + + _queuedEmailAttachmentRepository.Delete(qea); + + _services.EventPublisher.EntityDeleted(qea); + } + + #endregion + } + + public class SearchEmailsQuery + { + public string From { get; set; } + public string To { get; set; } + public DateTime? StartTime { get; set; } + public DateTime? EndTime { get; set; } + public bool UnsentOnly { get; set; } + public int MaxSendTries { get; set; } + public bool OrderByLatest { get; set; } + public int PageIndex { get; set; } + public int PageSize { get; set; } + public bool? SendManually { get; set; } + + /// + /// Navigation properties to eager load (comma separataed) + /// + public string Expand { get; set; } + } + } diff --git a/src/Libraries/SmartStore.Services/Messages/QueuedMessagesClearTask.cs b/src/Libraries/SmartStore.Services/Messages/QueuedMessagesClearTask.cs new file mode 100644 index 0000000000..b31f3b4ce3 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/QueuedMessagesClearTask.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using SmartStore.Core.Data; +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 + /// + public partial class QueuedMessagesClearTask : ITask + { + private readonly IRepository _qeRepository; + + public QueuedMessagesClearTask(IRepository qeRepository) + { + this._qeRepository = qeRepository; + } + + public void Execute(TaskExecutionContext ctx) + { + var olderThan = DateTime.UtcNow.AddDays(-14); + _qeRepository.DeleteAll(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 1d8474f259..a37e7e82c4 100644 --- a/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs +++ b/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using SmartStore.Core.Email; -using SmartStore.Core.Logging; -using SmartStore.Services.Tasks; +using SmartStore.Services.Tasks; namespace SmartStore.Services.Messages { @@ -14,73 +8,37 @@ namespace SmartStore.Services.Messages public partial class QueuedMessagesSendTask : ITask { private readonly IQueuedEmailService _queuedEmailService; - private readonly IEmailSender _emailSender; - public QueuedMessagesSendTask(IQueuedEmailService queuedEmailService, IEmailSender emailSender) + public QueuedMessagesSendTask(IQueuedEmailService queuedEmailService) { - this._queuedEmailService = queuedEmailService; - this._emailSender = emailSender; - Logger = NullLogger.Instance; + _queuedEmailService = queuedEmailService; } - public ILogger Logger - { - get; - set; - } - - /// - /// Executes a task - /// public void Execute(TaskExecutionContext ctx) { - var maxTries = 3; - var queuedEmails = _queuedEmailService.SearchEmails(null, null, null, null, true, maxTries, false, 0, 10000); - - foreach (var qe in queuedEmails) - { - var bcc = String.IsNullOrWhiteSpace(qe.Bcc) - ? null - : qe.Bcc.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - var cc = String.IsNullOrWhiteSpace(qe.CC) - ? null - : qe.CC.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - - try - { - var smtpContext = new SmtpContext(qe.EmailAccount); - - var msg = new EmailMessage( - new EmailAddress(qe.To, qe.ToName), - qe.Subject, - qe.Body, - new EmailAddress(qe.From, qe.FromName)); - - if (qe.ReplyTo.HasValue()) - { - msg.ReplyTo.Add(new EmailAddress(qe.ReplyTo, qe.ReplyToName)); - } - - if (cc != null) - msg.Cc.AddRange(cc.Where(x => x.HasValue()).Select(x => new EmailAddress(x))); - - if (bcc != null) - msg.Bcc.AddRange(bcc.Where(x => x.HasValue()).Select(x => new EmailAddress(x))); - - _emailSender.SendEmail(smtpContext, msg); - - qe.SentOnUtc = DateTime.UtcNow; - } - catch (Exception exc) - { - Logger.Error(string.Format("Error sending e-mail: {0}", exc.Message), exc); - } - finally - { - qe.SentTries = qe.SentTries + 1; - _queuedEmailService.UpdateQueuedEmail(qe); - } - } + const int pageSize = 1000; + const int maxTries = 3; + + for (int i = 0; i < 9999999; ++i) + { + var q = new SearchEmailsQuery + { + MaxSendTries = maxTries, + PageIndex = i, + PageSize = pageSize, + Expand = "Attachments", + UnsentOnly = true + }; + var queuedEmails = _queuedEmailService.SearchEmails(q); + + foreach (var queuedEmail in queuedEmails) + { + _queuedEmailService.SendEmail(queuedEmail); + } + + if (!queuedEmails.HasNextPage) + break; + } } } } diff --git a/src/Libraries/SmartStore.Services/Messages/QueuingEmailEvent.cs b/src/Libraries/SmartStore.Services/Messages/QueuingEmailEvent.cs new file mode 100644 index 0000000000..d9efdacd84 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/QueuingEmailEvent.cs @@ -0,0 +1,42 @@ +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 new file mode 100644 index 0000000000..11eca682cc --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/QueuingEmailEventConsumer.cs @@ -0,0 +1,97 @@ +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/WorkflowMessageService.cs b/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs index 2a1f760ecd..1f2746245e 100644 --- a/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs +++ b/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs @@ -1,22 +1,25 @@ 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.Services.Customers; using SmartStore.Core.Events; +using SmartStore.Core.Localization; +using SmartStore.Services.Customers; using SmartStore.Services.Localization; +using SmartStore.Services.Media; using SmartStore.Services.Stores; -using System.Web; namespace SmartStore.Services.Messages { @@ -36,21 +39,26 @@ public partial class WorkflowMessageService : IWorkflowMessageService 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, + 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) + HttpRequestBase httpRequest, + IDownloadService downloadServioce) { this._messageTemplateService = messageTemplateService; this._queuedEmailService = queuedEmailService; @@ -63,18 +71,23 @@ public WorkflowMessageService(IMessageTemplateService messageTemplateService, this._emailAccountSettings = emailAccountSettings; this._eventPublisher = eventPublisher; this._workContext = workContext; - _httpRequest = httpRequest; - } + this._httpRequest = httpRequest; + this._downloadServioce = downloadServioce; - #endregion + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } - #region Utilities + #endregion - protected int SendNotification( + #region Utilities + + protected int SendNotification( MessageTemplate messageTemplate, EmailAccount emailAccount, int languageId, - IEnumerable tokens, + IList tokens, string toEmailAddress, string toName, string replyTo = null, @@ -90,8 +103,8 @@ protected int SendNotification( var bodyReplaced = _tokenizer.Replace(body, tokens, true); bodyReplaced = WebHelper.MakeAllUrlsAbsolute(bodyReplaced, _httpRequest); - - var email = new QueuedEmail() + + var email = new QueuedEmail { Priority = 5, From = emailAccount.Email, @@ -105,20 +118,56 @@ protected int SendNotification( Subject = subjectReplaced, Body = bodyReplaced, CreatedOnUtc = DateTime.UtcNow, - EmailAccountId = emailAccount.Id + 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 GetLocalizedActiveMessageTemplate(string messageTemplateName, - int languageId, int storeId) + protected MessageTemplate GetActiveMessageTemplate(string messageTemplateName, int storeId) { - //TODO remove languageId parameter var messageTemplate = _messageTemplateService.GetMessageTemplateByName(messageTemplateName, storeId); - //no template found if (messageTemplate == null) return null; @@ -135,9 +184,7 @@ protected EmailAccount GetEmailAccountOfMessageTemplate(MessageTemplate messageT var emailAccounId = messageTemplate.GetLocalized(mt => mt.EmailAccountId, languageId); var emailAccount = _emailAccountService.GetEmailAccountById(emailAccounId); if (emailAccount == null) - emailAccount = _emailAccountService.GetEmailAccountById(_emailAccountSettings.DefaultEmailAccountId); - if (emailAccount == null) - emailAccount = _emailAccountService.GetAllEmailAccounts().FirstOrDefault(); + emailAccount = _emailAccountService.GetDefaultEmailAccount(); return emailAccount; } @@ -191,7 +238,7 @@ private string GetDisplayNameForCustomer(Customer customer) return name ?? customer.Username.EmptyNull(); } - protected int EnsureLanguageIsActive(int languageId, int storeId) + protected Language EnsureLanguageIsActive(int languageId, int storeId) { //load language by specified ID var language = _languageService.GetLanguageById(languageId); @@ -208,8 +255,9 @@ protected int EnsureLanguageIsActive(int languageId, int storeId) } if (language == null) - throw new Exception("No active language could be loaded"); - return language.Id; + throw new SmartException(T("Common.Error.NoActiveLanguage")); + + return language; } #endregion @@ -230,9 +278,9 @@ public virtual int SendCustomerRegisteredNotificationMessage(Customer customer, throw new ArgumentNullException("customer"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("NewCustomer.Notification", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("NewCustomer.Notification", store.Id); if (messageTemplate == null) return 0; @@ -244,17 +292,14 @@ public virtual int SendCustomerRegisteredNotificationMessage(Customer customer, //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + 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, - languageId, tokens, - toEmail, toName, - replyTo.Item1, replyTo.Item2); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); } /// @@ -269,9 +314,9 @@ public virtual int SendCustomerWelcomeMessage(Customer customer, int languageId) throw new ArgumentNullException("customer"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Customer.WelcomeMessage", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Customer.WelcomeMessage", store.Id); if (messageTemplate == null) return 0; @@ -283,12 +328,11 @@ public virtual int SendCustomerWelcomeMessage(Customer customer, int languageId) //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = customer.Email; var toName = customer.GetFullName(); - return SendNotification(messageTemplate, emailAccount, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -303,9 +347,9 @@ public virtual int SendCustomerEmailValidationMessage(Customer customer, int lan throw new ArgumentNullException("customer"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Customer.EmailValidationMessage", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Customer.EmailValidationMessage", store.Id); if (messageTemplate == null) return 0; @@ -317,12 +361,11 @@ public virtual int SendCustomerEmailValidationMessage(Customer customer, int lan //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = customer.Email; var toName = customer.GetFullName(); - return SendNotification(messageTemplate, emailAccount, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -337,9 +380,9 @@ public virtual int SendCustomerPasswordRecoveryMessage(Customer customer, int la throw new ArgumentNullException("customer"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Customer.PasswordRecovery", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Customer.PasswordRecovery", store.Id); if (messageTemplate == null) return 0; @@ -351,12 +394,11 @@ public virtual int SendCustomerPasswordRecoveryMessage(Customer customer, int la //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = customer.Email; var toName = customer.GetFullName(); - return SendNotification(messageTemplate, emailAccount, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } #endregion @@ -375,22 +417,22 @@ public virtual int SendOrderPlacedStoreOwnerNotification(Order order, int langua throw new ArgumentNullException("order"); var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("OrderPlaced.StoreOwnerNotification", 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, languageId); + _messageTokenProvider.AddOrderTokens(tokens, order, language); _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = emailAccount.Email; var toName = emailAccount.DisplayName; @@ -402,10 +444,7 @@ public virtual int SendOrderPlacedStoreOwnerNotification(Order order, int langua replyToName += ", " + order.BillingAddress.Company; } - return SendNotification(messageTemplate, emailAccount, - languageId, tokens, - toEmail, toName, - replyToEmail, replyToName); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyToEmail, replyToName); } /// @@ -420,31 +459,30 @@ public virtual int SendOrderPlacedCustomerNotification(Order order, int language throw new ArgumentNullException("order"); var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("OrderPlaced.CustomerNotification", 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, languageId); + _messageTokenProvider.AddOrderTokens(tokens, order, language); _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); _messageTokenProvider.AddCompanyTokens(tokens); _messageTokenProvider.AddBankConnectionTokens(tokens); _messageTokenProvider.AddContactDataTokens(tokens); - //event notification + // event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + 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, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -460,31 +498,30 @@ public virtual int SendShipmentSentCustomerNotification(Shipment shipment, int l var order = shipment.Order; if (order == null) - throw new Exception("Order cannot be loaded"); + throw new SmartException(T("Order.NotFound", shipment.OrderId)); var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("ShipmentSent.CustomerNotification", 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, languageId); - _messageTokenProvider.AddOrderTokens(tokens, shipment.Order, languageId); + _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, languageId); + 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, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -500,31 +537,30 @@ public virtual int SendShipmentDeliveredCustomerNotification(Shipment shipment, var order = shipment.Order; if (order == null) - throw new Exception("Order cannot be loaded"); + throw new SmartException(T("Order.NotFound", shipment.OrderId)); var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("ShipmentDelivered.CustomerNotification", 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, languageId); - _messageTokenProvider.AddOrderTokens(tokens, shipment.Order, languageId); + _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, languageId); + 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, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -539,16 +575,16 @@ public virtual int SendOrderCompletedCustomerNotification(Order order, int langu throw new ArgumentNullException("order"); var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("OrderCompleted.CustomerNotification", 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, languageId); + _messageTokenProvider.AddOrderTokens(tokens, order, language); _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); _messageTokenProvider.AddCompanyTokens(tokens); @@ -558,12 +594,11 @@ public virtual int SendOrderCompletedCustomerNotification(Order order, int langu //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + 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, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -578,27 +613,26 @@ public virtual int SendOrderCancelledCustomerNotification(Order order, int langu throw new ArgumentNullException("order"); var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("OrderCancelled.CustomerNotification", 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, languageId); + _messageTokenProvider.AddOrderTokens(tokens, order, language); _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + 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, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -615,9 +649,9 @@ public virtual int SendNewOrderNoteAddedCustomerNotification(OrderNote orderNote var order = orderNote.Order; var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Customer.NewOrderNote", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Customer.NewOrderNote", store.Id); if (messageTemplate == null) return 0; @@ -625,18 +659,17 @@ public virtual int SendNewOrderNoteAddedCustomerNotification(OrderNote orderNote var tokens = new List(); _messageTokenProvider.AddStoreTokens(tokens, store); _messageTokenProvider.AddOrderNoteTokens(tokens, orderNote); - _messageTokenProvider.AddOrderTokens(tokens, orderNote.Order, languageId); + _messageTokenProvider.AddOrderTokens(tokens, orderNote.Order, language); _messageTokenProvider.AddCustomerTokens(tokens, orderNote.Order.Customer); //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + 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, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -651,29 +684,27 @@ public virtual int SendRecurringPaymentCancelledStoreOwnerNotification(Recurring throw new ArgumentNullException("recurringPayment"); var store = _storeService.GetStoreById(recurringPayment.InitialOrder.StoreId) ?? _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("RecurringPaymentCancelled.StoreOwnerNotification", 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, languageId); + _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, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = emailAccount.Email; var toName = emailAccount.DisplayName; - return SendNotification(messageTemplate, emailAccount, - languageId, tokens, - toEmail, toName); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } #endregion @@ -686,16 +717,15 @@ public virtual int SendRecurringPaymentCancelledStoreOwnerNotification(Recurring /// Newsletter subscription /// Language identifier /// Queued email identifier - public virtual int SendNewsLetterSubscriptionActivationMessage(NewsLetterSubscription subscription, - int languageId) + public virtual int SendNewsLetterSubscriptionActivationMessage(NewsLetterSubscription subscription, int languageId) { if (subscription == null) throw new ArgumentNullException("subscription"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("NewsLetterSubscription.ActivationMessage", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("NewsLetterSubscription.ActivationMessage", store.Id); if (messageTemplate == null) return 0; @@ -707,12 +737,11 @@ public virtual int SendNewsLetterSubscriptionActivationMessage(NewsLetterSubscri //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = subscription.Email; var toName = ""; - return SendNotification(messageTemplate, emailAccount, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -721,16 +750,15 @@ public virtual int SendNewsLetterSubscriptionActivationMessage(NewsLetterSubscri /// Newsletter subscription /// Language identifier /// Queued email identifier - public virtual int SendNewsLetterSubscriptionDeactivationMessage(NewsLetterSubscription subscription, - int languageId) + public virtual int SendNewsLetterSubscriptionDeactivationMessage(NewsLetterSubscription subscription, int languageId) { if (subscription == null) throw new ArgumentNullException("subscription"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("NewsLetterSubscription.DeactivationMessage", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("NewsLetterSubscription.DeactivationMessage", store.Id); if (messageTemplate == null) return 0; @@ -741,12 +769,11 @@ public virtual int SendNewsLetterSubscriptionDeactivationMessage(NewsLetterSubsc //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = subscription.Email; var toName = ""; - return SendNotification(messageTemplate, emailAccount, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } #endregion @@ -773,9 +800,9 @@ public virtual int SendProductEmailAFriendMessage(Customer customer, int languag throw new ArgumentNullException("product"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Service.EmailAFriend", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Service.EmailAFriend", store.Id); if (messageTemplate == null) return 0; @@ -783,19 +810,19 @@ public virtual int SendProductEmailAFriendMessage(Customer customer, int languag var tokens = new List(); _messageTokenProvider.AddStoreTokens(tokens, store); _messageTokenProvider.AddCustomerTokens(tokens, customer); - _messageTokenProvider.AddProductTokens(tokens, product, languageId); + _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, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = friendsEmail; var toName = ""; - return SendNotification(messageTemplate, emailAccount, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } public virtual int SendProductQuestionMessage(Customer customer, int languageId, Product product, @@ -811,16 +838,16 @@ public virtual int SendProductQuestionMessage(Customer customer, int languageId, throw new ArgumentNullException("product"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Product.AskQuestion", 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, languageId); + _messageTokenProvider.AddProductTokens(tokens, product, language); tokens.Add(new Token("ProductQuestion.Message", question, true)); tokens.Add(new Token("ProductQuestion.SenderEmail", senderEmail)); @@ -830,11 +857,11 @@ public virtual int SendProductQuestionMessage(Customer customer, int languageId, //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = emailAccount.Email; var toName = emailAccount.DisplayName; - return SendNotification(messageTemplate, emailAccount, languageId, tokens, toEmail, toName, senderEmail, senderName); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, senderEmail, senderName); } /// @@ -853,9 +880,9 @@ public virtual int SendWishlistEmailAFriendMessage(Customer customer, int langua throw new ArgumentNullException("customer"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Wishlist.EmailAFriend", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Wishlist.EmailAFriend", store.Id); if (messageTemplate == null) return 0; @@ -869,12 +896,11 @@ public virtual int SendWishlistEmailAFriendMessage(Customer customer, int langua //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = friendsEmail; var toName = ""; - return SendNotification(messageTemplate, emailAccount, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } #endregion @@ -894,9 +920,9 @@ public virtual int SendNewReturnRequestStoreOwnerNotification(ReturnRequest retu throw new ArgumentNullException("returnRequest"); var store = _storeService.GetStoreById(orderItem.Order.StoreId) ?? _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("NewReturnRequest.StoreOwnerNotification", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("NewReturnRequest.StoreOwnerNotification", store.Id); if (messageTemplate == null) return 0; @@ -909,17 +935,14 @@ public virtual int SendNewReturnRequestStoreOwnerNotification(ReturnRequest retu //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + 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, - languageId, tokens, - toEmail, toName, - replyTo.Item1, replyTo.Item2); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); } /// @@ -935,9 +958,9 @@ public virtual int SendReturnRequestStatusChangedCustomerNotification(ReturnRequ throw new ArgumentNullException("returnRequest"); var store = _storeService.GetStoreById(orderItem.Order.StoreId) ?? _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("ReturnRequestStatusChanged.CustomerNotification", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("ReturnRequestStatusChanged.CustomerNotification", store.Id); if (messageTemplate == null) return 0; @@ -950,14 +973,14 @@ public virtual int SendReturnRequestStatusChangedCustomerNotification(ReturnRequ //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + 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, languageId, tokens, toEmail, toName); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } #endregion @@ -972,8 +995,7 @@ public virtual int SendReturnRequestStatusChangedCustomerNotification(ReturnRequ /// Forum /// Message language identifier /// Queued email identifier - public int SendNewForumTopicMessage(Customer customer, - ForumTopic forumTopic, Forum forum, int languageId) + public int SendNewForumTopicMessage(Customer customer, ForumTopic forumTopic, Forum forum, int languageId) { if (customer == null) { @@ -981,8 +1003,9 @@ public int SendNewForumTopicMessage(Customer customer, } var store = _storeContext.CurrentStore; + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Forums.NewForumTopic", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Forums.NewForumTopic", store.Id); if (messageTemplate == null) { return 0; @@ -993,16 +1016,16 @@ public int SendNewForumTopicMessage(Customer customer, _messageTokenProvider.AddStoreTokens(tokens, store); _messageTokenProvider.AddCustomerTokens(tokens, customer); _messageTokenProvider.AddForumTopicTokens(tokens, forumTopic); - _messageTokenProvider.AddForumTokens(tokens, forumTopic.Forum); + _messageTokenProvider.AddForumTokens(tokens, forumTopic.Forum, language); //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = customer.Email; var toName = customer.GetFullName(); - return SendNotification(messageTemplate, emailAccount, languageId, tokens, toEmail, toName); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -1015,9 +1038,7 @@ public int SendNewForumTopicMessage(Customer customer, /// 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) + public int SendNewForumPostMessage(Customer customer, ForumPost forumPost, ForumTopic forumTopic, Forum forum, int friendlyForumTopicPageIndex, int languageId) { if (customer == null) { @@ -1025,8 +1046,9 @@ public int SendNewForumPostMessage(Customer customer, } var store = _storeContext.CurrentStore; + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Forums.NewForumPost", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Forums.NewForumPost", store.Id); if (messageTemplate == null) { return 0; @@ -1037,18 +1059,17 @@ public int SendNewForumPostMessage(Customer customer, _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); + _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, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = customer.Email; var toName = customer.GetFullName(); - return SendNotification(messageTemplate, emailAccount, languageId, tokens, toEmail, toName); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -1066,7 +1087,7 @@ public int SendPrivateMessageNotification(Customer customer, PrivateMessage priv var store = _storeService.GetStoreById(privateMessage.StoreId) ?? _storeContext.CurrentStore; - var messageTemplate = GetLocalizedActiveMessageTemplate("Customer.NewPM", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Customer.NewPM", store.Id); if (messageTemplate == null) { return 0; @@ -1123,9 +1144,10 @@ public virtual int SendGenericMessage(string messageTemplateName, Action @@ -1191,16 +1212,15 @@ public virtual int SendGiftCardNotification(GiftCard giftCard, int languageId) /// Product review /// Message language identifier /// Queued email identifier - public virtual int SendProductReviewNotificationMessage(ProductReview productReview, - int languageId) + public virtual int SendProductReviewNotificationMessage(ProductReview productReview, int languageId) { if (productReview == null) throw new ArgumentNullException("productReview"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Product.ProductReview", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Product.ProductReview", store.Id); if (messageTemplate == null) return 0; @@ -1212,17 +1232,14 @@ public virtual int SendProductReviewNotificationMessage(ProductReview productRev //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + 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, - languageId, tokens, - toEmail, toName, - replyTo.Item1, replyTo.Item2); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); } /// @@ -1237,25 +1254,24 @@ public virtual int SendQuantityBelowStoreOwnerNotification(Product product, int throw new ArgumentNullException("product"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("QuantityBelow.StoreOwnerNotification", 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, languageId); + _messageTokenProvider.AddProductTokens(tokens, product, language); //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var toEmail = emailAccount.Email; var toName = emailAccount.DisplayName; - return SendNotification(messageTemplate, emailAccount, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } /// @@ -1266,16 +1282,15 @@ public virtual int SendQuantityBelowStoreOwnerNotification(Product product, int /// Received VAT address /// Message language identifier /// Queued email identifier - public virtual int SendNewVatSubmittedStoreOwnerNotification(Customer customer, - string vatName, string vatAddress, int languageId) + public virtual int SendNewVatSubmittedStoreOwnerNotification(Customer customer, string vatName, string vatAddress, int languageId) { if (customer == null) throw new ArgumentNullException("customer"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("NewVATSubmitted.StoreOwnerNotification", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("NewVATSubmitted.StoreOwnerNotification", store.Id); if (messageTemplate == null) return 0; @@ -1289,17 +1304,14 @@ public virtual int SendNewVatSubmittedStoreOwnerNotification(Customer customer, //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + 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, - languageId, tokens, - toEmail, toName, - replyTo.Item1, replyTo.Item2); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); } /// @@ -1314,9 +1326,9 @@ public virtual int SendBlogCommentNotificationMessage(BlogComment blogComment, i throw new ArgumentNullException("blogComment"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Blog.BlogComment", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Blog.BlogComment", store.Id); if (messageTemplate == null) return 0; @@ -1328,17 +1340,14 @@ public virtual int SendBlogCommentNotificationMessage(BlogComment blogComment, i //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + 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, - languageId, tokens, - toEmail, toName, - replyTo.Item1, replyTo.Item2); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); } /// @@ -1353,9 +1362,9 @@ public virtual int SendNewsCommentNotificationMessage(NewsComment newsComment, i throw new ArgumentNullException("newsComment"); var store = _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("News.NewsComment", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("News.NewsComment", store.Id); if (messageTemplate == null) return 0; @@ -1367,17 +1376,14 @@ public virtual int SendNewsCommentNotificationMessage(NewsComment newsComment, i //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + 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, - languageId, tokens, - toEmail, toName, - replyTo.Item1, replyTo.Item2); + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); } /// @@ -1392,9 +1398,9 @@ public virtual int SendBackInStockNotification(BackInStockSubscription subscript throw new ArgumentNullException("subscription"); var store = _storeService.GetStoreById(subscription.StoreId) ?? _storeContext.CurrentStore; - languageId = EnsureLanguageIsActive(languageId, store.Id); + var language = EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = GetLocalizedActiveMessageTemplate("Customer.BackInStock", languageId, store.Id); + var messageTemplate = GetActiveMessageTemplate("Customer.BackInStock", store.Id); if (messageTemplate == null) return 0; @@ -1407,13 +1413,12 @@ public virtual int SendBackInStockNotification(BackInStockSubscription subscript //event notification _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); + var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); var customer = subscription.Customer; var toEmail = customer.Email; var toName = customer.GetFullName(); - return SendNotification(messageTemplate, emailAccount, - languageId, tokens, - toEmail, toName); + + return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); } #endregion diff --git a/src/Libraries/SmartStore.Services/News/INewsService.cs b/src/Libraries/SmartStore.Services/News/INewsService.cs index 435dca7ced..d25a8d8afa 100644 --- a/src/Libraries/SmartStore.Services/News/INewsService.cs +++ b/src/Libraries/SmartStore.Services/News/INewsService.cs @@ -1,3 +1,5 @@ +using System; +using System.Linq; using SmartStore.Core; using SmartStore.Core.Domain.News; @@ -21,6 +23,13 @@ public partial interface INewsService /// News NewsItem GetNewsById(int newsId); + /// + /// Get news by identifiers + /// + /// News identifiers + /// News query + IQueryable GetNewsByIds(int[] newsIds); + /// /// Gets all news /// @@ -29,9 +38,9 @@ public partial interface INewsService /// Page index /// Page size /// A value indicating whether to show hidden records + /// The maximum age of returned news /// News items - IPagedList GetAllNews(int languageId, int storeId, - int pageIndex, int pageSize, bool showHidden = false); + IPagedList GetAllNews(int languageId, int storeId, int pageIndex, int pageSize, bool showHidden = false, DateTime? maxAge = null); /// /// Inserts a news item diff --git a/src/Libraries/SmartStore.Services/News/NewsService.cs b/src/Libraries/SmartStore.Services/News/NewsService.cs index 92a7051fc8..743ab7f9f4 100644 --- a/src/Libraries/SmartStore.Services/News/NewsService.cs +++ b/src/Libraries/SmartStore.Services/News/NewsService.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using SmartStore.Core; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.News; using SmartStore.Core.Domain.Stores; @@ -18,8 +17,9 @@ public partial class NewsService : INewsService private readonly IRepository _newsItemRepository; private readonly IRepository _storeMappingRepository; - private readonly ICacheManager _cacheManager; - private readonly IEventPublisher _eventPublisher; + private readonly ICommonServices _services; + + private readonly NewsSettings _newsSettings; #endregion @@ -27,13 +27,13 @@ public partial class NewsService : INewsService public NewsService(IRepository newsItemRepository, IRepository storeMappingRepository, - ICacheManager cacheManager, - IEventPublisher eventPublisher) + ICommonServices services, + NewsSettings newsSettings) { _newsItemRepository = newsItemRepository; _storeMappingRepository = storeMappingRepository; - _cacheManager = cacheManager; - _eventPublisher = eventPublisher; + _services = services; + _newsSettings = newsSettings; this.QuerySettings = DbQuerySettings.Default; } @@ -56,7 +56,7 @@ public virtual void DeleteNews(NewsItem newsItem) _newsItemRepository.Delete(newsItem); //event notification - _eventPublisher.EntityDeleted(newsItem); + _services.EventPublisher.EntityDeleted(newsItem); } /// @@ -72,6 +72,24 @@ public virtual NewsItem GetNewsById(int newsId) return _newsItemRepository.GetById(newsId); } + /// + /// Get news by identifiers + /// + /// News identifiers + /// News query + public virtual IQueryable GetNewsByIds(int[] newsIds) + { + if (newsIds == null || newsIds.Length == 0) + return null; + + var query = + from x in _newsItemRepository.Table + where newsIds.Contains(x.Id) + select x; + + return query; + } + /// /// Gets all news /// @@ -80,12 +98,22 @@ public virtual NewsItem GetNewsById(int newsId) /// Page index /// Page size /// A value indicating whether to show hidden records + /// The maximum age of returned news /// News items - public virtual IPagedList GetAllNews(int languageId, int storeId, int pageIndex, int pageSize, bool showHidden = false) + public virtual IPagedList GetAllNews(int languageId, int storeId, int pageIndex, int pageSize, bool showHidden = false, DateTime? maxAge = null) { var query = _newsItemRepository.Table; - if (languageId > 0) - query = query.Where(n => languageId == n.LanguageId); + + if (languageId > 0) + { + query = query.Where(n => languageId == n.LanguageId); + } + + if (maxAge.HasValue) + { + query = query.Where(n => n.CreatedOnUtc >= maxAge.Value); + } + if (!showHidden) { var utcNow = DateTime.UtcNow; @@ -93,6 +121,7 @@ public virtual IPagedList GetAllNews(int languageId, int storeId, int query = query.Where(n => !n.StartDateUtc.HasValue || n.StartDateUtc <= utcNow); query = query.Where(n => !n.EndDateUtc.HasValue || n.EndDateUtc >= utcNow); } + query = query.OrderByDescending(n => n.CreatedOnUtc); //Store mapping @@ -110,6 +139,7 @@ from sm in n_sm.DefaultIfEmpty() group n by n.Id into nGroup orderby nGroup.Key select nGroup.FirstOrDefault(); + query = query.OrderByDescending(n => n.CreatedOnUtc); } @@ -129,7 +159,7 @@ public virtual void InsertNews(NewsItem news) _newsItemRepository.Insert(news); //event notification - _eventPublisher.EntityInserted(news); + _services.EventPublisher.EntityInserted(news); } /// @@ -144,7 +174,7 @@ public virtual void UpdateNews(NewsItem news) _newsItemRepository.Update(news); //event notification - _eventPublisher.EntityUpdated(news); + _services.EventPublisher.EntityUpdated(news); } /// diff --git a/src/Libraries/SmartStore.Services/Orders/AppliedGiftCard.cs b/src/Libraries/SmartStore.Services/Orders/AppliedGiftCard.cs index ea8cdd8be8..4318b43cd6 100644 --- a/src/Libraries/SmartStore.Services/Orders/AppliedGiftCard.cs +++ b/src/Libraries/SmartStore.Services/Orders/AppliedGiftCard.cs @@ -1,6 +1,3 @@ - - - using SmartStore.Core.Domain.Orders; namespace SmartStore.Services.Orders diff --git a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs index 981f4f879a..7e266a98b1 100644 --- a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs @@ -4,29 +4,33 @@ using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; namespace SmartStore.Services.Orders { - /// - /// Checkout attribute service - /// - public partial class CheckoutAttributeService : ICheckoutAttributeService + /// + /// Checkout attribute service + /// + public partial class CheckoutAttributeService : ICheckoutAttributeService { #region Constants - private const string CHECKOUTATTRIBUTES_ALL_KEY = "SmartStore.checkoutattribute.all-{0}"; + + 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 IEventPublisher _eventPublisher; + private readonly IRepository _storeMappingRepository; + private readonly IEventPublisher _eventPublisher; private readonly ICacheManager _cacheManager; #endregion @@ -43,25 +47,31 @@ public partial class CheckoutAttributeService : ICheckoutAttributeService public CheckoutAttributeService(ICacheManager cacheManager, IRepository checkoutAttributeRepository, IRepository checkoutAttributeValueRepository, - IEventPublisher eventPublisher) + IRepository storeMappingRepository, + IEventPublisher eventPublisher) { _cacheManager = cacheManager; _checkoutAttributeRepository = checkoutAttributeRepository; _checkoutAttributeValueRepository = checkoutAttributeValueRepository; + _storeMappingRepository = storeMappingRepository; _eventPublisher = eventPublisher; - } - #endregion + this.QuerySettings = DbQuerySettings.Default; + } - #region Methods + #endregion - #region Checkout attributes + public DbQuerySettings QuerySettings { get; set; } - /// - /// Deletes a checkout attribute - /// - /// Checkout attribute - public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) + #region Methods + + #region Checkout attributes + + /// + /// Deletes a checkout attribute + /// + /// Checkout attribute + public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) { if (checkoutAttribute == null) throw new ArgumentNullException("checkoutAttribute"); @@ -75,20 +85,56 @@ public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) _eventPublisher.EntityDeleted(checkoutAttribute); } - /// - /// Gets all checkout attributes - /// - /// Checkout attribute collection - public virtual IList GetAllCheckoutAttributes() + /// + /// 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; + + if (!showHidden) + query = query.Where(x => x.IsActive); + + if (storeId > 0 && !QuerySettings.IgnoreMultiStore) + { + query = + from x in query + join sm in _storeMappingRepository.Table on new { c1 = x.Id, c2 = "CheckoutAttribute" } 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(); + } + + query = query.OrderBy(x => x.DisplayOrder); + + 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) { - return _cacheManager.Get(CHECKOUTATTRIBUTES_ALL_KEY, () => + string key = CHECKOUTATTRIBUTES_ALL_KEY.FormatInvariant(storeId, showHidden); + + return _cacheManager.Get(key, () => { - var query = from ca in _checkoutAttributeRepository.Table - orderby ca.DisplayOrder - select ca; - var checkoutAttributes = query.ToList(); - return checkoutAttributes; - }); + var query = GetCheckoutAttributes(storeId, showHidden); + + return query.ToList(); + }); } /// diff --git a/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs b/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs index cb34e948c4..9a520ca28b 100644 --- a/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs @@ -1,12 +1,13 @@ using System.Collections.Generic; +using System.Linq; using SmartStore.Core.Domain.Orders; namespace SmartStore.Services.Orders { - /// - /// Checkout attribute service - /// - public partial interface ICheckoutAttributeService + /// + /// Checkout attribute service + /// + public partial interface ICheckoutAttributeService { #region Checkout attributes @@ -16,11 +17,21 @@ public partial interface ICheckoutAttributeService /// Checkout attribute void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute); - /// - /// Gets all checkout attributes - /// - /// Checkout attribute collection - IList GetAllCheckoutAttributes(); + /// + /// Gets checkout attributes + /// + /// Whether to filter result by store identifier + /// A value indicating whether to show hidden records + /// Checkout attributes query + IQueryable GetCheckoutAttributes(int storeId = 0, bool showHidden = false); + + /// + /// Gets all checkout attributes + /// + /// Whether to filter result by store identifier + /// A value indicating whether to show hidden records + /// Checkout attribute collection + IList GetAllCheckoutAttributes(int storeId = 0, bool showHidden = false); /// /// Gets a checkout attribute diff --git a/src/Libraries/SmartStore.Services/Orders/IOrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/IOrderProcessingService.cs index 9452f42b7b..9c3f825b03 100644 --- a/src/Libraries/SmartStore.Services/Orders/IOrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IOrderProcessingService.cs @@ -266,5 +266,14 @@ public partial interface IOrderProcessingService /// Shopping cart /// true - OK; false - minimum order total amount is not reached bool ValidateMinOrderTotalAmount(IList cart); + + /// + /// Adds a shipment to an order + /// + /// Order + /// Tracking number + /// Quantities by order item identifiers. null to use the remaining total number of products for each order item. + /// New shipment, null if no shipment was added + Shipment AddShipment(Order order, string trackingNumber, Dictionary quantities); } } diff --git a/src/Libraries/SmartStore.Services/Orders/IOrderService.cs b/src/Libraries/SmartStore.Services/Orders/IOrderService.cs index d0a6a6769d..9fca02b17f 100644 --- a/src/Libraries/SmartStore.Services/Orders/IOrderService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IOrderService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; @@ -69,6 +70,32 @@ public partial interface IOrderService /// The order void DeleteOrder(Order order); + /// + /// Get orders + /// + /// Store identifier; null to load all orders + /// Customer identifier; null 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. + /// Filter by order number + /// Billing name. Leave empty to load all records. + /// Order query + IQueryable GetOrders( + int storeId, + int customerId, + DateTime? startTime, + DateTime? endTime, + int[] orderStatusIds, + int[] paymentStatusIds, + int[] shippingStatusIds, + string billingEmail, + string orderNumber, + string billingName = null); + /// /// Search orders /// @@ -137,17 +164,25 @@ IPagedList SearchOrders(int storeId, int customerId, DateTime? startTime, /// Payment method system name /// Order Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId, string paymentMethodSystemName); - - #endregion - #region Orders items - - /// - /// Gets an order item - /// - /// Order item identifier - /// Order item - OrderItem GetOrderItemById(int orderItemId); + /// + /// Shortcut to add an order + /// + /// Order + /// Order note + /// Whether to display the note to the customer + void AddOrderNote(Order order, string note, bool displayToCustomer = false); + + #endregion + + #region Orders items + + /// + /// Gets an order item + /// + /// Order item identifier + /// Order item + OrderItem GetOrderItemById(int orderItemId); /// /// Gets an order item @@ -173,6 +208,13 @@ IList GetAllOrderItems(int? orderId, OrderStatus? os, PaymentStatus? ps, ShippingStatus? ss, bool loadDownloableProductsOnly = false); + /// + /// Get order items by order identifiers + /// + /// Order identifiers + /// Order items + Multimap GetOrderItemsByOrderIds(int[] orderIds); + /// /// Delete an order item /// diff --git a/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs b/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs index 3f8c28cb37..d8479eb218 100644 --- a/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs @@ -58,18 +58,25 @@ IList GetStandardWarnings(Customer customer, ShoppingCartType shoppingCa Product product, string selectedAttributes, decimal customerEnteredPrice, int quantity); - /// - /// Validates shopping cart item attributes - /// + /// + /// Validates shopping cart item attributes + /// /// The customer - /// Shopping cart type + /// Shopping cart type /// Product - /// Selected attributes + /// Selected attributes + /// The product variant attribute combination instance (reduces database roundtrips) /// Quantity /// Product bundle item - /// Warnings - IList GetShoppingCartItemAttributeWarnings(Customer customer, ShoppingCartType shoppingCartType, - Product product, string selectedAttributes, int quantity = 1, ProductBundleItem bundleItem = null); + /// Warnings + IList GetShoppingCartItemAttributeWarnings( + Customer customer, + ShoppingCartType shoppingCartType, + Product product, + string selectedAttributes, + int quantity = 1, + ProductBundleItem bundleItem = null, + ProductVariantAttributeCombination combination = null); /// /// Validates shopping cart item (gift card) @@ -168,7 +175,15 @@ List AddToCart(Customer customer, Product product, ShoppingCartType cart /// Stores the shopping card items in the database /// /// Add to cart context - void AddToCartStore(AddToCartContext ctx); + void AddToCartStoring(AddToCartContext ctx); + + /// + /// Validates if all required attributes are selected + /// + /// Selected attributes + /// Product + /// bool + bool AreAllAttributesForCombinationSelected(string selectedAttributes, Product product); /// /// Updates the shopping cart item diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index 053d491176..0993597da4 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; @@ -16,6 +15,7 @@ using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Events; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Affiliates; @@ -33,10 +33,10 @@ namespace SmartStore.Services.Orders { - /// - /// Order processing service - /// - public partial class OrderProcessingService : IOrderProcessingService + /// + /// Order processing service + /// + public partial class OrderProcessingService : IOrderProcessingService { #region Fields @@ -69,8 +69,9 @@ public partial class OrderProcessingService : IOrderProcessingService private readonly IAffiliateService _affiliateService; private readonly IEventPublisher _eventPublisher; private readonly IGenericAttributeService _genericAttributeService; + private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; - private readonly PaymentSettings _paymentSettings; + private readonly PaymentSettings _paymentSettings; private readonly RewardPointsSettings _rewardPointsSettings; private readonly OrderSettings _orderSettings; private readonly TaxSettings _taxSettings; @@ -78,48 +79,48 @@ public partial class OrderProcessingService : IOrderProcessingService private readonly CurrencySettings _currencySettings; private readonly ShoppingCartSettings _shoppingCartSettings; - #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 + /// + /// 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 + /// 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, + /// Event published + /// Payment settings + /// Reward points settings + /// Order settings + /// Tax settings + /// Localization settings + /// Currency settings + public OrderProcessingService(IOrderService orderService, IWebHelper webHelper, ILocalizationService localizationService, ILanguageService languageService, @@ -148,7 +149,8 @@ public OrderProcessingService(IOrderService orderService, IAffiliateService affiliateService, IEventPublisher eventPublisher, IGenericAttributeService genericAttributeService, - PaymentSettings paymentSettings, + INewsLetterSubscriptionService newsLetterSubscriptionService, + PaymentSettings paymentSettings, RewardPointsSettings rewardPointsSettings, OrderSettings orderSettings, TaxSettings taxSettings, @@ -185,6 +187,7 @@ public OrderProcessingService(IOrderService orderService, this._affiliateService = affiliateService; this._eventPublisher = eventPublisher; this._genericAttributeService = genericAttributeService; + this._newsLetterSubscriptionService = newsLetterSubscriptionService; this._paymentSettings = paymentSettings; this._rewardPointsSettings = rewardPointsSettings; this._orderSettings = orderSettings; @@ -192,21 +195,31 @@ public OrderProcessingService(IOrderService orderService, this._localizationSettings = localizationSettings; this._currencySettings = currencySettings; this._shoppingCartSettings = shoppingCartSettings; - } - #endregion + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } - #region Utilities + #endregion + + #region Utilities private decimal Round(decimal value) { return (_shoppingCartSettings.RoundPricesDuringCalculation ? Math.Round(value, 2) : value); } - private string T(string resKey) - { - return _localizationService.GetResource("Admin.OrderNotice." + resKey); - } + private void ProcessErrors(Order order, IList errors, string messageKey) + { + if (errors.Any()) + { + var msg = string.Concat(T(messageKey, order.GetOrderNumber()), " ", string.Join(" ", errors)); + + _orderService.AddOrderNote(order, msg); + _logger.InsertLog(LogLevel.Error, msg, msg); + } + } /// /// Award reward points @@ -229,18 +242,15 @@ 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); + // Trucate same as Floor for positive amounts + int points = (int)Math.Truncate((amount ?? order.OrderTotal) / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); if (points == 0) return; //add reward points - order.Customer.AddRewardPointsHistoryEntry(points, string.Format(_localizationService.GetResource("RewardPoints.Message.EarnedForOrder"), order.GetOrderNumber())); + order.Customer.AddRewardPointsHistoryEntry(points, T("RewardPoints.Message.EarnedForOrder", order.GetOrderNumber())); order.RewardPointsWereAdded = true; + _orderService.UpdateOrder(order); } @@ -277,7 +287,7 @@ protected void ReduceRewardPoints(Order order, decimal? amount = null) return; //reduce reward points - order.Customer.AddRewardPointsHistoryEntry(-points, string.Format(_localizationService.GetResource("RewardPoints.Message.ReducedForOrder"), order.GetOrderNumber())); + order.Customer.AddRewardPointsHistoryEntry(-points, T("RewardPoints.Message.ReducedForOrder", order.GetOrderNumber())); if (!order.RewardPointsRemaining.HasValue) order.RewardPointsRemaining = (int)Math.Round(order.OrderTotal / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); @@ -295,34 +305,42 @@ protected void ReduceRewardPoints(Order order, decimal? amount = null) protected void SetActivatedValueForPurchasedGiftCards(Order order, bool activate) { var giftCards = _giftCardService.GetAllGiftCards(order.Id, null, null, !activate); + foreach (var gc in giftCards) { if (activate) { //activate - bool isRecipientNotified = gc.IsRecipientNotified; + var isRecipientNotified = gc.IsRecipientNotified; + if (gc.GiftCardType == GiftCardType.Virtual) { //send email for virtual gift card - if (!String.IsNullOrEmpty(gc.RecipientEmail) && - !String.IsNullOrEmpty(gc.SenderEmail)) + if (!String.IsNullOrEmpty(gc.RecipientEmail) && !String.IsNullOrEmpty(gc.SenderEmail)) { var customerLang = _languageService.GetLanguageById(order.CustomerLanguageId); if (customerLang == null) customerLang = _languageService.GetAllLanguages().FirstOrDefault(); - int queuedEmailId = _workflowMessageService.SendGiftCardNotification(gc, customerLang.Id); - if (queuedEmailId > 0) - isRecipientNotified = true; + + var queuedEmailId = _workflowMessageService.SendGiftCardNotification(gc, customerLang.Id); + + if (queuedEmailId > 0) + { + isRecipientNotified = true; + } } } + gc.IsGiftCardActivated = true; gc.IsRecipientNotified = isRecipientNotified; + _giftCardService.UpdateGiftCard(gc); } else { //deactivate gc.IsGiftCardActivated = false; + _giftCardService.UpdateGiftCard(gc); } } @@ -347,49 +365,25 @@ protected void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) order.OrderStatusId = (int)os; _orderService.UpdateOrder(order); - //order notes, notifications - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("OrderStatusChanged"), os.GetLocalizedEnum(_localizationService)), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderStatusChanged", os.GetLocalizedEnum(_localizationService))); - if (prevOrderStatus != OrderStatus.Complete && - os == OrderStatus.Complete - && notifyCustomer) + if (prevOrderStatus != OrderStatus.Complete && os == OrderStatus.Complete && notifyCustomer) { //notification int orderCompletedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderCompletedCustomerNotification(order, order.CustomerLanguageId); if (orderCompletedCustomerNotificationQueuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("CustomerCompletedEmailQueued"), orderCompletedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerCompletedEmailQueued", orderCompletedCustomerNotificationQueuedEmailId)); } } - if (prevOrderStatus != OrderStatus.Cancelled && - os == OrderStatus.Cancelled - && notifyCustomer) + if (prevOrderStatus != OrderStatus.Cancelled && os == OrderStatus.Cancelled && notifyCustomer) { //notification int orderCancelledCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderCancelledCustomerNotification(order, order.CustomerLanguageId); if (orderCancelledCustomerNotificationQueuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("CustomerCancelledEmailQueued"), orderCancelledCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerCancelledEmailQueued", orderCancelledCustomerNotificationQueuedEmailId)); } } @@ -404,15 +398,13 @@ protected void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) } //gift cards activation - if (_orderSettings.GiftCards_Activated_OrderStatusId > 0 && - _orderSettings.GiftCards_Activated_OrderStatusId == (int)order.OrderStatus) + if (_orderSettings.GiftCards_Activated_OrderStatusId > 0 && _orderSettings.GiftCards_Activated_OrderStatusId == (int)order.OrderStatus) { SetActivatedValueForPurchasedGiftCards(order, true); } //gift cards deactivation - if (_orderSettings.GiftCards_Deactivated_OrderStatusId > 0 && - _orderSettings.GiftCards_Deactivated_OrderStatusId == (int)order.OrderStatus) + if (_orderSettings.GiftCards_Deactivated_OrderStatusId > 0 && _orderSettings.GiftCards_Deactivated_OrderStatusId == (int)order.OrderStatus) { SetActivatedValueForPurchasedGiftCards(order, false); } @@ -437,8 +429,7 @@ public void CheckOrderStatus(Order order) if (order.OrderStatus == OrderStatus.Pending) { - if (order.PaymentStatus == PaymentStatus.Authorized || - order.PaymentStatus == PaymentStatus.Paid) + if (order.PaymentStatus == PaymentStatus.Authorized || order.PaymentStatus == PaymentStatus.Paid) { SetOrderStatus(order, OrderStatus.Processing, false); } @@ -446,16 +437,13 @@ public void CheckOrderStatus(Order order) if (order.OrderStatus == OrderStatus.Pending) { - if (order.ShippingStatus == ShippingStatus.PartiallyShipped || - order.ShippingStatus == ShippingStatus.Shipped || - order.ShippingStatus == ShippingStatus.Delivered) + if (order.ShippingStatus == ShippingStatus.PartiallyShipped || order.ShippingStatus == ShippingStatus.Shipped || order.ShippingStatus == ShippingStatus.Delivered) { SetOrderStatus(order, OrderStatus.Processing, false); } } - if (order.OrderStatus != OrderStatus.Cancelled && - order.OrderStatus != OrderStatus.Complete) + if (order.OrderStatus != OrderStatus.Cancelled && order.OrderStatus != OrderStatus.Complete) { if (order.PaymentStatus == PaymentStatus.Paid) { @@ -476,9 +464,11 @@ public void CheckOrderStatus(Order order) /// /// Process payment request /// Place order result - public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentRequest, Dictionary extraData) + 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 + // think about moving functionality of processing recurring orders (after the initial order was placed) to ProcessNextRecurringPayment() method if (processPaymentRequest == null) throw new ArgumentNullException("processPaymentRequest"); @@ -492,28 +482,30 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { #region Order details (customer, totals) - //Recurring orders. Load initial order - Order initialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); + // Recurring orders. Load initial order + var initialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); if (processPaymentRequest.IsRecurringPayment) { if (initialOrder == null) - throw new ArgumentException("Initial order is not set for recurring payment"); + throw new ArgumentException(T("Order.InitialOrderDoesNotExistForRecurringPayment")); processPaymentRequest.PaymentMethodSystemName = initialOrder.PaymentMethodSystemName; } - //customer + // customer var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); if (customer == null) - throw new ArgumentException("Customer is not set"); + throw new ArgumentException(T("Customer.DoesNotExist")); - //affilites - int affiliateId = 0; + // affilites + var affiliateId = 0; var affiliate = _affiliateService.GetAffiliateById(customer.AffiliateId); if (affiliate != null && affiliate.Active && !affiliate.Deleted) + { affiliateId = affiliate.Id; + } - //customer currency + // customer currency string customerCurrencyCode = ""; decimal customerCurrencyRate = decimal.Zero; if (!processPaymentRequest.IsRecurringPayment) @@ -521,7 +513,9 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR var currencyTmp = _currencyService.GetCurrencyById(customer.GetAttribute(SystemCustomerAttributeNames.CurrencyId, processPaymentRequest.StoreId)); var customerCurrency = (currencyTmp != null && currencyTmp.Published) ? currencyTmp : _workContext.WorkingCurrency; customerCurrencyCode = customerCurrency.CurrencyCode; - var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); + + var primaryStoreCurrency = _storeContext.CurrentStore.PrimaryStoreCurrency; + customerCurrencyRate = customerCurrency.Rate / primaryStoreCurrency.Rate; } else @@ -530,87 +524,77 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR customerCurrencyRate = initialOrder.CurrencyRate; } - //customer language + // customer language Language customerLanguage = null; if (!processPaymentRequest.IsRecurringPayment) { - customerLanguage = _languageService.GetLanguageById(customer.GetAttribute( - SystemCustomerAttributeNames.LanguageId, processPaymentRequest.StoreId)); + customerLanguage = _languageService.GetLanguageById(customer.GetAttribute(SystemCustomerAttributeNames.LanguageId, processPaymentRequest.StoreId)); } else { customerLanguage = _languageService.GetLanguageById(initialOrder.CustomerLanguageId); } - if (customerLanguage == null || !customerLanguage.Published) - customerLanguage = _workContext.WorkingLanguage; - //check whether customer is guest + if (customerLanguage == null || !customerLanguage.Published) + { + customerLanguage = _workContext.WorkingLanguage; + } + + // check whether customer is guest if (customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) - throw new SmartException("Anonymous checkout is not allowed"); + throw new SmartException(T("Checkout.AnonymousNotAllowed")); var storeId = _storeContext.CurrentStore.Id; - //load and validate customer shopping cart + // load and validate customer shopping cart IList cart = null; if (!processPaymentRequest.IsRecurringPayment) { //load shopping cart - cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, processPaymentRequest.StoreId); + if (processPaymentRequest.ShoppingCartItems.Count > 0) + cart = processPaymentRequest.ShoppingCartItems; + else + cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, processPaymentRequest.StoreId); if (cart.Count == 0) - throw new SmartException("Cart is empty"); + throw new SmartException(T("ShoppingCart.CartIsEmpty")); - //validate the entire shopping cart - var warnings = _shoppingCartService.GetShoppingCartWarnings(cart, - customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes), true); + // validate the entire shopping cart + var warnings = _shoppingCartService.GetShoppingCartWarnings(cart, customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes), true); if (warnings.Count > 0) - { - var warningsSb = new StringBuilder(); - foreach (string warning in warnings) - { - warningsSb.Append(warning); - warningsSb.Append(";"); - } - throw new SmartException(warningsSb.ToString()); - } + throw new SmartException(string.Join(" ", warnings)); - //validate individual cart items + // 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) - { - var warningsSb = new StringBuilder(); - foreach (string warning in sciWarnings) - { - warningsSb.Append(warning); - warningsSb.Append(";"); - } - throw new SmartException(warningsSb.ToString()); - } + throw new SmartException(string.Join(" ", sciWarnings)); } } - //min totals validation + // min totals validation if (!processPaymentRequest.IsRecurringPayment) { - bool minOrderSubtotalAmountOk = ValidateMinOrderSubtotalAmount(cart); + var minOrderSubtotalAmountOk = ValidateMinOrderSubtotalAmount(cart); if (!minOrderSubtotalAmountOk) { - decimal minOrderSubtotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSubtotalAmount, _workContext.WorkingCurrency); - throw new SmartException(string.Format(_localizationService.GetResource("Checkout.MinOrderSubtotalAmount"), _priceFormatter.FormatPrice(minOrderSubtotalAmount, true, false))); + var minOrderSubtotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSubtotalAmount, _workContext.WorkingCurrency); + throw new SmartException(T("Checkout.MinOrderSubtotalAmount", _priceFormatter.FormatPrice(minOrderSubtotalAmount, true, false))); } - bool minOrderTotalAmountOk = ValidateMinOrderTotalAmount(cart); + + var minOrderTotalAmountOk = ValidateMinOrderTotalAmount(cart); if (!minOrderTotalAmountOk) { - decimal minOrderTotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderTotalAmount, _workContext.WorkingCurrency); - throw new SmartException(string.Format(_localizationService.GetResource("Checkout.MinOrderTotalAmount"), _priceFormatter.FormatPrice(minOrderTotalAmount, true, false))); + 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) { @@ -621,7 +605,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR customerTaxDisplayType = initialOrder.CustomerTaxDisplayType; } - //checkout attributes + // checkout attributes string checkoutAttributeDescription, checkoutAttributesXml; if (!processPaymentRequest.IsRecurringPayment) { @@ -634,15 +618,15 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR 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 + // sub total decimal orderSubTotalInclTax, orderSubTotalExclTax; decimal orderSubTotalDiscountInclTax = 0, orderSubTotalDiscountExclTax = 0; if (!processPaymentRequest.IsRecurringPayment) { - //sub total (incl tax) + // sub total (incl tax) decimal orderSubTotalDiscountAmount1 = decimal.Zero; Discount orderSubTotalAppliedDiscount1 = null; decimal subTotalWithoutDiscountBase1 = decimal.Zero; @@ -654,11 +638,11 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR orderSubTotalInclTax = subTotalWithoutDiscountBase1; orderSubTotalDiscountInclTax = orderSubTotalDiscountAmount1; - //discount history - if (orderSubTotalAppliedDiscount1 != null && !appliedDiscounts.ContainsDiscount(orderSubTotalAppliedDiscount1)) + // discount history + if (orderSubTotalAppliedDiscount1 != null && !appliedDiscounts.Any(x => x.Id == orderSubTotalAppliedDiscount1.Id)) appliedDiscounts.Add(orderSubTotalAppliedDiscount1); - //sub total (excl tax) + // sub total (excl tax) decimal orderSubTotalDiscountAmount2 = decimal.Zero; Discount orderSubTotalAppliedDiscount2 = null; decimal subTotalWithoutDiscountBase2 = decimal.Zero; @@ -677,7 +661,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } - //shipping info + // shipping info bool shoppingCartRequiresShipping = false; if (!processPaymentRequest.IsRecurringPayment) { @@ -693,7 +677,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { if (!processPaymentRequest.IsRecurringPayment) { - var shippingOption = customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, processPaymentRequest.StoreId); + var shippingOption = customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, processPaymentRequest.StoreId); if (shippingOption != null) { shippingMethodName = shippingOption.Name; @@ -707,7 +691,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } } - //shipping total + // shipping total decimal? orderShippingTotalInclTax, orderShippingTotalExclTax = null; decimal orderShippingTaxRate = decimal.Zero; if (!processPaymentRequest.IsRecurringPayment) @@ -716,9 +700,9 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR orderShippingTotalInclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart, true, out orderShippingTaxRate, out shippingTotalDiscount); orderShippingTotalExclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart, false); if (!orderShippingTotalInclTax.HasValue || !orderShippingTotalExclTax.HasValue) - throw new SmartException("Shipping total couldn't be calculated"); + throw new SmartException(T("Order.CannotCalculateShippingTotal")); - if (shippingTotalDiscount != null && !appliedDiscounts.ContainsDiscount(shippingTotalDiscount)) + if (shippingTotalDiscount != null && !appliedDiscounts.Any(x => x.Id == shippingTotalDiscount.Id)) appliedDiscounts.Add(shippingTotalDiscount); } else @@ -755,7 +739,9 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR //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) @@ -783,14 +769,16 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { Discount orderAppliedDiscount = null; orderTotal = _orderTotalCalculationService.GetShoppingCartTotal(cart, - out orderDiscountAmount, out orderAppliedDiscount, out appliedGiftCards, - out redeemedRewardPoints, out redeemedRewardPointsAmount); + out orderDiscountAmount, out orderAppliedDiscount, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount); + if (!orderTotal.HasValue) - throw new SmartException("Order total couldn't be calculated"); + throw new SmartException(T("Order.CannotCalculateOrderTotal")); - //discount history - if (orderAppliedDiscount != null && !appliedDiscounts.ContainsDiscount(orderAppliedDiscount)) - appliedDiscounts.Add(orderAppliedDiscount); + //discount history + if (orderAppliedDiscount != null && !appliedDiscounts.Any(x => x.Id == orderAppliedDiscount.Id)) + { + appliedDiscounts.Add(orderAppliedDiscount); + } } else { @@ -802,41 +790,38 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR #endregion #region Addresses & pre payment workflow - + // give payment processor the opportunity to fullfill billing address var preProcessPaymentResult = _paymentService.PreProcessPayment(processPaymentRequest); if (!preProcessPaymentResult.Success) { - foreach (var paymentError in preProcessPaymentResult.Errors) - { - result.AddError(string.Format("Payment error: {0}", paymentError)); - } - - throw new SmartException("Error while pre-processing the payment"); + result.Errors.AddRange(preProcessPaymentResult.Errors); + result.Errors.Add(T("Common.Error.PreProcessPayment")); + return result; } Address billingAddress = null; if (!processPaymentRequest.IsRecurringPayment) { if (customer.BillingAddress == null) - throw new SmartException("Billing address is not provided"); + throw new SmartException(T("Order.BillingAddressMissing")); if (!customer.BillingAddress.Email.IsEmail()) - throw new SmartException("Email is not valid"); + throw new SmartException(T("Common.Error.InvalidEmail")); billingAddress = (Address)customer.BillingAddress.Clone(); } else { if (initialOrder.BillingAddress == null) - throw new SmartException("Billing address is not available"); + throw new SmartException(T("Order.BillingAddressMissing")); billingAddress = (Address)initialOrder.BillingAddress.Clone(); } if (billingAddress.Country != null && !billingAddress.Country.AllowsBilling) - throw new SmartException(string.Format("Country '{0}' is not allowed for billing", billingAddress.Country.Name)); + throw new SmartException(T("Order.CountryNotAllowedForBilling", billingAddress.Country.Name)); Address shippingAddress = null; if (shoppingCartRequiresShipping) @@ -844,64 +829,68 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR if (!processPaymentRequest.IsRecurringPayment) { if (customer.ShippingAddress == null) - throw new SmartException("Shipping address is not provided"); + throw new SmartException(T("Order.ShippingAddressMissing")); if (!customer.ShippingAddress.Email.IsEmail()) - throw new SmartException("Email is not valid"); + throw new SmartException(T("Common.Error.InvalidEmail")); shippingAddress = (Address)customer.ShippingAddress.Clone(); } else { if (initialOrder.ShippingAddress == null) - throw new SmartException("Shipping address is not available"); + throw new SmartException(T("Order.ShippingAddressMissing")); shippingAddress = (Address)initialOrder.ShippingAddress.Clone(); } if (shippingAddress.Country != null && !shippingAddress.Country.AllowsShipping) - throw new SmartException(string.Format("Country '{0}' is not allowed for shipping", shippingAddress.Country.Name)); + throw new SmartException(T("Order.CountryNotAllowedForShipping", shippingAddress.Country.Name)); } #endregion #region Payment workflow - //skip payment workflow if order total equals zero - bool skipPaymentWorkflow = false; - if (orderTotal.Value == decimal.Zero) - skipPaymentWorkflow = true; + // skip payment workflow if order total equals zero + var skipPaymentWorkflow = false; + if (orderTotal.Value == decimal.Zero) + { + skipPaymentWorkflow = true; + } - //payment workflow + // payment workflow Provider paymentMethod = null; if (!skipPaymentWorkflow) { paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); - //ensure that payment method is active + // ensure that payment method is active if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) - throw new SmartException("Payment method is not active"); + throw new SmartException(T("Payment.MethodNotAvailable")); } else { processPaymentRequest.PaymentMethodSystemName = ""; } - //recurring or standard shopping cart? - bool isRecurringShoppingCart = false; + // recurring or standard shopping cart? + var isRecurringShoppingCart = false; if (!processPaymentRequest.IsRecurringPayment) { isRecurringShoppingCart = cart.IsRecurring(); if (isRecurringShoppingCart) { - int recurringCycleLength = 0; + var recurringCycleLength = 0; + var recurringTotalCycles = 0; RecurringProductCyclePeriod recurringCyclePeriod; - int recurringTotalCycles = 0; string recurringCyclesError = cart.GetRecurringCycleInfo(_localizationService, out recurringCycleLength, out recurringCyclePeriod, out recurringTotalCycles); + if (!string.IsNullOrEmpty(recurringCyclesError)) throw new SmartException(recurringCyclesError); + processPaymentRequest.RecurringCycleLength = recurringCycleLength; processPaymentRequest.RecurringCyclePeriod = recurringCyclePeriod; processPaymentRequest.RecurringTotalCycles = recurringTotalCycles; @@ -912,31 +901,31 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR isRecurringShoppingCart = true; } - //process payment + // process payment ProcessPaymentResult processPaymentResult = null; - if (!skipPaymentWorkflow) + if (!skipPaymentWorkflow && !processPaymentRequest.IsMultiOrder) { if (!processPaymentRequest.IsRecurringPayment) { if (isRecurringShoppingCart) { - //recurring cart + // recurring cart var recurringPaymentType = _paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName); switch (recurringPaymentType) { case RecurringPaymentType.NotSupported: - throw new SmartException("Recurring payments are not supported by selected payment method"); + throw new SmartException(T("Payment.RecurringPaymentNotSupported")); case RecurringPaymentType.Manual: case RecurringPaymentType.Automatic: processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); break; default: - throw new SmartException("Not supported recurring payment type"); + throw new SmartException(T("Payment.RecurringPaymentTypeUnknown")); } } else { - //standard cart + // standard cart processPaymentResult = _paymentService.ProcessPayment(processPaymentRequest); } } @@ -944,12 +933,13 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { 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 { processPaymentRequest.CreditCardExpireMonth = initialOrder.AllowStoringCreditCardNumber ? Convert.ToInt32(_encryptionService.DecryptText(initialOrder.CardExpirationMonth)) : 0; @@ -961,7 +951,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR switch (recurringPaymentType) { case RecurringPaymentType.NotSupported: - throw new SmartException("Recurring payments are not supported by selected payment method"); + throw new SmartException(T("Payment.RecurringPaymentNotSupported")); case RecurringPaymentType.Manual: processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); break; @@ -970,26 +960,25 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR processPaymentResult = new ProcessPaymentResult(); break; default: - throw new SmartException("Not supported recurring payment type"); - } + throw new SmartException(T("Payment.RecurringPaymentTypeUnknown")); + } } else { - throw new SmartException("No recurring products"); + throw new SmartException(T("Order.NoRecurringProducts")); } } } else { - //payment is not required - if (processPaymentResult == null) - processPaymentResult = new ProcessPaymentResult(); + // payment is not required + if (processPaymentResult == null) + { + processPaymentResult = new ProcessPaymentResult(); + } processPaymentResult.NewPaymentStatus = PaymentStatus.Paid; } - if (processPaymentResult == null) - throw new SmartException("processPaymentResult is not available"); - #endregion if (processPaymentResult.Success) @@ -1001,10 +990,12 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR #region Save order details var shippingStatus = ShippingStatus.NotYetShipped; - if (!shoppingCartRequiresShipping) - shippingStatus = ShippingStatus.ShippingNotRequired; + if (!shoppingCartRequiresShipping) + { + shippingStatus = ShippingStatus.ShippingNotRequired; + } - var order = new Order() + var order = new Order { StoreId = processPaymentRequest.StoreId, OrderGuid = processPaymentRequest.OrderGuid, @@ -1069,6 +1060,12 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR UpdatedOnUtc = utcNow, CustomerOrderComment = extraData.ContainsKey("CustomerComment") ? extraData["CustomerComment"] : "" }; + + if (extraData.ContainsKey("AcceptThirdPartyEmailHandOver") && _shoppingCartSettings.ThirdPartyEmailHandOver != CheckoutThirdPartyEmailHandOver.None) + { + order.AcceptThirdPartyEmailHandOver = extraData["AcceptThirdPartyEmailHandOver"].ToBool(); + } + _orderService.InsertOrder(order); result.PlacedOrder = order; @@ -1095,16 +1092,19 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR decimal discountAmount = _priceCalculationService.GetDiscountAmount(sc, out scDiscount); decimal discountAmountInclTax = _taxService.GetProductPrice(sc.Item.Product, discountAmount, true, customer, out taxRate); decimal discountAmountExclTax = _taxService.GetProductPrice(sc.Item.Product, discountAmount, false, customer, out taxRate); - if (scDiscount != null && !appliedDiscounts.ContainsDiscount(scDiscount)) - appliedDiscounts.Add(scDiscount); + + if (scDiscount != null && !appliedDiscounts.Any(x => x.Id == scDiscount.Id)) + { + appliedDiscounts.Add(scDiscount); + } //attributes - string attributeDescription = _productAttributeFormatter.FormatAttributes(sc.Item.Product, sc.Item.AttributesXml, customer); + var attributeDescription = _productAttributeFormatter.FormatAttributes(sc.Item.Product, sc.Item.AttributesXml, customer); var itemWeight = _shippingService.GetShoppingCartItemWeight(sc); //save order item - var orderItem = new OrderItem() + var orderItem = new OrderItem { OrderItemGuid = Guid.NewGuid(), Order = order, @@ -1132,10 +1132,10 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR foreach (var childItem in sc.ChildItems) { - decimal bundleItemSubTotal = _taxService.GetProductPrice(childItem.Item.Product, _priceCalculationService.GetSubTotal(childItem, true), out taxRate); + var bundleItemSubTotal = _taxService.GetProductPrice(childItem.Item.Product, _priceCalculationService.GetSubTotal(childItem, true), out taxRate); - string attributesInfo = _productAttributeFormatter.FormatAttributes(childItem.Item.Product, childItem.Item.AttributesXml, order.Customer, - renderPrices: false, allowHyperlinks: false); + var attributesInfo = _productAttributeFormatter.FormatAttributes(childItem.Item.Product, childItem.Item.AttributesXml, order.Customer, + renderPrices: false, allowHyperlinks: true); childItem.BundleItemData.ToOrderData(listBundleData, bundleItemSubTotal, childItem.Item.AttributesXml, attributesInfo); } @@ -1149,15 +1149,14 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR //gift cards if (sc.Item.Product.IsGiftCard) { - string giftCardRecipientName, giftCardRecipientEmail, - giftCardSenderName, giftCardSenderEmail, giftCardMessage; + string giftCardRecipientName, giftCardRecipientEmail, giftCardSenderName, giftCardSenderEmail, giftCardMessage; + _productAttributeParser.GetGiftCardAttribute(sc.Item.AttributesXml, - out giftCardRecipientName, out giftCardRecipientEmail, - out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + out giftCardRecipientName, out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); for (int i = 0; i < sc.Item.Quantity; i++) { - var gc = new GiftCard() + var gc = new GiftCard { GiftCardType = sc.Item.Product.GiftCardType, PurchasedWithOrderItem = orderItem, @@ -1176,12 +1175,12 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } } - //inventory _productService.AdjustInventory(sc, true); } //clear shopping cart - cart.ToList().ForEach(sci => _shoppingCartService.DeleteShoppingCartItem(sci.Item, false)); + if (!processPaymentRequest.IsMultiOrder) + cart.ToList().ForEach(sci => _shoppingCartService.DeleteShoppingCartItem(sci.Item, false)); } else { @@ -1190,7 +1189,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR foreach (var orderItem in initialOrderItems) { //save item - var newOrderItem = new OrderItem() + var newOrderItem = new OrderItem { OrderItemGuid = Guid.NewGuid(), Order = order, @@ -1221,12 +1220,11 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR string giftCardRecipientName, giftCardRecipientEmail, giftCardSenderName, giftCardSenderEmail, giftCardMessage; _productAttributeParser.GetGiftCardAttribute(orderItem.AttributesXml, - out giftCardRecipientName, out giftCardRecipientEmail, - out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + out giftCardRecipientName, out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); for (int i = 0; i < orderItem.Quantity; i++) { - var gc = new GiftCard() + var gc = new GiftCard { GiftCardType = orderItem.Product.GiftCardType, PurchasedWithOrderItem = newOrderItem, @@ -1245,7 +1243,6 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } } - //inventory _productService.AdjustInventory(orderItem, true, orderItem.Quantity); } } @@ -1255,7 +1252,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { foreach (var discount in appliedDiscounts) { - var duh = new DiscountUsageHistory() + var duh = new DiscountUsageHistory { Discount = discount, Order = order, @@ -1270,8 +1267,8 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { foreach (var agc in appliedGiftCards) { - decimal amountUsed = agc.AmountCanBeUsed; - var gcuh = new GiftCardUsageHistory() + var amountUsed = agc.AmountCanBeUsed; + var gcuh = new GiftCardUsageHistory { GiftCard = agc.GiftCard, UsedWithOrder = order, @@ -1287,9 +1284,10 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR if (redeemedRewardPointsAmount > decimal.Zero) { customer.AddRewardPointsHistoryEntry(-redeemedRewardPoints, - string.Format(_localizationService.GetResource("RewardPoints.Message.RedeemedForOrder", order.CustomerLanguageId), order.GetOrderNumber()), + _localizationService.GetResource("RewardPoints.Message.RedeemedForOrder", order.CustomerLanguageId).FormatInvariant(order.GetOrderNumber()), order, - redeemedRewardPointsAmount); + redeemedRewardPointsAmount); + _customerService.UpdateCustomer(customer); } @@ -1297,7 +1295,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR if (!processPaymentRequest.IsRecurringPayment && isRecurringShoppingCart) { //create recurring payment (the first payment) - var rp = new RecurringPayment() + var rp = new RecurringPayment { CycleLength = processPaymentRequest.RecurringCycleLength, CyclePeriod = processPaymentRequest.RecurringCyclePeriod, @@ -1309,7 +1307,6 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR }; _orderService.InsertRecurringPayment(rp); - var recurringPaymentType = _paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName); switch (recurringPaymentType) { @@ -1341,50 +1338,34 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } } - #endregion + #endregion - #region Notifications, notes and attributes - - //notes, messages - order.OrderNotes.Add(new OrderNote() - { - Note = T("OrderPlaced"), - DisplayToCustomer = false, - CreatedOnUtc = utcNow - }); - _orderService.UpdateOrder(order); + #region Notifications, notes and attributes + + // notes, messages + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderPlaced")); //send email notifications int orderPlacedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); if (orderPlacedStoreOwnerNotificationQueuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("MerchantEmailQueued"), orderPlacedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = utcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.MerchantEmailQueued", orderPlacedStoreOwnerNotificationQueuedEmailId)); } int orderPlacedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedCustomerNotification(order, order.CustomerLanguageId); if (orderPlacedCustomerNotificationQueuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("CustomerEmailQueued"), orderPlacedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = utcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerEmailQueued", orderPlacedCustomerNotificationQueuedEmailId)); } - //check order status + // check order status CheckOrderStatus(order); - //reset checkout data - if (!processPaymentRequest.IsRecurringPayment) + //reset checkout data + if (!processPaymentRequest.IsRecurringPayment && !processPaymentRequest.IsMultiOrder) + { _customerService.ResetCheckoutData(customer, processPaymentRequest.StoreId, clearCouponCodes: true, clearCheckoutAttributes: true); + } // check for generic attributes to be inserted automatically foreach (var customProperty in processPaymentRequest.CustomProperties.Where(x => x.Key.HasValue() && x.Value.AutoCreateGenericAttribute)) @@ -1395,29 +1376,50 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR //uncomment this line to support transactions //scope.Complete(); - //raise event + // raise event _eventPublisher.PublishOrderPlaced(order); if (!processPaymentRequest.IsRecurringPayment) { - _customerActivityService.InsertActivity( - "PublicStore.PlaceOrder", - _localizationService.GetResource("ActivityLog.PublicStore.PlaceOrder"), - order.GetOrderNumber()); + _customerActivityService.InsertActivity("PublicStore.PlaceOrder", T("ActivityLog.PublicStore.PlaceOrder", order.GetOrderNumber())); } - + //raise event if (order.PaymentStatus == PaymentStatus.Paid) { _eventPublisher.PublishOrderPaid(order); } - #endregion - } - } + + #endregion + + #region Newsletter subscription + + if (extraData.ContainsKey("SubscribeToNewsLetter") && _shoppingCartSettings.NewsLetterSubscription != CheckoutNewsLetterSubscription.None) + { + var addSubscription = extraData["SubscribeToNewsLetter"].ToBool(); + + bool? nsResult = _newsLetterSubscriptionService.AddNewsLetterSubscriptionFor(addSubscription, customer.Email, order.StoreId); + + if (nsResult.HasValue) + { + if (nsResult.Value) + _orderService.AddOrderNote(order, T("Admin.OrderNotice.NewsLetterSubscriptionAdded")); + else + _orderService.AddOrderNote(order, T("Admin.OrderNotice.NewsLetterSubscriptionRemoved")); + } + } + + #endregion + } + } else { - foreach (var paymentError in processPaymentResult.Errors) - result.AddError(paymentError); + result.AddError(T("Payment.PayingFailed")); + + foreach (var paymentError in processPaymentResult.Errors) + { + result.AddError(paymentError); + } } } catch (Exception exc) @@ -1426,23 +1428,10 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR result.AddError(exc.Message); } - #region Process errors - - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i + 1, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //log it - string logError = string.Format("Error while placing order. {0}", error); - _logger.Error(logError); - } - - #endregion + if (result.Errors.Count > 0) + { + _logger.Error(string.Join(" ", result.Errors)); + } return result; } @@ -1473,14 +1462,8 @@ public virtual void DeleteOrder(Order order) _productService.AdjustInventory(orderItem, false, orderItem.Quantity); } - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = T("OrderDeleted"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + //add a note + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderDeleted")); //now delete an order _orderService.DeleteOrder(order); @@ -1495,25 +1478,25 @@ public virtual void ProcessNextRecurringPayment(RecurringPayment recurringPaymen { if (recurringPayment == null) throw new ArgumentNullException("recurringPayment"); + try { if (!recurringPayment.IsActive) - throw new SmartException("Recurring payment is not active"); + throw new SmartException(T("Payment.RecurringPaymentNotActive")); var initialOrder = recurringPayment.InitialOrder; if (initialOrder == null) - throw new SmartException("Initial order could not be loaded"); + throw new SmartException(T("Order.InitialOrderDoesNotExistForRecurringPayment")); var customer = initialOrder.Customer; if (customer == null) - throw new SmartException("Customer could not be loaded"); + throw new SmartException(T("Customer.DoesNotExist")); var nextPaymentDate = recurringPayment.NextPaymentDate; if (!nextPaymentDate.HasValue) - throw new SmartException("Next payment date could not be calculated"); + throw new SmartException(T("Payment.CannotCalculateNextPaymentDate")); - //payment info - var paymentInfo = new ProcessPaymentRequest() + var paymentInfo = new ProcessPaymentRequest { StoreId = initialOrder.StoreId, CustomerId = customer.Id, @@ -1527,35 +1510,30 @@ public virtual void ProcessNextRecurringPayment(RecurringPayment recurringPaymen //place a new order var result = this.PlaceOrder(paymentInfo, new Dictionary()); + if (result.Success) { if (result.PlacedOrder == null) - throw new SmartException("Placed order could not be loaded"); + throw new SmartException(T("Order.NotFound", "".NaIfEmpty())); - var rph = new RecurringPaymentHistory() + var rph = new RecurringPaymentHistory { RecurringPayment = recurringPayment, CreatedOnUtc = DateTime.UtcNow, - OrderId = result.PlacedOrder.Id, + OrderId = result.PlacedOrder.Id }; + recurringPayment.RecurringPaymentHistory.Add(rph); _orderService.UpdateRecurringPayment(recurringPayment); } - else - { - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - throw new SmartException(error); + else if (result.Errors.Count > 0) + { + throw new SmartException(string.Join(" ", result.Errors)); } } - catch (Exception exc) + catch (Exception exception) { - _logger.Error(string.Format("Error while processing recurring order. {0}", exc.Message), exc); + _logger.ErrorsAll(exception); throw; } } @@ -1571,69 +1549,42 @@ public virtual IList CancelRecurringPayment(RecurringPayment recurringPa var initialOrder = recurringPayment.InitialOrder; if (initialOrder == null) - return new List() { "Initial order could not be loaded" }; - + return new List { T("Order.InitialOrderDoesNotExistForRecurringPayment") }; var request = new CancelRecurringPaymentRequest(); CancelRecurringPaymentResult result = null; + try { request.Order = initialOrder; - result = _paymentService.CancelRecurringPayment(request); + + result = _paymentService.CancelRecurringPayment(request); + if (result.Success) { //update recurring payment recurringPayment.IsActive = false; _orderService.UpdateRecurringPayment(recurringPayment); - - //add a note - initialOrder.OrderNotes.Add(new OrderNote() - { - Note = T("RecurringPaymentCancelled"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(initialOrder); + _orderService.AddOrderNote(initialOrder, T("Admin.OrderNotice.RecurringPaymentCancelled")); //notify a store owner - _workflowMessageService - .SendRecurringPaymentCancelledStoreOwnerNotification(recurringPayment, - _localizationSettings.DefaultAdminLanguageId); + _workflowMessageService.SendRecurringPaymentCancelledStoreOwnerNotification(recurringPayment, _localizationSettings.DefaultAdminLanguageId); } } - catch (Exception exc) + catch (Exception exception) { - if (result == null) - result = new CancelRecurringPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + if (result == null) + { + result = new CancelRecurringPaymentResult(); + } + + result.AddError(exception.ToAllMessages()); } + ProcessErrors(initialOrder, result.Errors, "Admin.OrderNotice.RecurringPaymentCancellationError"); - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - initialOrder.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("RecurringPaymentCancellationError"), error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(initialOrder); - - //log it - string logError = string.Format("Error cancelling recurring payment. Order #{0}. Error: {1}", initialOrder.Id, error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; + return result.Errors; } /// @@ -1687,10 +1638,10 @@ public virtual void Ship(Shipment shipment, bool notifyCustomer) var order = _orderService.GetOrderById(shipment.OrderId); if (order == null) - throw new Exception("Order cannot be loaded"); + throw new SmartException(T("Order.NotFound", shipment.OrderId)); if (shipment.ShippedDateUtc.HasValue) - throw new Exception("This shipment is already shipped"); + throw new SmartException(T("Shipment.AlreadyShipped")); shipment.ShippedDateUtc = DateTime.UtcNow; _shipmentService.UpdateShipment(shipment); @@ -1700,30 +1651,18 @@ public virtual void Ship(Shipment shipment, bool notifyCustomer) order.ShippingStatusId = (int)ShippingStatus.PartiallyShipped; else order.ShippingStatusId = (int)ShippingStatus.Shipped; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("ShipmentSent"), shipment.Id), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.ShipmentSent", shipment.Id)); + if (notifyCustomer) { //notify customer int queuedEmailId = _workflowMessageService.SendShipmentSentCustomerNotification(shipment, order.CustomerLanguageId); if (queuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("CustomerShippedEmailQueued"), queuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerShippedEmailQueued", queuedEmailId)); } } @@ -1743,40 +1682,30 @@ public virtual void Deliver(Shipment shipment, bool notifyCustomer) var order = shipment.Order; if (order == null) - throw new Exception("Order cannot be loaded"); + throw new SmartException(T("Order.NotFound", shipment.OrderId)); - if (shipment.DeliveryDateUtc.HasValue) - throw new Exception("This shipment is already delivered"); + if (shipment.DeliveryDateUtc.HasValue) + throw new SmartException(T("Shipment.AlreadyDelivered")); shipment.DeliveryDateUtc = DateTime.UtcNow; _shipmentService.UpdateShipment(shipment); - if (!order.HasItemsToAddToShipment() && !order.HasItemsToShip() && !order.HasItemsToDeliver()) - order.ShippingStatusId = (int)ShippingStatus.Delivered; - _orderService.UpdateOrder(order); + if (!order.HasItemsToAddToShipment() && !order.HasItemsToShip() && !order.HasItemsToDeliver()) + { + order.ShippingStatusId = (int)ShippingStatus.Delivered; + } - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("ShipmentDelivered"), shipment.Id), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.ShipmentDelivered", shipment.Id)); + if (notifyCustomer) { //send email notification int queuedEmailId = _workflowMessageService.SendShipmentDeliveredCustomerNotification(shipment, order.CustomerLanguageId); if (queuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("CustomerDeliveredEmailQueued"), queuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerDeliveredEmailQueued", queuedEmailId)); } } @@ -1813,19 +1742,12 @@ public virtual void CancelOrder(Order order, bool notifyCustomer) throw new ArgumentNullException("order"); if (!CanCancelOrder(order)) - throw new SmartException("Cannot do cancel for order."); + throw new SmartException(T("Order.CannotCancel")); //Cancel order SetOrderStatus(order, OrderStatus.Cancelled, notifyCustomer); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = T("OrderCancelled"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderCancelled")); //cancel recurring payments var recurringPayments = _orderService.SearchRecurringPayments(0, 0, order.Id, null); @@ -1936,14 +1858,7 @@ public virtual void MarkAsAuthorized(Order order) order.PaymentStatusId = (int)PaymentStatus.Authorized; _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = T("OrderMarkedAsAuthorized"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderMarkedAsAuthorized")); //check order status CheckOrderStatus(order); @@ -1970,7 +1885,7 @@ public virtual bool CanCompleteOrder(Order order) public virtual void CompleteOrder(Order order) { if (!CanCompleteOrder(order)) - throw new SmartException("You can't mark this order as completed"); + throw new SmartException(T("Order.CannotMarkCompleted")); if (CanMarkOrderAsPaid(order)) { @@ -1998,12 +1913,10 @@ public virtual bool CanCapture(Order order) if (order == null) throw new ArgumentNullException("order"); - if (order.OrderStatus == OrderStatus.Cancelled || - order.OrderStatus == OrderStatus.Pending) + if (order.OrderStatus == OrderStatus.Cancelled || order.OrderStatus == OrderStatus.Pending) return false; - if (order.PaymentStatus == PaymentStatus.Authorized && - _paymentService.SupportCapture(order.PaymentMethodSystemName)) + if (order.PaymentStatus == PaymentStatus.Authorized && _paymentService.SupportCapture(order.PaymentMethodSystemName)) return true; return false; @@ -2020,7 +1933,7 @@ public virtual IList Capture(Order order) throw new ArgumentNullException("order"); if (!CanCapture(order)) - throw new SmartException("Cannot do capture for order."); + throw new SmartException(T("Order.CannotCapture")); var request = new CapturePaymentRequest(); CapturePaymentResult result = null; @@ -2033,24 +1946,20 @@ public virtual IList Capture(Order order) if (result.Success) { var paidDate = order.PaidDateUtc; - if (result.NewPaymentStatus == PaymentStatus.Paid) - paidDate = DateTime.UtcNow; + if (result.NewPaymentStatus == PaymentStatus.Paid) + { + paidDate = DateTime.UtcNow; + } order.CaptureTransactionId = result.CaptureTransactionId; order.CaptureTransactionResult = result.CaptureTransactionResult; order.PaymentStatus = result.NewPaymentStatus; order.PaidDateUtc = paidDate; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = T("OrderCaptured"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderCaptured")); + CheckOrderStatus(order); //raise event @@ -2060,38 +1969,19 @@ public virtual IList Capture(Order order) } } } - catch (Exception exc) + catch (Exception exception) { - if (result == null) - result = new CapturePaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } + if (result == null) + { + result = new CapturePaymentResult(); + } + result.AddError(exception.ToAllMessages()); + } - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format("Unable to capture order. {0}", error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + ProcessErrors(order, result.Errors, "Admin.OrderNotice.OrderCaptureError"); - //log it - string logError = string.Format(T("OrderCaptureError"), order.GetOrderNumber(), error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; + return result.Errors; } /// @@ -2125,24 +2015,18 @@ public virtual void MarkOrderAsPaid(Order order) throw new ArgumentNullException("order"); if (!CanMarkOrderAsPaid(order)) - throw new SmartException("You can't mark this order as paid"); + throw new SmartException(T("Order.CannotMarkPaid")); order.PaymentStatusId = (int)PaymentStatus.Paid; order.PaidDateUtc = DateTime.UtcNow; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = T("OrderMarkedAsPaid"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.UpdateOrder(order); + + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderMarkedAsPaid")); CheckOrderStatus(order); - //raise event + // raise event if (order.PaymentStatus == PaymentStatus.Paid) { _eventPublisher.PublishOrderPaid(order); @@ -2186,7 +2070,7 @@ public virtual IList Refund(Order order) throw new ArgumentNullException("order"); if (!CanRefund(order)) - throw new SmartException("Cannot do refund for order."); + throw new SmartException(T("Order.CannotRefund")); var request = new RefundPaymentRequest(); RefundPaymentResult result = null; @@ -2195,7 +2079,9 @@ public virtual IList Refund(Order order) request.Order = order; request.AmountToRefund = order.OrderTotal; request.IsPartialRefund = false; - result = _paymentService.Refund(request); + + result = _paymentService.Refund(request); + if (result.Success) { //total amount refunded @@ -2204,53 +2090,29 @@ public virtual IList Refund(Order order) //update order info order.RefundedAmount = totalAmountRefunded; order.PaymentStatus = result.NewPaymentStatus; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("OrderRefunded"), _priceFormatter.FormatPrice(request.AmountToRefund, true, false)), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderRefunded", _priceFormatter.FormatPrice(request.AmountToRefund, true, false))); + //check order status CheckOrderStatus(order); } } - catch (Exception exc) + catch (Exception exception) { - if (result == null) - result = new RefundPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } + if (result == null) + { + result = new RefundPaymentResult(); + } - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; + result.AddError(exception.ToAllMessages()); } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("OrderRefundError"), error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - //log it - string logError = string.Format("Error refunding order '{0}'. Error: {1}", order.GetOrderNumber(), error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; + ProcessErrors(order, result.Errors, "Admin.OrderNotice.OrderRefundError"); + + return result.Errors; } /// @@ -2286,7 +2148,7 @@ public virtual void RefundOffline(Order order) throw new ArgumentNullException("order"); if (!CanRefundOffline(order)) - throw new SmartException("You can't refund this order"); + throw new SmartException(T("Order.CannotRefund")); //amout to refund decimal amountToRefund = order.OrderTotal; @@ -2297,17 +2159,11 @@ public virtual void RefundOffline(Order order) //update order info order.RefundedAmount = totalAmountRefunded; order.PaymentStatus = PaymentStatus.Refunded; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("OrderMarkedAsRefunded"), _priceFormatter.FormatPrice(amountToRefund, true, false)), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderMarkedAsRefunded", _priceFormatter.FormatPrice(amountToRefund, true, false))); + //check order status CheckOrderStatus(order); } @@ -2357,10 +2213,11 @@ public virtual IList PartiallyRefund(Order order, decimal amountToRefund throw new ArgumentNullException("order"); if (!CanPartiallyRefund(order, amountToRefund)) - throw new SmartException("Cannot do partial refund for order."); + throw new SmartException(T("Order.CannotPartialRefund")); var request = new RefundPaymentRequest(); RefundPaymentResult result = null; + try { request.Order = order; @@ -2377,53 +2234,28 @@ public virtual IList PartiallyRefund(Order order, decimal amountToRefund //update order info order.RefundedAmount = totalAmountRefunded; order.PaymentStatus = result.NewPaymentStatus; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("OrderPartiallyRefunded"), _priceFormatter.FormatPrice(amountToRefund, true, false)), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderPartiallyRefunded", _priceFormatter.FormatPrice(amountToRefund, true, false))); + //check order status CheckOrderStatus(order); } } - catch (Exception exc) + catch (Exception exception) { - if (result == null) - result = new RefundPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } + if (result == null) + { + result = new RefundPaymentResult(); + } - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("OrderPartiallyRefundError"), error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + result.AddError(exception.ToAllMessages()); + } - //log it - string logError = string.Format("Error refunding order '{0}'. Error: {1}", order.GetOrderNumber(), error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; + ProcessErrors(order, result.Errors, "Admin.OrderNotice.OrderPartiallyRefundError"); + + return result.Errors; } /// @@ -2469,7 +2301,7 @@ public virtual void PartiallyRefundOffline(Order order, decimal amountToRefund) throw new ArgumentNullException("order"); if (!CanPartiallyRefundOffline(order, amountToRefund)) - throw new SmartException("You can't partially refund (offline) this order"); + throw new SmartException(T("Order.CannotPartialRefund")); //total amount refunded decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; @@ -2478,17 +2310,11 @@ public virtual void PartiallyRefundOffline(Order order, decimal amountToRefund) order.RefundedAmount = totalAmountRefunded; //if (order.OrderTotal == totalAmountRefunded), then set order.PaymentStatus = PaymentStatus.Refunded; order.PaymentStatus = PaymentStatus.PartiallyRefunded; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("OrderMarkedAsPartiallyRefunded"), _priceFormatter.FormatPrice(amountToRefund, true, false)), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderMarkedAsPartiallyRefunded", _priceFormatter.FormatPrice(amountToRefund, true, false))); + //check order status CheckOrderStatus(order); } @@ -2530,10 +2356,11 @@ public virtual IList Void(Order order) throw new ArgumentNullException("order"); if (!CanVoid(order)) - throw new SmartException("Cannot do void for order."); + throw new SmartException(T("Order.CannotVoid")); var request = new VoidPaymentRequest(); VoidPaymentResult result = null; + try { request.Order = order; @@ -2543,51 +2370,27 @@ public virtual IList Void(Order order) { //update order info order.PaymentStatus = result.NewPaymentStatus; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = T("OrderVoided"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderVoided")); + //check order status CheckOrderStatus(order); } } - catch (Exception exc) + catch (Exception exception) { - if (result == null) - result = new VoidPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } + if (result == null) + { + result = new VoidPaymentResult(); + } - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(T("OrderVoidError"), error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + result.AddError(exception.ToAllMessages()); + } + + ProcessErrors(order, result.Errors, "Admin.OrderNotice.OrderVoidError"); - //log it - string logError = string.Format("Error voiding order '{0}'. Error: {1}", order.GetOrderNumber(), error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } return result.Errors; } @@ -2624,20 +2427,14 @@ public virtual void VoidOffline(Order order) throw new ArgumentNullException("order"); if (!CanVoidOffline(order)) - throw new SmartException("You can't void this order"); + throw new SmartException(T("Order.CannotVoid")); - order.PaymentStatusId = (int)PaymentStatus.Voided; - _orderService.UpdateOrder(order); + order.PaymentStatusId = (int)PaymentStatus.Voided; - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = T("OrderMarkedAsVoided"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderMarkedAsVoided")); + //check orer status CheckOrderStatus(order); } @@ -2655,7 +2452,7 @@ public virtual void ReOrder(Order order) foreach (var orderItem in order.OrderItems) { - bool isBundle = (orderItem.Product.ProductType == ProductType.BundledProduct); + var isBundle = (orderItem.Product.ProductType == ProductType.BundledProduct); var addToCartContext = new AddToCartContext(); @@ -2674,7 +2471,7 @@ public virtual void ReOrder(Order order) } } - _shoppingCartService.AddToCartStore(addToCartContext); + _shoppingCartService.AddToCartStoring(addToCartContext); } } @@ -2694,7 +2491,8 @@ public virtual bool IsReturnRequestAllowed(Order order) if (order.OrderStatus != OrderStatus.Complete) return false; - bool numberOfDaysReturnRequestAvailableValid = false; + var numberOfDaysReturnRequestAvailableValid = false; + if (_orderSettings.NumberOfDaysReturnRequestAvailable == 0) { numberOfDaysReturnRequestAvailableValid = true; @@ -2721,14 +2519,13 @@ public virtual bool ValidateMinOrderSubtotalAmount(IList 0 && _orderSettings.MinOrderSubtotalAmount > decimal.Zero) { - //subtotal 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); + out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscount, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); if (subTotalWithoutDiscountBase < _orderSettings.MinOrderSubtotalAmount) return false; @@ -2750,6 +2547,7 @@ public virtual bool ValidateMinOrderTotalAmount(IList if (cart.Count > 0 && _orderSettings.MinOrderTotalAmount > decimal.Zero) { decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart); + if (shoppingCartTotalBase.HasValue && shoppingCartTotalBase.Value < _orderSettings.MinOrderTotalAmount) return false; } @@ -2757,6 +2555,80 @@ public virtual bool ValidateMinOrderTotalAmount(IList return true; } + public virtual Shipment AddShipment(Order order, string trackingNumber, Dictionary quantities) + { + Guard.ArgumentNotNull(() => order); + + Shipment shipment = null; + decimal? totalWeight = null; + + foreach (var orderItem in order.OrderItems) + { + if (!orderItem.Product.IsShipEnabled) + continue; + + //ensure that this product can be shipped (have at least one item to ship) + var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); + if (maxQtyToAdd <= 0) + continue; + + var qtyToAdd = 0; + + if (quantities != null && quantities.ContainsKey(orderItem.Id)) + qtyToAdd = quantities[orderItem.Id]; + else if (quantities == null) + qtyToAdd = maxQtyToAdd; + + if (qtyToAdd <= 0) + continue; + + if (qtyToAdd > maxQtyToAdd) + qtyToAdd = maxQtyToAdd; + + var orderItemTotalWeight = orderItem.ItemWeight.HasValue ? orderItem.ItemWeight * qtyToAdd : null; + if (orderItemTotalWeight.HasValue) + { + if (!totalWeight.HasValue) + totalWeight = 0; + + totalWeight += orderItemTotalWeight.Value; + } + + if (shipment == null) + { + shipment = new Shipment + { + OrderId = order.Id, + Order = order, // otherwise order updated event would not be fired during InsertShipment + TrackingNumber = trackingNumber, + TotalWeight = null, + ShippedDateUtc = null, + DeliveryDateUtc = null, + CreatedOnUtc = DateTime.UtcNow, + }; + } + + var shipmentItem = new ShipmentItem + { + OrderItemId = orderItem.Id, + Quantity = qtyToAdd + }; + + shipment.ShipmentItems.Add(shipmentItem); + } + + if (shipment != null && shipment.ShipmentItems.Count > 0) + { + shipment.TotalWeight = totalWeight; + + _shipmentService.InsertShipment(shipment); + + return shipment; + } + + return null; + } + #endregion } } diff --git a/src/Libraries/SmartStore.Services/Orders/OrderService.cs b/src/Libraries/SmartStore.Services/Orders/OrderService.cs index 296951ab48..7ed45bb61a 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; @@ -12,10 +13,10 @@ namespace SmartStore.Services.Orders { - /// - /// Order service - /// - public partial class OrderService : IOrderService + /// + /// Order service + /// + public partial class OrderService : IOrderService { #region Fields @@ -182,54 +183,45 @@ from o in _orderRepository.Table return query.FirstOrDefault(); } - /// - /// 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) - { - var query = _orderRepository.Table; + public virtual IQueryable GetOrders( + int storeId, + int customerId, + DateTime? startTime, + DateTime? endTime, + int[] orderStatusIds, + int[] paymentStatusIds, + int[] shippingStatusIds, + string billingEmail, + string orderNumber, + string billingName = null) + { + var query = _orderRepository.Table; if (storeId > 0) - query = query.Where(o => o.StoreId == storeId); + query = query.Where(x => x.StoreId == storeId); if (customerId > 0) - query = query.Where(o => o.CustomerId == customerId); + query = query.Where(x => x.CustomerId == customerId); - if (startTime.HasValue) - query = query.Where(o => startTime.Value <= o.CreatedOnUtc); + if (startTime.HasValue) + query = query.Where(x => startTime.Value <= x.CreatedOnUtc); - if (endTime.HasValue) - query = query.Where(o => endTime.Value >= o.CreatedOnUtc); + if (endTime.HasValue) + query = query.Where(x => endTime.Value >= x.CreatedOnUtc); - if (!String.IsNullOrEmpty(billingEmail)) - query = query.Where(o => o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.Email) && o.BillingAddress.Email.Contains(billingEmail)); + if (billingEmail.HasValue()) + query = query.Where(x => x.BillingAddress != null && !String.IsNullOrEmpty(x.BillingAddress.Email) && x.BillingAddress.Email.Contains(billingEmail)); if (billingName.HasValue()) { - query = query.Where(o => o.BillingAddress != null && ( - (!String.IsNullOrEmpty(o.BillingAddress.LastName) && o.BillingAddress.LastName.Contains(billingName)) || - (!String.IsNullOrEmpty(o.BillingAddress.FirstName) && o.BillingAddress.FirstName.Contains(billingName)) + query = query.Where(x => x.BillingAddress != null && ( + (!String.IsNullOrEmpty(x.BillingAddress.LastName) && x.BillingAddress.LastName.Contains(billingName)) || + (!String.IsNullOrEmpty(x.BillingAddress.FirstName) && x.BillingAddress.FirstName.Contains(billingName)) )); } - if (orderNumber.HasValue()) - query = query.Where(o => o.OrderNumber.ToLower().Contains(orderNumber.ToLower())); + if (orderNumber.HasValue()) + query = query.Where(x => x.OrderNumber.ToLower().Contains(orderNumber.ToLower())); if (orderStatusIds != null && orderStatusIds.Count() > 0) query = query.Where(x => orderStatusIds.Contains(x.OrderStatusId)); @@ -240,14 +232,43 @@ public virtual IPagedList SearchOrders(int storeId, int customerId, DateT if (shippingStatusIds != null && shippingStatusIds.Count() > 0) query = query.Where(x => shippingStatusIds.Contains(x.ShippingStatusId)); - query = query.Where(o => !o.Deleted); - query = query.OrderByDescending(o => o.CreatedOnUtc); + 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) + { + var query = GetOrders(storeId, customerId, startTime, endTime, orderStatusIds, paymentStatusIds, shippingStatusIds, + billingEmail, orderNumber, billingName); + + query = query.OrderByDescending(x => x.CreatedOnUtc); - if (!String.IsNullOrEmpty(orderGuid)) + if (orderGuid.HasValue()) { //filter by GUID. Filter in BLL because EF doesn't support casting of GUID to string var orders = query.ToList(); - orders = orders.FindAll(o => o.OrderGuid.ToString().ToLowerInvariant().Contains(orderGuid.ToLowerInvariant())); + orders = orders.FindAll(x => x.OrderGuid.ToString().ToLowerInvariant().Contains(orderGuid.ToLowerInvariant())); + return new PagedList(orders, pageIndex, pageSize); } else @@ -371,8 +392,7 @@ public virtual void DeleteOrderNote(OrderNote orderNote) /// Authorization transaction ID /// Payment method system name /// Order - public virtual Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId, - string paymentMethodSystemName) + public virtual Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId, string paymentMethodSystemName) { var query = _orderRepository.Table; if (!String.IsNullOrWhiteSpace(authorizationTransactionId)) @@ -385,17 +405,32 @@ public virtual Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string var order = query.FirstOrDefault(); return order; } - - #endregion - - #region Order items - /// - /// Gets an Order item - /// - /// Order item identifier - /// Order item - public virtual OrderItem GetOrderItemById(int orderItemId) + public virtual void AddOrderNote(Order order, string note, bool displayToCustomer = false) + { + if (order != null && note.HasValue()) + { + order.OrderNotes.Add(new OrderNote + { + Note = note, + DisplayToCustomer = displayToCustomer, + CreatedOnUtc = DateTime.UtcNow + }); + + UpdateOrder(order); + } + } + + #endregion + + #region Order items + + /// + /// Gets an Order item + /// + /// Order item identifier + /// Order item + public virtual OrderItem GetOrderItemById(int orderItemId) { if (orderItemId == 0) return null; @@ -469,6 +504,23 @@ join p in _productRepository.Table on orderItem.ProductId equals p.Id return orderItems; } + public virtual Multimap GetOrderItemsByOrderIds(int[] orderIds) + { + Guard.ArgumentNotNull(() => orderIds); + + var query = + from x in _orderItemRepository.TableUntracked.Expand(x => x.Product) + where orderIds.Contains(x.OrderId) + select x; + + var map = query + .OrderBy(x => x.OrderId) + .ToList() + .ToMultimap(x => x.OrderId, x => x); + + return map; + } + /// /// Delete an Order item /// diff --git a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs index 3de602a094..e154dd519d 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs @@ -9,7 +9,8 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Tax; -using SmartStore.Core.Infrastructure; +using SmartStore.Core.Localization; +using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Discounts; @@ -19,10 +20,10 @@ namespace SmartStore.Services.Orders { - /// - /// Order service - /// - public partial class OrderTotalCalculationService : IOrderTotalCalculationService + /// + /// Order service + /// + public partial class OrderTotalCalculationService : IOrderTotalCalculationService { #region Fields @@ -31,16 +32,18 @@ public partial class OrderTotalCalculationService : IOrderTotalCalculationServic private readonly IPriceCalculationService _priceCalculationService; private readonly ITaxService _taxService; private readonly IShippingService _shippingService; - private readonly IPaymentService _paymentService; + private readonly IProviderManager _providerManager; private readonly ICheckoutAttributeParser _checkoutAttributeParser; private readonly IDiscountService _discountService; private readonly IGiftCardService _giftCardService; private readonly IGenericAttributeService _genericAttributeService; + private readonly IProductAttributeParser _productAttributeParser; private readonly TaxSettings _taxSettings; private readonly RewardPointsSettings _rewardPointsSettings; private readonly ShippingSettings _shippingSettings; private readonly ShoppingCartSettings _shoppingCartSettings; private readonly CatalogSettings _catalogSettings; + #endregion #region Ctor @@ -68,11 +71,12 @@ public OrderTotalCalculationService(IWorkContext workContext, IPriceCalculationService priceCalculationService, ITaxService taxService, IShippingService shippingService, - IPaymentService paymentService, + IProviderManager providerManager, ICheckoutAttributeParser checkoutAttributeParser, IDiscountService discountService, IGiftCardService giftCardService, IGenericAttributeService genericAttributeService, + IProductAttributeParser productAttributeParser, TaxSettings taxSettings, RewardPointsSettings rewardPointsSettings, ShippingSettings shippingSettings, @@ -84,30 +88,35 @@ public OrderTotalCalculationService(IWorkContext workContext, this._priceCalculationService = priceCalculationService; this._taxService = taxService; this._shippingService = shippingService; - this._paymentService = paymentService; + 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; - } - #endregion + T = NullLocalizer.Instance; + } - #region Methods + public Localizer T { get; set; } - /// - /// Gets shopping cart subtotal - /// - /// Cart - /// Applied discount amount - /// Applied discount - /// Sub total (without discount) - /// Sub total (with discount) + #endregion + + #region Methods + + /// + /// Gets shopping cart subtotal + /// + /// Cart + /// Applied discount amount + /// Applied discount + /// Sub total (without discount) + /// Sub total (with discount) public virtual void GetShoppingCartSubTotal(IList cart, out decimal discountAmount, out Discount appliedDiscount, out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount) @@ -186,12 +195,7 @@ public virtual void GetShoppingCartSubTotal(IList car decimal taxRate, sciSubTotal, sciExclTax, sciInclTax = decimal.Zero; - IProductAttributeParser productAttrParser; - if (EngineContext.Current.ContainerManager.TryResolve(null, out productAttrParser)) - { - // make the call unit test safe - shoppingCartItem.Item.Product.MergeWithCombination(shoppingCartItem.Item.AttributesXml, productAttrParser); - } + shoppingCartItem.Item.Product.MergeWithCombination(shoppingCartItem.Item.AttributesXml, _productAttributeParser); if (_shoppingCartSettings.RoundPricesDuringCalculation) { @@ -356,8 +360,7 @@ public virtual decimal GetOrderSubtotalDiscount(Customer customer, { foreach (var discount in allDiscounts) { - if (_discountService.IsDiscountValid(discount, customer) && discount.DiscountType == DiscountType.AssignedToOrderSubTotal && - !allowedDiscounts.ContainsDiscount(discount)) + if (discount.DiscountType == DiscountType.AssignedToOrderSubTotal && !allowedDiscounts.Any(x => x.Id == discount.Id) && _discountService.IsDiscountValid(discount, customer)) { allowedDiscounts.Add(discount); } @@ -615,11 +618,11 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, IList(); - if (allDiscounts != null) - foreach (var discount in allDiscounts) - if (_discountService.IsDiscountValid(discount, customer) && - discount.DiscountType == DiscountType.AssignedToShipping && - !allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); + + if (allDiscounts != null) + { + foreach (var discount in allDiscounts) + { + if (discount.DiscountType == DiscountType.AssignedToShipping && !allowedDiscounts.Any(x => x.Id == discount.Id) && _discountService.IsDiscountValid(discount, customer)) + { + allowedDiscounts.Add(discount); + } + } + } appliedDiscount = allowedDiscounts.GetPreferredDiscount(shippingTotal); if (appliedDiscount != null) @@ -800,8 +808,15 @@ public virtual decimal GetTaxTotal(IList cart, out So if (usePaymentMethodAdditionalFee && _taxSettings.PaymentMethodAdditionalFeeIsTaxable) { decimal taxRate = 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); + } - decimal paymentMethodAdditionalFee = _paymentService.GetAdditionalHandlingFee(cart, paymentMethodSystemName); decimal paymentMethodAdditionalFeeExclTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer, out taxRate); decimal paymentMethodAdditionalFeeInclTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, true, customer, out taxRate); @@ -854,9 +869,9 @@ public virtual decimal GetTaxTotal(IList cart, out So 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); + out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, ignoreRewardPonts, usePaymentMethodAdditionalFee); } /// @@ -894,9 +909,7 @@ public virtual decimal GetTaxTotal(IList cart, out So decimal subTotalWithoutDiscountBase = decimal.Zero; decimal subTotalWithDiscountBase = decimal.Zero; - GetShoppingCartSubTotal(cart, false, - out orderSubTotalDiscountAmount, out orderSubTotalAppliedDiscount, - out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); + GetShoppingCartSubTotal(cart, false, out orderSubTotalDiscountAmount, out orderSubTotalAppliedDiscount, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); //subtotal with discount subtotalBase = subTotalWithDiscountBase; @@ -908,8 +921,15 @@ public virtual decimal GetTaxTotal(IList cart, out So decimal paymentMethodAdditionalFeeWithoutTax = decimal.Zero; if (usePaymentMethodAdditionalFee && !String.IsNullOrEmpty(paymentMethodSystemName)) { - decimal paymentMethodAdditionalFee = _paymentService.GetAdditionalHandlingFee(cart, paymentMethodSystemName); - paymentMethodAdditionalFeeWithoutTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer); + 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); } //tax @@ -1065,8 +1085,7 @@ public virtual decimal GetOrderTotalDiscount(Customer customer, decimal orderTot { foreach (var discount in allDiscounts) { - if (_discountService.IsDiscountValid(discount, customer) && discount.DiscountType == DiscountType.AssignedToOrderTotal && - !allowedDiscounts.ContainsDiscount(discount)) + if (discount.DiscountType == DiscountType.AssignedToOrderTotal && !allowedDiscounts.Any(x => x.Id == discount.Id) &&_discountService.IsDiscountValid(discount, customer)) { allowedDiscounts.Add(discount); } @@ -1118,8 +1137,14 @@ public virtual int ConvertAmountToRewardPoints(decimal amount) if (amount <= 0) return 0; - if (_rewardPointsSettings.ExchangeRate > 0) - result = (int)Math.Ceiling(amount / _rewardPointsSettings.ExchangeRate); + if (_rewardPointsSettings.ExchangeRate > 0) + { + if (_rewardPointsSettings.RoundDownRewardPoints) + result = (int)Math.Floor(amount / _rewardPointsSettings.ExchangeRate); + else + result = (int)Math.Ceiling(amount / _rewardPointsSettings.ExchangeRate); + } + return result; } diff --git a/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs b/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs index 9054c7ffe7..84dad37b83 100644 --- a/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs +++ b/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs @@ -177,7 +177,7 @@ public static IList Organize(this IList - /// Shopping cart service - /// - public partial class ShoppingCartService : IShoppingCartService + /// + /// Shopping cart service + /// + public partial class ShoppingCartService : IShoppingCartService { #region Fields @@ -71,8 +72,10 @@ public partial class ShoppingCartService : IShoppingCartService /// ACL service /// Store mapping service /// Generic attribute service - public ShoppingCartService(IRepository sciRepository, - IWorkContext workContext, IStoreContext storeContext, + public ShoppingCartService( + IRepository sciRepository, + IWorkContext workContext, + IStoreContext storeContext, ICurrencyService currencyService, IProductService productService, ILocalizationService localizationService, IProductAttributeParser productAttributeParser, @@ -110,20 +113,24 @@ public ShoppingCartService(IRepository sciRepository, this._genericAttributeService = genericAttributeService; this._downloadService = downloadService; this._catalogSettings = catalogSettings; - } - #endregion + T = NullLocalizer.Instance; + } - #region Methods + public Localizer T { get; set; } - /// - /// 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 + #endregion + + #region Methods + + /// + /// 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, + public virtual void DeleteShoppingCartItem(ShoppingCartItem shoppingCartItem, bool resetCheckoutData = true, bool ensureOnlyActiveCheckoutAttributes = false, bool deleteChildCartItems = true) { if (shoppingCartItem == null) @@ -269,17 +276,17 @@ public virtual IList GetRequiredProductWarnings(Customer customer, //don't display specific errors from 'addToCartWarnings' variable //display only generic error - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.RequiredProductWarning"), rp.GetLocalized(x => x.Name))); + warnings.Add(T("ShoppingCart.RequiredProductWarning", rp.GetLocalized(x => x.Name))); } } else { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.RequiredProductWarning"), rp.GetLocalized(x => x.Name))); + warnings.Add(T("ShoppingCart.RequiredProductWarning", rp.GetLocalized(x => x.Name))); } } else { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.RequiredProductWarning"), rp.GetLocalized(x => x.Name))); + warnings.Add(T("ShoppingCart.RequiredProductWarning", rp.GetLocalized(x => x.Name))); } } } @@ -312,57 +319,57 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart //deleted? if (product.Deleted) { - warnings.Add(_localizationService.GetResource("ShoppingCart.ProductDeleted")); + warnings.Add(T("ShoppingCart.ProductDeleted")); return warnings; } // check if the product type is available for order if (product.ProductType == ProductType.GroupedProduct) { - warnings.Add(_localizationService.GetResource("ShoppingCart.ProductNotAvailableForOrder")); + warnings.Add(T("ShoppingCart.ProductNotAvailableForOrder")); } // validate bundle if (product.ProductType == ProductType.BundledProduct) { if (product.BundlePerItemPricing && customerEnteredPrice != decimal.Zero) - warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.NoCustomerEnteredPrice")); + warnings.Add(T("ShoppingCart.Bundle.NoCustomerEnteredPrice")); } //published? if (!product.Published) { - warnings.Add(_localizationService.GetResource("ShoppingCart.ProductUnpublished")); + warnings.Add(T("ShoppingCart.ProductUnpublished")); } //ACL if (!_aclService.Authorize(product, customer)) { - warnings.Add(_localizationService.GetResource("ShoppingCart.ProductUnpublished")); + warnings.Add(T("ShoppingCart.ProductUnpublished")); } //Store mapping if (!_storeMappingService.Authorize(product, _storeContext.CurrentStore.Id)) { - warnings.Add(_localizationService.GetResource("ShoppingCart.ProductUnpublished")); + warnings.Add(T("ShoppingCart.ProductUnpublished")); } //disabled "add to cart" button if (shoppingCartType == ShoppingCartType.ShoppingCart && product.DisableBuyButton) { - warnings.Add(_localizationService.GetResource("ShoppingCart.BuyingDisabled")); + warnings.Add(T("ShoppingCart.BuyingDisabled")); } //disabled "add to wishlist" button if (shoppingCartType == ShoppingCartType.Wishlist && product.DisableWishlistButton) { - warnings.Add(_localizationService.GetResource("ShoppingCart.WishlistDisabled")); + warnings.Add(T("ShoppingCart.WishlistDisabled")); } //call for price if (shoppingCartType == ShoppingCartType.ShoppingCart && product.CallForPrice) { - warnings.Add(_localizationService.GetResource("Products.CallForPrice")); + warnings.Add(T("Products.CallForPrice")); } //customer entered price @@ -371,11 +378,13 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart if (customerEnteredPrice < product.MinimumCustomerEnteredPrice || customerEnteredPrice > product.MaximumCustomerEnteredPrice) { - decimal minimumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MinimumCustomerEnteredPrice, _workContext.WorkingCurrency); - decimal maximumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MaximumCustomerEnteredPrice, _workContext.WorkingCurrency); - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.CustomerEnteredPrice.RangeError"), - _priceFormatter.FormatPrice(minimumCustomerEnteredPrice, true, false), - _priceFormatter.FormatPrice(maximumCustomerEnteredPrice, true, false))); + var minimumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MinimumCustomerEnteredPrice, _workContext.WorkingCurrency); + var maximumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MaximumCustomerEnteredPrice, _workContext.WorkingCurrency); + + warnings.Add(T("ShoppingCart.CustomerEnteredPrice.RangeError", + _priceFormatter.FormatPrice(minimumCustomerEnteredPrice, true, false), + _priceFormatter.FormatPrice(maximumCustomerEnteredPrice, true, false)) + ); } } @@ -383,18 +392,20 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart var hasQtyWarnings = false; if (quantity < product.OrderMinimumQuantity) { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.MinimumQuantity"), product.OrderMinimumQuantity)); + warnings.Add(T("ShoppingCart.MinimumQuantity", product.OrderMinimumQuantity)); hasQtyWarnings = true; } + if (quantity > product.OrderMaximumQuantity) { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.MaximumQuantity"), product.OrderMaximumQuantity)); + warnings.Add(T("ShoppingCart.MaximumQuantity", product.OrderMaximumQuantity)); hasQtyWarnings = true; } + var allowedQuantities = product.ParseAllowedQuatities(); if (allowedQuantities.Length > 0 && !allowedQuantities.Contains(quantity)) { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.AllowedQuantities"), string.Join(", ", allowedQuantities))); + warnings.Add(T("ShoppingCart.AllowedQuantities", string.Join(", ", allowedQuantities))); } var validateOutOfStock = shoppingCartType == ShoppingCartType.ShoppingCart || !_shoppingCartSettings.AllowOutOfStockItemsToBeAddedToWishlist; @@ -412,35 +423,30 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart { if (product.StockQuantity < quantity) { - int maximumQuantityCanBeAdded = product.StockQuantity; + var maximumQuantityCanBeAdded = product.StockQuantity; + if (maximumQuantityCanBeAdded <= 0) - warnings.Add(_localizationService.GetResource("ShoppingCart.OutOfStock")); + warnings.Add(T("ShoppingCart.OutOfStock")); else - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.QuantityExceedsStock"), maximumQuantityCanBeAdded)); + warnings.Add(T("ShoppingCart.QuantityExceedsStock", maximumQuantityCanBeAdded)); } } } break; case ManageInventoryMethod.ManageStockByAttributes: { - var combination = product.ProductVariantAttributeCombinations - .FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, selectedAttributes)); + var combination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes); - if (combination != null) + if (combination != null) { if (!combination.AllowOutOfStockOrders && combination.StockQuantity < quantity) { int maximumQuantityCanBeAdded = combination.StockQuantity; if (maximumQuantityCanBeAdded <= 0) - warnings.Add(_localizationService.GetResource("ShoppingCart.OutOfStock")); + warnings.Add(T("ShoppingCart.OutOfStock")); else - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.QuantityExceedsStock"), maximumQuantityCanBeAdded)); - } - - if (!combination.IsActive) - { - warnings.Add(_localizationService.GetResource("ShoppingCart.OutOfStock")); + warnings.Add(T("ShoppingCart.QuantityExceedsStock", maximumQuantityCanBeAdded)); } } } @@ -451,14 +457,14 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart } //availability dates - bool availableStartDateError = false; + var availableStartDateError = false; if (product.AvailableStartDateTimeUtc.HasValue) { DateTime now = DateTime.UtcNow; DateTime availableStartDateTime = DateTime.SpecifyKind(product.AvailableStartDateTimeUtc.Value, DateTimeKind.Utc); if (availableStartDateTime.CompareTo(now) > 0) { - warnings.Add(_localizationService.GetResource("ShoppingCart.NotAvailable")); + warnings.Add(T("ShoppingCart.NotAvailable")); availableStartDateError = true; } } @@ -468,30 +474,28 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart DateTime availableEndDateTime = DateTime.SpecifyKind(product.AvailableEndDateTimeUtc.Value, DateTimeKind.Utc); if (availableEndDateTime.CompareTo(now) < 0) { - warnings.Add(_localizationService.GetResource("ShoppingCart.NotAvailable")); + warnings.Add(T("ShoppingCart.NotAvailable")); } } return warnings; } - /// - /// Validates shopping cart item attributes - /// - /// The customer - /// Shopping cart type - /// Product - /// Selected attributes - /// Quantity - /// Product bundle item - /// Warnings - public virtual IList GetShoppingCartItemAttributeWarnings(Customer customer, ShoppingCartType shoppingCartType, - Product product, string selectedAttributes, int quantity = 1, ProductBundleItem bundleItem = null) + public virtual IList GetShoppingCartItemAttributeWarnings( + Customer customer, + ShoppingCartType shoppingCartType, + Product product, + string selectedAttributes, + int quantity = 1, + ProductBundleItem bundleItem = null, + ProductVariantAttributeCombination combination = null) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.ArgumentNotNull(() => product); var warnings = new List(); + if (product.ProductType == ProductType.BundledProduct) + return warnings; // customer cannot select anything cause bundles have no attributes + if (bundleItem != null && !bundleItem.BundleProduct.BundlePerItemPricing) return warnings; // customer cannot select anything... selectedAttribute is always empty @@ -500,18 +504,12 @@ public virtual IList GetShoppingCartItemAttributeWarnings(Customer custo foreach (var pva1 in pva1Collection) { var pv1 = pva1.Product; - if (pv1 != null) - { - if (pv1.Id != product.Id) - { - warnings.Add("Attribute error"); - } - } - else - { - warnings.Add("Attribute error"); - return warnings; - } + + if (pv1 == null || pv1.Id != product.Id) + { + warnings.Add(T("ShoppingCart.AttributeError")); + return warnings; + } } //existing product attributes @@ -545,17 +543,28 @@ public virtual IList GetShoppingCartItemAttributeWarnings(Customer custo if (!found) { - if (pva2.TextPrompt.HasValue()) - warnings.Add(pva2.TextPrompt); - else - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.SelectAttribute"), pva2.ProductAttribute.GetLocalized(a => a.Name))); + warnings.Add(T("ShoppingCart.SelectAttribute", pva2.TextPrompt.HasValue() ? pva2.TextPrompt : pva2.ProductAttribute.GetLocalized(a => a.Name))); } } } + // check if there is a selected attribute combination and if it is active + if (warnings.Count == 0 && selectedAttributes.HasValue()) + { + if (combination == null) + { + combination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes); + } + + if (combination != null && !combination.IsActive) + { + warnings.Add(T("ShoppingCart.NotAvailable")); + } + } + if (warnings.Count == 0) { - var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(selectedAttributes); + var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(selectedAttributes).ToList(); foreach (var pvaValue in pvaValues) { if (pvaValue.ValueType == ProductVariantAttributeValueType.ProductLinkage) @@ -568,18 +577,16 @@ public virtual IList GetShoppingCartItemAttributeWarnings(Customer custo foreach (var linkageWarning in linkageWarnings) { - string msg = _localizationService.GetResource("ShoppingCart.ProductLinkageAttributeWarning").FormatWith( + warnings.Add(T("ShoppingCart.ProductLinkageAttributeWarning", pvaValue.ProductVariantAttribute.ProductAttribute.GetLocalized(a => a.Name), pvaValue.GetLocalized(a => a.Name), - linkageWarning); - - warnings.Add(msg); + linkageWarning) + ); } } else { - string msg = _localizationService.GetResource("ShoppingCart.ProductLinkageProductNotLoading").FormatWith(pvaValue.LinkedProductId); - warnings.Add(msg); + warnings.Add(T("ShoppingCart.ProductLinkageProductNotLoading", pvaValue.LinkedProductId)); } } } @@ -588,6 +595,61 @@ public virtual IList GetShoppingCartItemAttributeWarnings(Customer custo return warnings; } + /// + /// Validates if all required attributes are selected + /// + /// Selected attributes + /// Product + /// bool + public virtual bool AreAllAttributesForCombinationSelected(string selectedAttributes, Product product) + { + Guard.ArgumentNotNull(() => product); + + var hasAttributeCombinations = _sciRepository.Context.QueryForCollection(product, (Product p) => p.ProductVariantAttributeCombinations).Any(); + if (!hasAttributeCombinations) + return true; + + //selected attributes + var pva1Collection = _productAttributeParser.ParseProductVariantAttributes(selectedAttributes); + + //existing product attributes + var pva2Collection = product.ProductVariantAttributes; + foreach (var pva2 in pva2Collection) + { + if (pva2.IsRequired) + { + bool found = false; + //selected product attributes + foreach (var pva1 in pva1Collection) + { + if (pva1.Id == pva2.Id) + { + var pvaValuesStr = _productAttributeParser.ParseValues(selectedAttributes, pva1.Id); + foreach (string str1 in pvaValuesStr) + { + if (!String.IsNullOrEmpty(str1.Trim())) + { + found = true; + break; + } + } + } + } + + if (!found) + { + return found; + } + } + else + { + return true; + } + } + + return true; + } + /// /// Validates shopping cart item (gift card) /// @@ -613,27 +675,34 @@ public virtual IList GetShoppingCartItemGiftCardWarnings(ShoppingCartTyp string giftCardMessage = string.Empty; _productAttributeParser.GetGiftCardAttribute(selectedAttributes, - out giftCardRecipientName, out giftCardRecipientEmail, - out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + out giftCardRecipientName, out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); - if (String.IsNullOrEmpty(giftCardRecipientName)) - warnings.Add(_localizationService.GetResource("ShoppingCart.RecipientNameError")); + if (String.IsNullOrEmpty(giftCardRecipientName)) + { + warnings.Add(T("ShoppingCart.RecipientNameError")); + } 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(_localizationService.GetResource("ShoppingCart.RecipientEmailError")); + { + warnings.Add(T("ShoppingCart.RecipientEmailError")); + } } - if (String.IsNullOrEmpty(giftCardSenderName)) - warnings.Add(_localizationService.GetResource("ShoppingCart.SenderNameError")); + if (String.IsNullOrEmpty(giftCardSenderName)) + { + warnings.Add(T("ShoppingCart.SenderNameError")); + } 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(_localizationService.GetResource("ShoppingCart.SenderEmailError")); + { + warnings.Add(T("ShoppingCart.SenderEmailError")); + } } } @@ -652,19 +721,27 @@ public virtual IList GetBundleItemWarnings(ProductBundleItem bundleItem) if (bundleItem != null) { - string name = bundleItem.GetLocalizedName(); + var name = bundleItem.GetLocalizedName(); if (!bundleItem.Published) - warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.BundleItemUnpublished").FormatWith(name)); + { + warnings.Add(T("ShoppingCart.Bundle.BundleItemUnpublished", name)); + } if (bundleItem.ProductId == 0 || bundleItem.BundleProductId == 0 || bundleItem.Product == null || bundleItem.BundleProduct == null) - warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.MissingProduct").FormatWith(name)); + { + warnings.Add(T("ShoppingCart.Bundle.MissingProduct", name)); + } if (bundleItem.Quantity <= 0) - warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.Quantity").FormatWith(name)); + { + warnings.Add(T("ShoppingCart.Bundle.Quantity", name)); + } if (bundleItem.Product.IsDownload || bundleItem.Product.IsRecurring) - warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.ProductResrictions").FormatWith(name)); + { + warnings.Add(T("ShoppingCart.Bundle.ProductResrictions", name)); + } } return warnings; @@ -764,7 +841,7 @@ public virtual IList GetShoppingCartWarnings(IList GetShoppingCartWarnings(IList GetShoppingCartWarnings(IList GetShoppingCartWarnings(IList GetShoppingCartWarnings(IList a.TextPrompt))) warnings.Add(ca2.GetLocalized(a => a.TextPrompt)); else - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.SelectAttribute"), ca2.GetLocalized(a => a.Name))); + warnings.Add(T("ShoppingCart.SelectAttribute", ca2.GetLocalized(a => a.Name))); } } } @@ -872,7 +955,7 @@ public virtual OrganizedShoppingCartItem FindShoppingCartItemInTheCart(IList AddToCart(Customer customer, Product product, Shoppi if (cartType == ShoppingCartType.ShoppingCart && !_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart, customer)) { - warnings.Add("Shopping cart is disabled"); + warnings.Add(T("ShoppingCart.IsDisabled")); return warnings; } if (cartType == ShoppingCartType.Wishlist && !_permissionService.Authorize(StandardPermissionProvider.EnableWishlist, customer)) { - warnings.Add("Wishlist is disabled"); + warnings.Add(T("Wishlist.IsDisabled")); return warnings; } if (quantity <= 0) { - warnings.Add(_localizationService.GetResource("ShoppingCart.QuantityShouldPositive")); + warnings.Add(T("ShoppingCart.QuantityShouldPositive")); return warnings; } //if (parentItemId.HasValue && (parentItemId.Value == 0 || bundleItem == null || bundleItem.Id == 0)) //{ - // warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.BundleItemNotFound").FormatWith(bundleItem.GetLocalizedName())); + // warnings.Add(T("ShoppingCart.Bundle.BundleItemNotFound", bundleItem.GetLocalizedName())); // return warnings; //} @@ -1011,17 +1098,17 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi //maximum items validation if (cartType == ShoppingCartType.ShoppingCart && cart.Count >= _shoppingCartSettings.MaximumShoppingCartItems) { - warnings.Add(_localizationService.GetResource("ShoppingCart.MaximumShoppingCartItems")); + warnings.Add(T("ShoppingCart.MaximumShoppingCartItems")); return warnings; } else if (cartType == ShoppingCartType.Wishlist && cart.Count >= _shoppingCartSettings.MaximumWishlistItems) { - warnings.Add(_localizationService.GetResource("ShoppingCart.MaximumWishlistItems")); + warnings.Add(T("ShoppingCart.MaximumWishlistItems")); return warnings; } var now = DateTime.UtcNow; - var cartItem = new ShoppingCartItem() + var cartItem = new ShoppingCartItem { ShoppingCartType = cartType, StoreId = storeId, @@ -1035,8 +1122,9 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi }; if (bundleItem != null) + { cartItem.BundleItemId = bundleItem.Id; - + } if (ctx == null) { @@ -1082,7 +1170,7 @@ public virtual void AddToCart(AddToCartContext ctx) _downloadService, _catalogSettings, null, ctx.Warnings, true, ctx.BundleItemId); if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.Attributes.HasValue()) - ctx.Warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.NoAttributes")); + ctx.Warnings.Add(T("ShoppingCart.Bundle.NoAttributes")); if (ctx.Product.IsGiftCard) ctx.Attributes = ctx.AttributeForm.AddGiftCardAttribute(ctx.Attributes, ctx.Product.Id, _productAttributeParser, ctx.BundleItemId); @@ -1121,7 +1209,7 @@ public virtual void AddToCart(AddToCartContext ctx) if (ctx.BundleItem == null) { - AddToCartStore(ctx); + AddToCartStoring(ctx); } } @@ -1129,7 +1217,7 @@ public virtual void AddToCart(AddToCartContext ctx) /// Stores the shopping card items in the database /// /// Add to cart context - public virtual void AddToCartStore(AddToCartContext ctx) + public virtual void AddToCartStoring(AddToCartContext ctx) { if (ctx.Warnings.Count == 0 && ctx.Item != null) { @@ -1255,7 +1343,10 @@ public virtual IList Copy(OrganizedShoppingCartItem sci, Customer custom if (sci == null) throw new ArgumentNullException("item"); - var addToCartContext = new AddToCartContext(); + var addToCartContext = new AddToCartContext + { + Customer = customer + }; addToCartContext.Warnings = AddToCart(customer, sci.Item.Product, cartType, storeId, sci.Item.AttributesXml, sci.Item.CustomerEnteredPrice, sci.Item.Quantity, addRequiredProductsIfEnabled, addToCartContext); @@ -1271,7 +1362,7 @@ public virtual IList Copy(OrganizedShoppingCartItem sci, Customer custom } } - AddToCartStore(addToCartContext); + AddToCartStoring(addToCartContext); return addToCartContext.Warnings; } diff --git a/src/Libraries/SmartStore.Services/Payments/IPaymentMethod.cs b/src/Libraries/SmartStore.Services/Payments/IPaymentMethod.cs index 9e4113fd46..9dc48a1b85 100644 --- a/src/Libraries/SmartStore.Services/Payments/IPaymentMethod.cs +++ b/src/Libraries/SmartStore.Services/Payments/IPaymentMethod.cs @@ -97,6 +97,11 @@ public partial interface IPaymentMethod : IProvider, IUserEditable #region Properties + /// + /// Gets a value indicating whether the payment method is active and should be offered to customers + /// + bool IsActive { get; } + /// /// Gets a value indicating whether the payment method requires user input /// before proceeding (e.g. CreditCard, DirectDebit etc.) diff --git a/src/Libraries/SmartStore.Services/Payments/IPaymentMethodFilter.cs b/src/Libraries/SmartStore.Services/Payments/IPaymentMethodFilter.cs new file mode 100644 index 0000000000..4ecd12457d --- /dev/null +++ b/src/Libraries/SmartStore.Services/Payments/IPaymentMethodFilter.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Payments +{ + public partial interface IPaymentMethodFilter + { + /// + /// Gets a value indicating whether a payment method should be filtered out + /// + /// Payment filter request + /// true filter out method, false do not filter out method + bool IsExcluded(PaymentFilterRequest request); + + /// + /// Get URL for filter configuration + /// + /// Payment provider system name + /// URL for filter configuration + string GetConfigurationUrl(string systemName); + } + + + public partial class PaymentFilterRequest + { + /// + /// The payment method to be checked + /// + public Provider PaymentMethod { get; set; } + + /// + /// The context shopping cart + /// + public IList Cart { get; set; } + + /// + /// The context store identifier + /// + public int StoreId { get; set; } + + /// + /// The context customer + /// + public Customer Customer { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs b/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs index 0d56507652..ced73b9b10 100644 --- a/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; using SmartStore.Core.Plugins; namespace SmartStore.Services.Payments @@ -12,10 +14,18 @@ public partial interface IPaymentService /// /// Load active payment methods /// - /// Filter payment methods by customer; null to load all records - /// Load records allows only in specified store; pass 0 to load all records + /// 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 - IEnumerable> LoadActivePaymentMethods(int? filterByCustomerId = null, int storeId = 0); + IEnumerable> LoadActivePaymentMethods( + Customer customer = null, + IList cart = null, + int storeId = 0, + PaymentMethodType[] types = null, + bool provideFallbackMethod = true); /// /// Determines whether a payment method is active\enabled for a shop @@ -37,6 +47,37 @@ public partial interface IPaymentService IEnumerable> LoadAllPaymentMethods(int storeId = 0); + /// + /// Gets all payment method extra data + /// + /// List of payment method objects + IList GetAllPaymentMethods(); + + /// + /// Gets payment method extra data by system name + /// + /// Provider system name + /// Payment method entity + PaymentMethod GetPaymentMethodBySystemName(string systemName); + + /// + /// Insert payment method extra data + /// + /// Payment method + void InsertPaymentMethod(PaymentMethod paymentMethod); + + /// + /// Updates payment method extra data + /// + /// Payment method + void UpdatePaymentMethod(PaymentMethod paymentMethod); + + /// + /// Delete payment method extra data + /// + /// Payment method + void DeletePaymentMethod(PaymentMethod paymentMethod); + /// /// Pre process a payment @@ -168,5 +209,10 @@ public partial interface IPaymentService /// Masked credit card number string GetMaskedCreditCardNumber(string creditCardNumber); + /// + /// Gets all payment filters + /// + /// List of payment filters + IList GetAllPaymentMethodFilters(); } } diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs b/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs index 742dd98296..d9cd258259 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs @@ -1,15 +1,15 @@ using System; -using System.Linq; using System.Collections.Generic; +using System.Linq; +using System.Web.Routing; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Plugins; using SmartStore.Services.Orders; -using System.Web.Routing; namespace SmartStore.Services.Payments { - public static class PaymentExtentions + public static class PaymentExtentions { /// /// Is payment method active? @@ -28,6 +28,9 @@ public static bool IsPaymentMethodActive(this Provider paymentMe if (paymentSettings.ActivePaymentMethodSystemNames == null) return false; + if (!paymentMethod.Value.IsActive) + return false; + return paymentSettings.ActivePaymentMethodSystemNames.Contains(paymentMethod.Metadata.SystemName, StringComparer.OrdinalIgnoreCase); } diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentMethodBase.cs b/src/Libraries/SmartStore.Services/Payments/PaymentMethodBase.cs index b4809a78b3..3510b71b2a 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentMethodBase.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentMethodBase.cs @@ -166,6 +166,17 @@ public virtual RouteInfo GetPaymentInfoHandlerRoute() #region Properties + /// + /// Gets a value indicating whether the payment method is active and should be offered to customers + /// + public virtual bool IsActive + { + get + { + return true; + } + } + /// /// Gets a value indicating whether the payment method requires user input /// before proceeding (e.g. CreditCard, DirectDebit etc.) diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index 2fb61b0f4e..0bce57bb5e 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs @@ -2,27 +2,38 @@ using System.Collections.Generic; using System.Linq; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Events; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Localization; using SmartStore.Core.Plugins; -using SmartStore.Services.Configuration; -using SmartStore.Services.Localization; namespace SmartStore.Services.Payments { - /// - /// Payment service - /// - public partial class PaymentService : IPaymentService + /// + /// Payment service + /// + public partial class PaymentService : IPaymentService { - #region Fields + #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 IPluginFinder _pluginFinder; private readonly ShoppingCartSettings _shoppingCartSettings; - private readonly ISettingService _settingService; - private readonly ILocalizationService _localizationService; private readonly IProviderManager _providerManager; + private readonly ICommonServices _services; + private readonly ITypeFinder _typeFinder; #endregion @@ -36,55 +47,97 @@ public partial class PaymentService : IPaymentService /// Shopping cart settings /// Plugin service public PaymentService( + IRepository paymentMethodRepository, PaymentSettings paymentSettings, - IPluginFinder pluginFinder, ShoppingCartSettings shoppingCartSettings, - ISettingService settingService, - ILocalizationService localizationService, - IProviderManager providerManager) + IProviderManager providerManager, + ICommonServices services, + ITypeFinder typeFinder) { - this._paymentSettings = paymentSettings; - this._pluginFinder = pluginFinder; - this._shoppingCartSettings = shoppingCartSettings; - this._settingService = settingService; - this._localizationService = localizationService; - this._providerManager = providerManager; - } + _paymentMethodRepository = paymentMethodRepository; + _paymentSettings = paymentSettings; + _shoppingCartSettings = shoppingCartSettings; + _providerManager = providerManager; + _services = services; + _typeFinder = typeFinder; + + T = NullLocalizer.Instance; + } - #endregion + public Localizer T { get; set; } - #region Methods + #endregion - /// - /// Load active payment methods - /// - /// Filter payment methods by customer; null to load all records - /// Load records allows only in specified store; pass 0 to load all records - /// Payment methods - public virtual IEnumerable> LoadActivePaymentMethods(int? filterByCustomerId = null, int storeId = 0) + #region Methods + + /// + /// 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 + public virtual IEnumerable> LoadActivePaymentMethods( + Customer customer = null, + IList cart = null, + int storeId = 0, + PaymentMethodType[] types = null, + bool provideFallbackMethod = true) { - var allMethods = LoadAllPaymentMethods(storeId); - var activeMethods = allMethods - .Where(p => _paymentSettings.ActivePaymentMethodSystemNames.Contains(p.Metadata.SystemName, StringComparer.InvariantCultureIgnoreCase)); + IList allFilters = null; + IEnumerable> allProviders = null; + + var filterRequest = new PaymentFilterRequest + { + Cart = cart, + StoreId = storeId, + Customer = customer + }; + + if (types != null && types.Any()) + allProviders = LoadAllPaymentMethods(storeId).Where(x => types.Contains(x.Value.PaymentMethodType)); + else + allProviders = 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; + + return true; + }); - if (!activeMethods.Any()) + if (!activeProviders.Any() && provideFallbackMethod) { - var fallbackMethod = allMethods.FirstOrDefault(); + var fallbackMethod = allProviders.FirstOrDefault(x => x.IsPaymentMethodActive(_paymentSettings)); + + if (fallbackMethod == null) + fallbackMethod = allProviders.FirstOrDefault(); + if (fallbackMethod != null) { - _paymentSettings.ActivePaymentMethodSystemNames.Clear(); - _paymentSettings.ActivePaymentMethodSystemNames.Add(fallbackMethod.Metadata.SystemName); - _settingService.SaveSetting(_paymentSettings); return new Provider[] { fallbackMethod }; } else { if (DataSettings.DatabaseIsInstalled()) - throw Error.Application("At least one payment method provider is required to be active."); + throw new SmartException(T("Payment.OneActiveMethodProviderRequired")); } } - return activeMethods; + return activeProviders; } /// @@ -122,6 +175,73 @@ public virtual IEnumerable> LoadAllPaymentMethods(int s } + /// + /// Gets all payment method extra data + /// + /// List of payment method objects + public virtual IList GetAllPaymentMethods() + { + var methods = _paymentMethodRepository.TableUntracked.ToList(); + return methods; + } + + /// + /// Gets payment method extra data by system name + /// + /// Provider system name + /// Payment method entity + public virtual PaymentMethod GetPaymentMethodBySystemName(string systemName) + { + if (systemName.HasValue()) + { + return _paymentMethodRepository.Table.FirstOrDefault(x => x.PaymentMethodSystemName == systemName); + } + return null; + } + + /// + /// Insert payment method extra data + /// + /// Payment method + public virtual void InsertPaymentMethod(PaymentMethod paymentMethod) + { + if (paymentMethod == null) + throw new ArgumentNullException("paymentMethod"); + + _paymentMethodRepository.Insert(paymentMethod); + + _services.EventPublisher.EntityInserted(paymentMethod); + } + + /// + /// Updates payment method extra data + /// + /// Payment method + public virtual void UpdatePaymentMethod(PaymentMethod paymentMethod) + { + if (paymentMethod == null) + throw new ArgumentNullException("paymentMethod"); + + _paymentMethodRepository.Update(paymentMethod); + + _services.EventPublisher.EntityUpdated(paymentMethod); + } + + /// + /// Delete payment method extra data + /// + /// Payment method + public virtual void DeletePaymentMethod(PaymentMethod paymentMethod) + { + if (paymentMethod == null) + throw new ArgumentNullException("paymentMethod"); + + _paymentMethodRepository.Delete(paymentMethod); + + _services.EventPublisher.EntityDeleted(paymentMethod); + } + + /// /// Pre process a payment /// @@ -138,7 +258,7 @@ public virtual PreProcessPaymentResult PreProcessPayment(ProcessPaymentRequest p { var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); return paymentMethod.Value.PreProcessPayment(processPaymentRequest); } @@ -167,9 +287,12 @@ public virtual ProcessPaymentResult ProcessPayment(ProcessPaymentRequest process processPaymentRequest.CreditCardNumber = processPaymentRequest.CreditCardNumber.Replace(" ", ""); processPaymentRequest.CreditCardNumber = processPaymentRequest.CreditCardNumber.Replace("-", ""); } - var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); - if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + + var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); + + if (paymentMethod == null) + throw new SmartException(T("Payment.CouldNotLoadMethod")); + return paymentMethod.Value.ProcessPayment(processPaymentRequest); } } @@ -181,10 +304,14 @@ public virtual ProcessPaymentResult ProcessPayment(ProcessPaymentRequest process /// Payment info required for an order processing public virtual void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) { - var paymentMethod = LoadPaymentMethodBySystemName(postProcessPaymentRequest.Order.PaymentMethodSystemName); - if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); - paymentMethod.Value.PostProcessPayment(postProcessPaymentRequest); + if (postProcessPaymentRequest.Order.PaymentMethodSystemName.HasValue()) + { + var paymentMethod = LoadPaymentMethodBySystemName(postProcessPaymentRequest.Order.PaymentMethodSystemName); + if (paymentMethod == null) + throw new SmartException(T("Payment.CouldNotLoadMethod")); + + paymentMethod.Value.PostProcessPayment(postProcessPaymentRequest); + } } /// @@ -230,15 +357,14 @@ public virtual bool CanRePostProcessPayment(Order order) public virtual decimal GetAdditionalHandlingFee(IList cart, string paymentMethodSystemName) { var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); - if (paymentMethod == null) - return decimal.Zero; + var paymentMethodAdditionalFee = (paymentMethod != null ? paymentMethod.Value.GetAdditionalHandlingFee(cart) : decimal.Zero); - decimal result = paymentMethod.Value.GetAdditionalHandlingFee(cart); - - if (_shoppingCartSettings.RoundPricesDuringCalculation) - result = Math.Round(result, 2); + if (_shoppingCartSettings.RoundPricesDuringCalculation) + { + paymentMethodAdditionalFee = Math.Round(paymentMethodAdditionalFee, 2); + } - return result; + return paymentMethodAdditionalFee; } @@ -265,7 +391,7 @@ public virtual CapturePaymentResult Capture(CapturePaymentRequest capturePayment { var paymentMethod = LoadPaymentMethodBySystemName(capturePaymentRequest.Order.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); try { @@ -274,7 +400,7 @@ public virtual CapturePaymentResult Capture(CapturePaymentRequest capturePayment catch (NotSupportedException) { var result = new CapturePaymentResult(); - result.AddError(_localizationService.GetResource("Common.Payment.NoCaptureSupport")); + result.AddError(T("Common.Payment.NoCaptureSupport")); return result; } catch @@ -320,7 +446,7 @@ public virtual RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequ { var paymentMethod = LoadPaymentMethodBySystemName(refundPaymentRequest.Order.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); try { @@ -329,7 +455,7 @@ public virtual RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequ catch (NotSupportedException) { var result = new RefundPaymentResult(); - result.AddError(_localizationService.GetResource("Common.Payment.NoRefundSupport")); + result.AddError(T("Common.Payment.NoRefundSupport")); return result; } catch @@ -362,7 +488,7 @@ public virtual VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest) { var paymentMethod = LoadPaymentMethodBySystemName(voidPaymentRequest.Order.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); try { @@ -371,7 +497,7 @@ public virtual VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest) catch (NotSupportedException) { var result = new VoidPaymentResult(); - result.AddError(_localizationService.GetResource("Common.Payment.NoVoidSupport")); + result.AddError(T("Common.Payment.NoVoidSupport")); return result; } catch @@ -392,6 +518,7 @@ public virtual RecurringPaymentType GetRecurringPaymentType(string paymentMethod var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); if (paymentMethod == null) return RecurringPaymentType.NotSupported; + return paymentMethod.Value.RecurringPaymentType; } @@ -414,7 +541,7 @@ public virtual ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReques { var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); try { @@ -423,7 +550,7 @@ public virtual ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReques catch (NotSupportedException) { var result = new ProcessPaymentResult(); - result.AddError(_localizationService.GetResource("Common.Payment.NoRecurringPaymentSupport")); + result.AddError(T("Common.Payment.NoRecurringPaymentSupport")); return result; } catch @@ -445,7 +572,7 @@ public virtual CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurri var paymentMethod = LoadPaymentMethodBySystemName(cancelPaymentRequest.Order.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); try { @@ -454,7 +581,7 @@ public virtual CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurri catch (NotSupportedException) { var result = new CancelRecurringPaymentResult(); - result.AddError(_localizationService.GetResource("Common.Payment.NoRecurringPaymentSupport")); + result.AddError(T("Common.Payment.NoRecurringPaymentSupport")); return result; } catch @@ -475,6 +602,7 @@ public virtual PaymentMethodType GetPaymentMethodType(string paymentMethodSystem var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); if (paymentMethod == null) return PaymentMethodType.Unknown; + return paymentMethod.Value.PaymentMethodType; } @@ -500,6 +628,27 @@ public virtual string GetMaskedCreditCardNumber(string creditCardNumber) return maskedChars + last4; } - #endregion - } + public virtual IList GetAllPaymentMethodFilters() + { + if (_paymentMethodFilterTypes == null) + { + lock (_lock) + { + if (_paymentMethodFilterTypes == null) + { + _paymentMethodFilterTypes = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true) + .ToList(); + } + } + } + + var paymentMethodFilters = _paymentMethodFilterTypes + .Select(x => EngineContext.Current.ContainerManager.ResolveUnregistered(x) as IPaymentMethodFilter) + .ToList(); + + return paymentMethodFilters; + } + + #endregion + } } diff --git a/src/Libraries/SmartStore.Services/Payments/PostProcessPaymentRequest.cs b/src/Libraries/SmartStore.Services/Payments/PostProcessPaymentRequest.cs index 1ca115635f..f14a6cc6aa 100644 --- a/src/Libraries/SmartStore.Services/Payments/PostProcessPaymentRequest.cs +++ b/src/Libraries/SmartStore.Services/Payments/PostProcessPaymentRequest.cs @@ -16,5 +16,10 @@ public partial class PostProcessPaymentRequest /// Whether the customer clicked the button to re-post the payment process /// public bool IsRePostProcessPayment { get; set; } + + /// + /// URL to a payment provider to fulfill the payment. The .NET core will redirect to it. + /// + public string RedirectUrl { get; set; } } } diff --git a/src/Libraries/SmartStore.Services/Payments/ProcessPaymentRequest.cs b/src/Libraries/SmartStore.Services/Payments/ProcessPaymentRequest.cs index 805af5ed70..a3ba77cb5b 100644 --- a/src/Libraries/SmartStore.Services/Payments/ProcessPaymentRequest.cs +++ b/src/Libraries/SmartStore.Services/Payments/ProcessPaymentRequest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Orders; namespace SmartStore.Services.Payments { @@ -12,7 +13,9 @@ public partial class ProcessPaymentRequest { public ProcessPaymentRequest() { - this.CustomProperties = new Dictionary(); + CustomProperties = new Dictionary(); + IsMultiOrder = false; + ShoppingCartItems = new List(); } /// @@ -45,6 +48,11 @@ public ProcessPaymentRequest() /// public string PaymentMethodSystemName { get; set; } + /// + /// Gets or sets a payment method identifier + /// + public bool IsMultiOrder { get; set; } + /// /// Use that dictionary for any payment method or checkout flow specific data /// @@ -144,6 +152,8 @@ public ProcessPaymentRequest() /// public int RecurringTotalCycles { get; set; } + public IList ShoppingCartItems { get; set; } + #endregion } diff --git a/src/Libraries/SmartStore.Services/Pdf/WkHtmlToPdfConverter.cs b/src/Libraries/SmartStore.Services/Pdf/WkHtmlToPdfConverter.cs index ca7dc05776..4694512828 100644 --- a/src/Libraries/SmartStore.Services/Pdf/WkHtmlToPdfConverter.cs +++ b/src/Libraries/SmartStore.Services/Pdf/WkHtmlToPdfConverter.cs @@ -1,21 +1,16 @@ using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Text; -using System.Web; using NReco.PdfGenerator; using SmartStore.Core.Logging; +using SmartStore.Utilities; namespace SmartStore.Services.Pdf { public class WkHtmlToPdfConverter : IPdfConverter { - private readonly HttpContextBase _httpContext; - - public WkHtmlToPdfConverter(HttpContextBase httpContext) + public WkHtmlToPdfConverter() { - this._httpContext = httpContext; Logger = NullLogger.Instance; } @@ -76,6 +71,7 @@ internal HtmlToPdfConverter CreateWkConverter(PdfConvertSettings settings) PageHeight = settings.PageHeight, PageWidth = settings.PageWidth, Size = (PageSize)(int)settings.Size, + PdfToolPath = FileSystemHelper.TempDir("wkhtmltopdf") }; if (settings.Margins != null) diff --git a/src/Libraries/SmartStore.Services/Properties/Settings.Designer.cs b/src/Libraries/SmartStore.Services/Properties/Settings.Designer.cs index 55c9eb47fe..0d4d638b36 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.17929 +// Laufzeitversion:4.0.30319.34209 // // Ä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", "11.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/src/Libraries/SmartStore.Services/Security/AclService.cs b/src/Libraries/SmartStore.Services/Security/AclService.cs index 62057fe3b6..f1ea323ff2 100644 --- a/src/Libraries/SmartStore.Services/Security/AclService.cs +++ b/src/Libraries/SmartStore.Services/Security/AclService.cs @@ -60,8 +60,7 @@ public bool HasActiveAcl { if (!_hasActiveAcl.HasValue) { - var query = _aclRecordRepository.Where(x => !x.IsIdle); - _hasActiveAcl = query.Any(); + _hasActiveAcl = _aclRecordRepository.TableUntracked.Any(x => !x.IsIdle); } return _hasActiveAcl.Value; } @@ -159,7 +158,7 @@ public virtual void InsertAclRecord(T entity, int customerRoleId) where T : B int entityId = entity.Id; string entityName = typeof(T).Name; - var aclRecord = new AclRecord() + var aclRecord = new AclRecord { EntityId = entityId, EntityName = entityName, @@ -180,7 +179,6 @@ public virtual void UpdateAclRecord(AclRecord aclRecord) _aclRecordRepository.Update(aclRecord); - //cache _cacheManager.RemoveByPattern(ACLRECORD_PATTERN_KEY); } diff --git a/src/Libraries/SmartStore.Services/Security/PermissionService.cs b/src/Libraries/SmartStore.Services/Security/PermissionService.cs index a105e18520..a3ada9721a 100644 --- a/src/Libraries/SmartStore.Services/Security/PermissionService.cs +++ b/src/Libraries/SmartStore.Services/Security/PermissionService.cs @@ -10,10 +10,10 @@ namespace SmartStore.Services.Security { - /// - /// Permission service - /// - public partial class PermissionService : IPermissionService + /// + /// Permission service + /// + public partial class PermissionService : IPermissionService { #region Constants /// @@ -141,9 +141,11 @@ orderby pr.Id /// Permissions public virtual IList GetAllPermissionRecords() { - var query = from pr in _permissionRecordRepository.Table - orderby pr.Name - select pr; + var query = + from pr in _permissionRecordRepository.Table + orderby pr.Category, pr.Name + select pr; + var permissions = query.ToList(); return permissions; } @@ -182,70 +184,59 @@ public virtual void UpdatePermissionRecord(PermissionRecord permission) /// Permission provider public virtual void InstallPermissions(IPermissionProvider permissionProvider) { - using (var scope = new DbContextScope(_permissionRecordRepository.Context, autoDetectChanges: false)) + using (var scope = new DbContextScope(_permissionRecordRepository.Context, autoDetectChanges: false, autoCommit: false)) { - try + //install new permissions + var permissions = permissionProvider.GetPermissions(); + foreach (var permission in permissions) { - _permissionRecordRepository.AutoCommitEnabled = false; - _customerRoleRepository.AutoCommitEnabled = false; - - //install new permissions - var permissions = permissionProvider.GetPermissions(); - foreach (var permission in permissions) + var permission1 = GetPermissionRecordBySystemName(permission.SystemName); + if (permission1 == null) { - var permission1 = GetPermissionRecordBySystemName(permission.SystemName); - if (permission1 == null) + //new permission (install it) + permission1 = new PermissionRecord() { - //new permission (install it) - permission1 = new PermissionRecord() - { - Name = permission.Name, - SystemName = permission.SystemName, - Category = permission.Category, - }; - - // default customer role mappings - var defaultPermissions = permissionProvider.GetDefaultPermissions(); - foreach (var defaultPermission in defaultPermissions) + Name = permission.Name, + SystemName = permission.SystemName, + Category = permission.Category, + }; + + // default customer role mappings + var defaultPermissions = permissionProvider.GetDefaultPermissions(); + foreach (var defaultPermission in defaultPermissions) + { + var customerRole = _customerService.GetCustomerRoleBySystemName(defaultPermission.CustomerRoleSystemName); + if (customerRole == null) { - var customerRole = _customerService.GetCustomerRoleBySystemName(defaultPermission.CustomerRoleSystemName); - if (customerRole == null) + //new role (save it) + customerRole = new CustomerRole { - //new role (save it) - customerRole = new CustomerRole() - { - Name = defaultPermission.CustomerRoleSystemName, - Active = true, - SystemName = defaultPermission.CustomerRoleSystemName - }; - _customerService.InsertCustomerRole(customerRole); - } - - - var defaultMappingProvided = (from p in defaultPermission.PermissionRecords - where p.SystemName == permission1.SystemName - select p).Any(); - var mappingExists = (from p in customerRole.PermissionRecords - where p.SystemName == permission1.SystemName - select p).Any(); - if (defaultMappingProvided && !mappingExists) - { - permission1.CustomerRoles.Add(customerRole); - } + Name = defaultPermission.CustomerRoleSystemName, + Active = true, + SystemName = defaultPermission.CustomerRoleSystemName + }; + _customerService.InsertCustomerRole(customerRole); } - //save new permission - InsertPermissionRecord(permission1); + + var defaultMappingProvided = (from p in defaultPermission.PermissionRecords + where p.SystemName == permission1.SystemName + select p).Any(); + var mappingExists = (from p in customerRole.PermissionRecords + where p.SystemName == permission1.SystemName + select p).Any(); + if (defaultMappingProvided && !mappingExists) + { + permission1.CustomerRoles.Add(customerRole); + } } - } - scope.Commit(); - } - finally - { - _permissionRecordRepository.AutoCommitEnabled = true; - _customerRoleRepository.AutoCommitEnabled = true; + //save new permission + InsertPermissionRecord(permission1); + } } + + scope.Commit(); } } diff --git a/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs b/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs index 01b83e6a38..4f1032077a 100644 --- a/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs +++ b/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs @@ -49,10 +49,12 @@ public partial class StandardPermissionProvider : IPermissionProvider public static readonly PermissionRecord ManageMaintenance = new PermissionRecord { Name = "Admin area. Manage Maintenance", SystemName = "ManageMaintenance", Category = "Configuration" }; public static readonly PermissionRecord UploadPictures = new PermissionRecord { Name = "Admin area. Upload Pictures", SystemName = "UploadPictures", Category = "Configuration" }; public static readonly PermissionRecord ManageScheduleTasks = new PermissionRecord { Name = "Admin area. Manage Schedule Tasks", SystemName = "ManageScheduleTasks", Category = "Configuration" }; + public static readonly PermissionRecord ManageExports = new PermissionRecord { Name = "Admin area. Manage Exports", SystemName = "ManageExports", Category = "Configuration" }; + public static readonly PermissionRecord ManageImports = new PermissionRecord { Name = "Admin area. Manage Imports", SystemName = "ManageImports", Category = "Configuration" }; + public static readonly PermissionRecord ManageUrlRecords = new PermissionRecord { Name = "Admin area. Manage Url Records", SystemName = "ManageUrlRecords", Category = "Configuration" }; - - //public store permissions - public static readonly PermissionRecord DisplayPrices = new PermissionRecord { Name = "Public store. Display Prices", SystemName = "DisplayPrices", Category = "PublicStore" }; + //public store permissions + public static readonly PermissionRecord DisplayPrices = new PermissionRecord { Name = "Public store. Display Prices", SystemName = "DisplayPrices", Category = "PublicStore" }; public static readonly PermissionRecord EnableShoppingCart = new PermissionRecord { Name = "Public store. Enable shopping cart", SystemName = "EnableShoppingCart", Category = "PublicStore" }; public static readonly PermissionRecord EnableWishlist = new PermissionRecord { Name = "Public store. Enable wishlist", SystemName = "EnableWishlist", Category = "PublicStore" }; public static readonly PermissionRecord PublicStoreAllowNavigation = new PermissionRecord { Name = "Public store. Allow navigation", SystemName = "PublicStoreAllowNavigation", Category = "PublicStore" }; @@ -103,7 +105,10 @@ public virtual IEnumerable GetPermissions() ManageMaintenance, UploadPictures, ManageScheduleTasks, - DisplayPrices, + ManageExports, + ManageImports, + ManageUrlRecords, + DisplayPrices, EnableShoppingCart, EnableWishlist, PublicStoreAllowNavigation, @@ -160,6 +165,9 @@ public virtual IEnumerable GetDefaultPermissions() ManageMaintenance, UploadPictures, ManageScheduleTasks, + ManageExports, + ManageImports, + ManageUrlRecords, DisplayPrices, EnableShoppingCart, EnableWishlist, diff --git a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs index 4f6f5f00ae..d3bfa5a04d 100644 --- a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq.Expressions; using SmartStore.Core; using SmartStore.Core.Domain.Seo; @@ -23,6 +24,13 @@ public partial interface IUrlRecordService /// URL record UrlRecord GetUrlRecordById(int urlRecordId); + /// + /// Gets URL records by identifiers + /// + /// + /// List of URL records + IList GetUrlRecordsByIds(int[] urlRecordIds); + /// /// Inserts an URL record /// @@ -45,11 +53,24 @@ public partial interface IUrlRecordService /// /// Gets all URL records /// - /// Slug /// Page index /// Page size + /// Slug + /// Entity name + /// Entity identifier + /// Whether to load only active records + /// Language identifier /// Customer collection - IPagedList GetAllUrlRecords(string slug, int pageIndex, int pageSize); + IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, int? entityId, int? languageId, bool? isActive); + + /// + /// Gets all URL records for the specified entity + /// + /// Type + /// Entity + /// Specifies whether only active URL records should be returned + /// List of URL records + IList GetUrlRecordsFor(string entityName, int entityId, bool activeOnly = false); /// /// Find slug @@ -80,5 +101,20 @@ public partial interface IUrlRecordService /// Name of a property /// Url record UrlRecord SaveSlug(T entity, Expression> nameProperty) where T : BaseEntity, ISlugSupported; + + /// + /// Get number of slugs per entity + /// + /// URL record identifier + /// Dictionary of slugs per entity count + Dictionary CountSlugsPerEntity(int[] urlRecordIds); + + /// + /// Get number of slugs per entity + /// + /// Entity name + /// Entity identifier + /// Number of slugs per entity + int CountSlugsPerEntity(string entityName, int entityId); } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs b/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs index b192ed8de9..29c29d13ef 100644 --- a/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs +++ b/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs @@ -1,17 +1,15 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using SmartStore.Core; using SmartStore.Core.Domain.Blogs; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.News; using SmartStore.Core.Domain.Seo; using SmartStore.Core.Infrastructure; using SmartStore.Services.Localization; using SmartStore.Utilities; +using SmartStore.Core.Localization; namespace SmartStore.Services.Seo { @@ -79,32 +77,6 @@ public static string GetSeName(this NewsItem newsItem) #region Forum - /// - /// Gets ForumGroup SE (search engine) name - /// - /// ForumGroup - /// ForumGroup SE (search engine) name - public static string GetSeName(this ForumGroup forumGroup) - { - if (forumGroup == null) - throw new ArgumentNullException("forumGroup"); - string seName = GetSeName(forumGroup.Name); - return seName; - } - - /// - /// Gets Forum SE (search engine) name - /// - /// Forum - /// Forum SE (search engine) name - public static string GetSeName(this Forum forum) - { - if (forum == null) - throw new ArgumentNullException("forum"); - string seName = GetSeName(forum.Name); - return seName; - } - /// /// Gets ForumTopic SE (search engine) name /// @@ -189,20 +161,20 @@ public static string GetSeName(this T entity, if (languageId > 0) { - //ensure that we have at least two published languages + // ensure that we have at least two published languages bool loadLocalizedValue = true; if (ensureTwoPublishedLanguages) { var totalPublishedLanguages = languageService.GetLanguagesCount(false); loadLocalizedValue = totalPublishedLanguages >= 2; } - //localized value + // localized value if (loadLocalizedValue) { result = urlRecordService.GetActiveSlug(entity.Id, entityName, languageId); } } - //set default value if required + // set default value if required if (String.IsNullOrEmpty(result) && returnDefaultValue) { result = urlRecordService.GetActiveSlug(entity.Id, entityName, 0); @@ -247,31 +219,40 @@ public static string ValidateSeName(this T entity, if (entity == null) throw new ArgumentNullException("entity"); - //use name if sename is not specified + // use name if sename is not specified if (String.IsNullOrWhiteSpace(seName) && !String.IsNullOrWhiteSpace(name)) seName = name; - //validation + // validation seName = GetSeName(seName, seoSettings); - //max length + // max length seName = seName.Truncate(400); if (String.IsNullOrWhiteSpace(seName)) { if (ensureNotEmpty) { - //use entity identifier as sename if empty + // use entity identifier as sename if empty seName = entity.Id.ToString(); } else { - //return. no need for further processing + // return. no need for further processing return seName; } } - //ensure this sename is not reserved yet + // validate and alter SeName if it could be interpreted as SEO code + if (LocalizationHelper.IsValidCultureCode(seName)) + { + if (seName.Length == 2) + { + seName = seName + "-0"; + } + } + + // ensure this sename is not reserved yet string entityName = typeof(T).Name; int i = 2; var tempSeName = seName; @@ -280,14 +261,14 @@ public static string ValidateSeName(this T entity, while (true) { - //check whether such slug already exists (and that is not the current product) + // check whether such slug already exists (and that it's not the current entity) var urlRecord = urlRecordService.GetBySlug(tempSeName) ?? extraSlugLookup(tempSeName); var reserved1 = urlRecord != null && !(urlRecord.EntityId == entity.Id && urlRecord.EntityName.Equals(entityName, StringComparison.InvariantCultureIgnoreCase)); if (!reserved1 && urlRecord != null && languageId.HasValue) reserved1 = (urlRecord.LanguageId != languageId.Value); - //and it's not in the list of reserved slugs + // and it's not in the list of reserved slugs var reserved2 = seoSettings.ReservedUrlRecordSlugs.Contains(tempSeName, StringComparer.InvariantCultureIgnoreCase); if (!reserved1 && !reserved2) break; @@ -323,7 +304,8 @@ public static string GetSeName(string name, SeoSettings seoSettings) return SeoHelper.GetSeName( name, seoSettings == null ? false : seoSettings.ConvertNonWesternChars, - seoSettings == null ? false : seoSettings.AllowUnicodeCharsInUrls); + seoSettings == null ? false : seoSettings.AllowUnicodeCharsInUrls, + seoSettings == null ? null : seoSettings.SeoNameCharConversion); } #endregion diff --git a/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs b/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs index f2b404cfdd..497555026d 100644 --- a/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs +++ b/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs @@ -96,28 +96,36 @@ private void WriteManufacturers(UrlHelper urlHelper) private void WriteProducts(UrlHelper urlHelper) { - var ctx = new ProductSearchContext() + var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; + + var ctx = new ProductSearchContext { OrderBy = ProductSortingEnum.CreatedOn, - PageSize = int.MaxValue, + PageSize = 500, StoreId = _storeContext.CurrentStoreIdIfMultiStoreMode, VisibleIndividuallyOnly = true }; - var products = _productService.SearchProducts(ctx); - var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; - 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); - } + 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); + 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) { diff --git a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs index 5d071432d3..38fe4a7bda 100644 --- a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using SmartStore.Core; @@ -15,8 +16,10 @@ public partial class UrlRecordService : IUrlRecordService { #region Constants - private const string URLRECORD_ACTIVE_BY_ID_NAME_LANGUAGE_KEY = "SmartStore.urlrecord.active.id-name-language-{0}-{1}-{2}"; - private const string URLRECORD_PATTERN_KEY = "SmartStore.urlrecord."; + // {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."; #endregion @@ -24,25 +27,23 @@ public partial class UrlRecordService : IUrlRecordService private readonly IRepository _urlRecordRepository; private readonly ICacheManager _cacheManager; + private readonly SeoSettings _seoSettings; #endregion #region Ctor - public UrlRecordService(ICacheManager cacheManager, IRepository urlRecordRepository) + public UrlRecordService(ICacheManager cacheManager, IRepository urlRecordRepository, SeoSettings seoSettings) { this._cacheManager = cacheManager; this._urlRecordRepository = urlRecordRepository; + this._seoSettings = seoSettings; } #endregion #region Methods - /// - /// Deletes an URL record - /// - /// URL record public virtual void DeleteUrlRecord(UrlRecord urlRecord) { if (urlRecord == null) @@ -50,15 +51,9 @@ public virtual void DeleteUrlRecord(UrlRecord urlRecord) _urlRecordRepository.Delete(urlRecord); - //cache _cacheManager.RemoveByPattern(URLRECORD_PATTERN_KEY); } - /// - /// Gets an URL record - /// - /// URL record identifier - /// URL record public virtual UrlRecord GetUrlRecordById(int urlRecordId) { if (urlRecordId == 0) @@ -68,10 +63,18 @@ public virtual UrlRecord GetUrlRecordById(int urlRecordId) return urlRecord; } - /// - /// Inserts an URL record - /// - /// URL record + public virtual IList GetUrlRecordsByIds(int[] urlRecordIds) + { + if (urlRecordIds == null || urlRecordIds.Length == 0) + return new List(); + + var urlRecords = _urlRecordRepository.Table + .Where(x => urlRecordIds.Contains(x.Id)) + .ToList(); + + return urlRecords; + } + public virtual void InsertUrlRecord(UrlRecord urlRecord) { if (urlRecord == null) @@ -83,10 +86,6 @@ public virtual void InsertUrlRecord(UrlRecord urlRecord) _cacheManager.RemoveByPattern(URLRECORD_PATTERN_KEY); } - /// - /// Updates the URL record - /// - /// URL record public virtual void UpdateUrlRecord(UrlRecord urlRecord) { if (urlRecord == null) @@ -98,72 +97,107 @@ public virtual void UpdateUrlRecord(UrlRecord urlRecord) _cacheManager.RemoveByPattern(URLRECORD_PATTERN_KEY); } - /// - /// Find URL record - /// - /// Slug - /// Found URL record - public virtual UrlRecord GetBySlug(string slug) + public virtual IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, int? entityId, int? languageId, bool? isActive) { - if (String.IsNullOrEmpty(slug)) - return null; + var query = _urlRecordRepository.Table; - var query = from ur in _urlRecordRepository.Table - where ur.Slug == slug - select ur; - var urlRecord = query.FirstOrDefault(); - return urlRecord; - } + if (slug.HasValue()) + query = query.Where(x => x.Slug.Contains(slug)); - /// - /// Gets all URL records - /// - /// Slug - /// Page index - /// Page size - /// Customer collection - public virtual IPagedList GetAllUrlRecords(string slug, int pageIndex, int pageSize) - { - var query = _urlRecordRepository.Table; - if (!String.IsNullOrWhiteSpace(slug)) - query = query.Where(ur => ur.Slug.Contains(slug)); - query = query.OrderBy(ur => ur.Slug); + if (entityName.HasValue()) + query = query.Where(x => x.EntityName == entityName); + + if (entityId.HasValue) + query = query.Where(x => x.EntityId == entityId.Value); + + if (isActive.HasValue) + query = query.Where(x => x.IsActive == isActive.Value); - var urlRecords = new PagedList(query, pageIndex, pageSize); - return urlRecords; + if (languageId.HasValue) + query = query.Where(x => x.LanguageId == languageId); + + query = query.OrderBy(x => x.Slug); + + var urlRecords = new PagedList(query, pageIndex, pageSize); + return urlRecords; } - /// - /// Find slug - /// - /// Entity identifier - /// Entity name - /// Language identifier - /// Found slug + public virtual IList GetUrlRecordsFor(string entityName, int entityId, bool activeOnly = false) + { + Guard.ArgumentNotEmpty(() => entityName); + + var query = from ur in _urlRecordRepository.Table + where ur.EntityId == entityId && + ur.EntityName == entityName + select ur; + + if (activeOnly) + { + query = query.Where(ur => ur.IsActive); + } + + return query.ToList(); + } + + public virtual UrlRecord GetBySlug(string slug) + { + // INFO: (mc) Caching unnecessary here. This is not a 'bottleneck' function. + if (String.IsNullOrEmpty(slug)) + return null; + + 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) - { - string key = string.Format(URLRECORD_ACTIVE_BY_ID_NAME_LANGUAGE_KEY, entityId, entityName, languageId); - return _cacheManager.Get(key, () => - { - 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; - var slug = query.FirstOrDefault(); - return slug ?? ""; - }); + { + 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; + }); + + var key = GenerateKey(entityId, entityName, languageId); + if (!allActiveSlugs.TryGetValue(key, out slug)) + { + return string.Empty; + } + } + else + { + string cacheKey = string.Format(URLRECORD_KEY, entityId, entityName, languageId); + slug = _cacheManager.Get(cacheKey, () => + { + 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 slug; } - /// - /// Save slug - /// - /// Type - /// Entity - /// Slug - /// Language ID public virtual UrlRecord SaveSlug(T entity, string slug, int languageId) where T : BaseEntity, ISlugSupported { if (entity == null) @@ -184,18 +218,18 @@ orderby ur.Id descending var activeUrlRecord = allUrlRecords.FirstOrDefault(x => x.IsActive); if (activeUrlRecord == null && !string.IsNullOrWhiteSpace(slug)) { - //find in non-active records with the specified slug + // find in non-active records with the specified slug var nonActiveRecordWithSpecifiedSlug = allUrlRecords.FirstOrDefault(x => x.Slug.Equals(slug, StringComparison.InvariantCultureIgnoreCase) && !x.IsActive); if (nonActiveRecordWithSpecifiedSlug != null) { - //mark non-active record as active + // mark non-active record as active nonActiveRecordWithSpecifiedSlug.IsActive = true; UpdateUrlRecord(nonActiveRecordWithSpecifiedSlug); } else { - //new record - var urlRecord = new UrlRecord() + // new record + var urlRecord = new UrlRecord { EntityId = entity.Id, EntityName = entityName, @@ -210,27 +244,27 @@ orderby ur.Id descending if (activeUrlRecord != null && string.IsNullOrWhiteSpace(slug)) { - //disable the previous active URL record + // disable the previous active URL record activeUrlRecord.IsActive = false; UpdateUrlRecord(activeUrlRecord); } if (activeUrlRecord != null && !string.IsNullOrWhiteSpace(slug)) { - //is it the same slug as in active URL record? + // is it the same slug as in active URL record? if (activeUrlRecord.Slug.Equals(slug, StringComparison.InvariantCultureIgnoreCase)) { - //yes. do nothing - //P.S. wrote this way for more source code readability + // yes. do nothing + // P.S. wrote this way for more source code readability } else { - //find in non-active records with the specified slug + // find in non-active records with the specified slug var nonActiveRecordWithSpecifiedSlug = allUrlRecords .FirstOrDefault(x => x.Slug.Equals(slug, StringComparison.InvariantCultureIgnoreCase) && !x.IsActive); if (nonActiveRecordWithSpecifiedSlug != null) { - //mark non-active record as active + // mark non-active record as active nonActiveRecordWithSpecifiedSlug.IsActive = true; UpdateUrlRecord(nonActiveRecordWithSpecifiedSlug); @@ -255,10 +289,10 @@ orderby ur.Id descending } else { - //insert new record - //we do not update the existing record because we should track all previously entered slugs - //to ensure that URLs will work fine - var urlRecord = new UrlRecord() + // insert new record + // we do not update the existing record because we should track all previously entered slugs + // to ensure that URLs will work fine + var urlRecord = new UrlRecord { EntityId = entity.Id, EntityName = entityName, @@ -269,7 +303,7 @@ orderby ur.Id descending InsertUrlRecord(urlRecord); result = urlRecord; - //disable the previous active URL record + // disable the previous active URL record activeUrlRecord.IsActive = false; UpdateUrlRecord(activeUrlRecord); } @@ -281,13 +315,6 @@ orderby ur.Id descending return result; } - /// - /// Save slug - /// - /// Type - /// Entity - /// Name of a property - /// Url record public virtual UrlRecord SaveSlug(T entity, Expression> nameProperty) where T : BaseEntity, ISlugSupported { string name = nameProperty.Compile().Invoke(entity); @@ -298,6 +325,41 @@ public virtual UrlRecord SaveSlug(T entity, Expression> nameP return SaveSlug(entity, existingSeName, 0); } + private string GenerateKey(int entityId, string entityName, int languageId) + { + return "{0}.{1}.{2}".FormatInvariant(entityId, entityName, languageId); + } + + public virtual Dictionary CountSlugsPerEntity(int[] urlRecordIds) + { + if (urlRecordIds == null || urlRecordIds.Length == 0) + return new Dictionary(); + + var query = + from x in _urlRecordRepository.TableUntracked + where urlRecordIds.Contains(x.Id) + select new + { + Id = x.Id, + Count = _urlRecordRepository.TableUntracked.Where(y => y.EntityName == x.EntityName && y.EntityId == x.EntityId).Count() + }; + + var result = query + .ToList() + .ToDictionary(x => x.Id, x => x.Count); + + return result; + } + + public virtual int CountSlugsPerEntity(string entityName, int entityId) + { + var count = _urlRecordRepository.Table + .Where(x => x.EntityName == entityName && x.EntityId == entityId) + .Count(); + + return count; + } + #endregion } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/ServiceCacheConsumer.cs b/src/Libraries/SmartStore.Services/ServiceCacheConsumer.cs index 702c0e504e..e3fd3f18b8 100644 --- a/src/Libraries/SmartStore.Services/ServiceCacheConsumer.cs +++ b/src/Libraries/SmartStore.Services/ServiceCacheConsumer.cs @@ -6,6 +6,8 @@ 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 { @@ -20,7 +22,7 @@ public class ServiceCacheConsumer : private readonly ICacheManager _cacheManager; - public ServiceCacheConsumer(Func cache) + public ServiceCacheConsumer(Func cache) { this._cacheManager = cache("static"); } @@ -49,5 +51,5 @@ public void HandleEvent(EntityDeleted eventMessage) { _cacheManager.Remove(STORE_LANGUAGE_MAP_KEY); } - } + } } diff --git a/src/Libraries/SmartStore.Services/Shipping/GetShippingOptionRequest.cs b/src/Libraries/SmartStore.Services/Shipping/GetShippingOptionRequest.cs index 1962f97177..3439b6fd4f 100644 --- a/src/Libraries/SmartStore.Services/Shipping/GetShippingOptionRequest.cs +++ b/src/Libraries/SmartStore.Services/Shipping/GetShippingOptionRequest.cs @@ -16,6 +16,11 @@ public GetShippingOptionRequest() this.Items = new List(); } + /// + /// The context store identifier + /// + public int StoreId { get; set; } + /// /// Gets or sets a customer /// diff --git a/src/Libraries/SmartStore.Services/Shipping/IShipmentService.cs b/src/Libraries/SmartStore.Services/Shipping/IShipmentService.cs index ac79efe3dc..0a544651ad 100644 --- a/src/Libraries/SmartStore.Services/Shipping/IShipmentService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/IShipmentService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Shipping; @@ -29,12 +30,19 @@ IPagedList GetAllShipments(string trackingNumber, DateTime? createdFro int pageIndex, int pageSize); /// - /// Get shipment by identifiers + /// Get shipments by identifiers /// /// Shipment identifiers /// Shipments IList GetShipmentsByIds(int[] shipmentIds); + /// + /// Get shipments by order identifiers + /// + /// Order identifiers + /// Shipments + Multimap GetShipmentsByOrderIds(int[] orderIds); + /// /// Gets a shipment /// diff --git a/src/Libraries/SmartStore.Services/Shipping/IShippingMethodFilter.cs b/src/Libraries/SmartStore.Services/Shipping/IShippingMethodFilter.cs new file mode 100644 index 0000000000..2fbed519bc --- /dev/null +++ b/src/Libraries/SmartStore.Services/Shipping/IShippingMethodFilter.cs @@ -0,0 +1,35 @@ +using SmartStore.Core.Domain.Shipping; + +namespace SmartStore.Services.Shipping +{ + public partial interface IShippingMethodFilter + { + /// + /// Gets a value indicating whether a shipping method should be filtered out + /// + /// Shipping filter request + /// true filter out method, false do not filter out method + bool IsExcluded(ShippingFilterRequest request); + + /// + /// Get URL for filter configuration + /// + /// Shipping method identifier + /// URL for filter configuration + string GetConfigurationUrl(int shippingMethodId); + } + + + public partial class ShippingFilterRequest + { + /// + /// The shipping method to be checked + /// + public ShippingMethod ShippingMethod { get; set; } + + /// + /// Shipping method request + /// + public GetShippingOptionRequest Option { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Shipping/IShippingRateComputationMethod.cs b/src/Libraries/SmartStore.Services/Shipping/IShippingRateComputationMethod.cs index a3a2568398..fd13e71b4c 100644 --- a/src/Libraries/SmartStore.Services/Shipping/IShippingRateComputationMethod.cs +++ b/src/Libraries/SmartStore.Services/Shipping/IShippingRateComputationMethod.cs @@ -32,5 +32,10 @@ public partial interface IShippingRateComputationMethod : IProvider, IUserEditab /// Gets a shipment tracker /// IShipmentTracker ShipmentTracker { get; } + + /// + /// Gets a value indicating whether the shipping rate computation method is active and should be offered to customers + /// + bool IsActive { get; } } } diff --git a/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs b/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs index a8fccc065b..9018fab4b6 100644 --- a/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs @@ -1,5 +1,6 @@ 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; @@ -52,9 +53,9 @@ public partial interface IShippingService /// /// Gets all shipping methods /// - /// The country indentifier to filter by + /// Shipping option request to filter out shipping methods. null to load all shipping methods. /// Shipping method collection - IList GetAllShippingMethods(int? filterByCountryId = null); + IList GetAllShippingMethods(GetShippingOptionRequest request = null); /// /// Inserts a shipping method @@ -96,8 +97,9 @@ public partial interface IShippingService /// /// Shopping cart /// Shipping address + /// Store identifier /// Shipment package - GetShippingOptionRequest CreateShippingOptionRequest(IList cart, Address shippingAddress); + GetShippingOptionRequest CreateShippingOptionRequest(IList cart, Address shippingAddress, int storeId); /// /// Gets available shipping options @@ -109,5 +111,11 @@ public partial interface IShippingService /// Shipping options GetShippingOptionResponse GetShippingOptions(IList cart, Address shippingAddress, string allowedShippingRateComputationMethodSystemName = "", int storeId = 0); + + /// + /// Gets all shipping method filters + /// + /// List of shipping method filters + IList GetAllShippingMethodFilters(); } } diff --git a/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs b/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs index 93d4e53d04..3a8c261164 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Events; -using SmartStore.Core.Plugins; using SmartStore.Services.Orders; namespace SmartStore.Services.Shipping @@ -84,7 +84,7 @@ public virtual void DeleteShipment(Shipment shipment) public virtual IPagedList GetAllShipments(string trackingNumber, DateTime? createdFrom, DateTime? createdTo, int pageIndex, int pageSize) { - var query = _shipmentRepository.Expand(_shipmentRepository.Table, x => x.Order); + var query = _shipmentRepository.Table.Expand(x => x.Order); if (!String.IsNullOrEmpty(trackingNumber)) query = query.Where(s => s.TrackingNumber.Contains(trackingNumber)); if (createdFrom.HasValue) @@ -108,7 +108,7 @@ public virtual IList GetShipmentsByIds(int[] shipmentIds) if (shipmentIds == null || shipmentIds.Length == 0) return new List(); - var query = from o in _shipmentRepository.Expand(_shipmentRepository.Table, x => x.Order) + var query = from o in _shipmentRepository.Table.Expand(x => x.Order) where shipmentIds.Contains(o.Id) select o; var shipments = query.ToList(); @@ -123,6 +123,24 @@ where shipmentIds.Contains(o.Id) return sortedOrders; } + public virtual Multimap GetShipmentsByOrderIds(int[] orderIds) + { + Guard.ArgumentNotNull(() => orderIds); + + var query = + from x in _shipmentRepository.TableUntracked.Expand(x => x.ShipmentItems) + where orderIds.Contains(x.OrderId) + select x; + + var map = query + .OrderBy(x => x.OrderId) + .ThenBy(x => x.CreatedOnUtc) + .ToList() + .ToMultimap(x => x.OrderId, x => x); + + return map; + } + /// /// Gets a shipment /// @@ -221,7 +239,20 @@ public virtual void InsertShipmentItem(ShipmentItem shipmentItem) //event notifications _eventPublisher.EntityInserted(shipmentItem); - _eventPublisher.PublishOrderUpdated(shipmentItem.Shipment.Order); + + if (shipmentItem.Shipment != null && shipmentItem.Shipment.Order != null) + { + _eventPublisher.PublishOrderUpdated(shipmentItem.Shipment.Order); + } + else + { + var shipment = _shipmentRepository.Table + .Expand(x => x.Order) + .FirstOrDefault(x => x.Id == shipmentItem.ShipmentId); + + if (shipment != null) + _eventPublisher.PublishOrderUpdated(shipment.Order); + } } /// @@ -237,7 +268,20 @@ public virtual void UpdateShipmentItem(ShipmentItem shipmentItem) //event notifications _eventPublisher.EntityUpdated(shipmentItem); - _eventPublisher.PublishOrderUpdated(shipmentItem.Shipment.Order); + + if (shipmentItem.Shipment != null && shipmentItem.Shipment.Order != null) + { + _eventPublisher.PublishOrderUpdated(shipmentItem.Shipment.Order); + } + else + { + var shipment = _shipmentRepository.Table + .Expand(x => x.Order) + .FirstOrDefault(x => x.Id == shipmentItem.ShipmentId); + + if (shipment != null) + _eventPublisher.PublishOrderUpdated(shipment.Order); + } } #endregion diff --git a/src/Libraries/SmartStore.Services/Shipping/ShippingExtentions.cs b/src/Libraries/SmartStore.Services/Shipping/ShippingExtentions.cs index fa8059e906..fedfbe3891 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShippingExtentions.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShippingExtentions.cs @@ -18,11 +18,13 @@ public static bool IsShippingRateComputationMethodActive(this Provider _shippingMethodRepository; - private readonly ICacheManager _cacheManager; + 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 IProductService _productService; private readonly ICheckoutAttributeParser _checkoutAttributeParser; private readonly IGenericAttributeService _genericAttributeService; - private readonly ILocalizationService _localizationService; private readonly ShippingSettings _shippingSettings; - private readonly IPluginFinder _pluginFinder; private readonly IEventPublisher _eventPublisher; private readonly ShoppingCartSettings _shoppingCartSettings; private readonly ISettingService _settingService; private readonly IProviderManager _providerManager; + private readonly ITypeFinder _typeFinder; #endregion @@ -58,67 +59,75 @@ public partial class ShippingService : IShippingService /// Event published /// Shopping cart settings /// Setting service - public ShippingService(ICacheManager cacheManager, + public ShippingService( IRepository shippingMethodRepository, ILogger logger, IProductAttributeParser productAttributeParser, IProductService productService, ICheckoutAttributeParser checkoutAttributeParser, IGenericAttributeService genericAttributeService, - ILocalizationService localizationService, ShippingSettings shippingSettings, - IPluginFinder pluginFinder, IEventPublisher eventPublisher, ShoppingCartSettings shoppingCartSettings, ISettingService settingService, - IProviderManager providerManager) + IProviderManager providerManager, + ITypeFinder typeFinder) { - this._cacheManager = cacheManager; this._shippingMethodRepository = shippingMethodRepository; this._logger = logger; this._productAttributeParser = productAttributeParser; this._productService = productService; this._checkoutAttributeParser = checkoutAttributeParser; this._genericAttributeService = genericAttributeService; - this._localizationService = localizationService; this._shippingSettings = shippingSettings; - this._pluginFinder = pluginFinder; this._eventPublisher = eventPublisher; this._shoppingCartSettings = shoppingCartSettings; this._settingService = settingService; this._providerManager = providerManager; + this._typeFinder = typeFinder; + + T = NullLocalizer.Instance; } - #endregion - - #region Methods + public Localizer T { get; set; } - #region Shipping rate computation methods + #endregion - /// - /// Load active shipping rate computation methods - /// + #region Methods + + #region Shipping rate computation methods + + /// + /// Load active shipping rate computation methods + /// /// Load records allows only in specified store; pass 0 to load all records - /// Shipping rate computation methods + /// Shipping rate computation methods public virtual IEnumerable> LoadActiveShippingRateComputationMethods(int storeId = 0) { var allMethods = LoadAllShippingRateComputationMethods(storeId); + var activeMethods = allMethods - .Where(p => _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Contains(p.Metadata.SystemName, StringComparer.InvariantCultureIgnoreCase)); + .Where(p => p.Value.IsActive && _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Contains(p.Metadata.SystemName, StringComparer.InvariantCultureIgnoreCase)); if (!activeMethods.Any()) { - var fallbackMethod = allMethods.FirstOrDefault(); + var fallbackMethod = allMethods.FirstOrDefault(x => x.IsShippingRateComputationMethodActive(_shippingSettings)); + + if (fallbackMethod == null) + fallbackMethod = allMethods.FirstOrDefault(); + if (fallbackMethod != null) { _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Clear(); _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Add(fallbackMethod.Metadata.SystemName); _settingService.SaveSetting(_shippingSettings); + return new Provider[] { fallbackMethod }; } else { - throw Error.Application("At least one shipping method provider is required to be active."); + if (DataSettings.DatabaseIsInstalled()) + throw new SmartException(T("Shipping.OneActiveMethodProviderRequired")); } } @@ -177,37 +186,37 @@ public virtual ShippingMethod GetShippingMethodById(int shippingMethodId) return _shippingMethodRepository.GetById(shippingMethodId); } - - /// - /// Gets all shipping methods - /// - /// The country indentifier to filter by - /// Shipping method collection - public virtual IList GetAllShippingMethods(int? filterByCountryId = null) + + public virtual IList GetAllShippingMethods(GetShippingOptionRequest request = null) { - if (filterByCountryId.HasValue && filterByCountryId.Value > 0) - { - var query1 = from sm in _shippingMethodRepository.Table - where - sm.RestrictedCountries.Select(c => c.Id).Contains(filterByCountryId.Value) - select sm.Id; - - var query2 = from sm in _shippingMethodRepository.Table - where !query1.Contains(sm.Id) - orderby sm.DisplayOrder - select sm; - - var shippingMethods = query2.ToList(); - return shippingMethods; - } - else - { - var query = from sm in _shippingMethodRepository.Table - orderby sm.DisplayOrder - select sm; - var shippingMethods = query.ToList(); - return shippingMethods; - } + var query = + from sm in _shippingMethodRepository.Table + orderby sm.DisplayOrder + select sm; + + var allMethods = query.ToList(); + + if (request == null) + return allMethods; + + IList allFilters = null; + var filterRequest = new ShippingFilterRequest { Option = request }; + + var activeShippingMethods = allMethods.Where(s => + { + // shipping method filtering + if (allFilters == null) + allFilters = GetAllShippingMethodFilters(); + + filterRequest.ShippingMethod = s; + + if (allFilters.Any(x => x.IsExcluded(filterRequest))) + return false; + + return true; + }); + + return activeShippingMethods.ToList(); } /// @@ -262,7 +271,7 @@ public virtual decimal GetShoppingCartItemWeight(OrganizedShoppingCartItem shopp if (!String.IsNullOrEmpty(shoppingCartItem.Item.AttributesXml)) { - var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); + var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml).ToList(); foreach (var pvaValue in pvaValues) { @@ -308,11 +317,11 @@ public virtual decimal GetShoppingCartTotalWeight(IList(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService); @@ -323,6 +332,7 @@ public virtual decimal GetShoppingCartTotalWeight(IList /// Shopping cart /// Shipping address + /// Store identifier /// Shipment package - public virtual GetShippingOptionRequest CreateShippingOptionRequest(IList cart, - Address shippingAddress) + public virtual GetShippingOptionRequest CreateShippingOptionRequest(IList cart, Address shippingAddress, int storeId) { var request = new GetShippingOptionRequest(); + request.StoreId = storeId; request.Customer = cart.GetCustomer(); - request.Items = new List(); - foreach (var sc in cart) - if (sc.Item.IsShipEnabled) - request.Items.Add(sc); request.ShippingAddress = shippingAddress; request.CountryFrom = null; request.StateProvinceFrom = null; request.ZipPostalCodeFrom = string.Empty; + + request.Items = new List(); + + foreach (var sc in cart) + { + if (sc.Item.IsShipEnabled) + request.Items.Add(sc); + } + return request; } @@ -357,7 +373,8 @@ public virtual GetShippingOptionRequest CreateShippingOptionRequest(IListFilter 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"); @@ -365,14 +382,16 @@ public virtual GetShippingOptionResponse GetShippingOptions(IList String.IsNullOrWhiteSpace(allowedShippingRateComputationMethodSystemName) || allowedShippingRateComputationMethodSystemName.Equals(srcm.Metadata.SystemName, StringComparison.InvariantCultureIgnoreCase)) .ToList(); + if (shippingRateComputationMethods.Count == 0) - throw new SmartException("Shipping rate computation method could not be loaded"); + throw new SmartException(T("Shipping.CouldNotLoadMethod")); //get shipping options foreach (var srcm in shippingRateComputationMethods) @@ -382,9 +401,11 @@ public virtual GetShippingOptionResponse GetShippingOptions(IList 0 && result.Errors.Count > 0) result.Errors.Clear(); } - - //no shipping options loaded - if (result.ShippingOptions.Count == 0 && result.Errors.Count == 0) - result.Errors.Add(_localizationService.GetResource("Checkout.ShippingOptionCouldNotBeLoaded")); + + //no shipping options loaded + if (result.ShippingOptions.Count == 0 && result.Errors.Count == 0) + { + result.Errors.Add(T("Checkout.ShippingOptionCouldNotBeLoaded")); + } return result; } + public virtual IList GetAllShippingMethodFilters() + { + if (_shippingMethodFilterTypes == null) + { + lock (_lock) + { + if (_shippingMethodFilterTypes == null) + { + _shippingMethodFilterTypes = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true) + .ToList(); + } + } + } + + var shippingMethodFilters = _shippingMethodFilterTypes + .Select(x => EngineContext.Current.ContainerManager.ResolveUnregistered(x) as IShippingMethodFilter) + .ToList(); + + return shippingMethodFilters; + } + #endregion #endregion diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index ddaf0207ca..1edfe58738 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -10,7 +10,7 @@ Properties SmartStore.Services SmartStore.Services - v4.5 + v4.5.1 512 @@ -62,31 +62,36 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + True + + + ..\..\packages\CronExpressionDescriptor.1.17.0\lib\net45\CronExpressionDescriptor.dll False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll - - + False - ..\..\packages\EPPlus.3.1.3.3\lib\net35\EPPlus.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - + False - ..\..\packages\fasterflect.2.1.3\lib\net40\Fasterflect.dll + ..\..\packages\EPPlus.4.0.3\lib\net20\EPPlus.dll - - False - ..\..\packages\ImageResizer.3.4.2\lib\ImageResizer.dll + + ..\..\packages\ImageResizer.4.0.4\lib\net45\ImageResizer.dll + True - - False - ..\..\packages\ImageResizer.Plugins.PrettyGifs.3.4.2\lib\ImageResizer.Plugins.PrettyGifs.dll + + ..\..\packages\ImageResizer.Plugins.PrettyGifs.4.0.4\lib\net45\ImageResizer.Plugins.PrettyGifs.dll + True + + + ..\..\packages\LumenWorksCsvReader.3.9\lib\net20\LumenWorks.Framework.IO.dll + True ..\..\packages\MaxMind.GeoIP.2.1.17\lib\net35\MaxMind.GeoIP.dll @@ -96,13 +101,16 @@ True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - False - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\ncrontab.2.0.0\lib\net20\NCrontab.dll + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True - + False - ..\..\packages\NReco.PdfGenerator.1.1.3.0\lib\net20\NReco.PdfGenerator.dll + ..\..\packages\NReco.PdfGenerator.1.1.5.0\lib\net20\NReco.PdfGenerator.dll @@ -110,39 +118,46 @@ + ..\..\packages\System.Linq.Dynamic.1.0.0\lib\net40\System.Linq.Dynamic.dll + + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll + + ..\..\packages\UAParser.2.0.0.0\lib\net40-Client\UAParser.dll + True + @@ -151,10 +166,82 @@ Properties\AssemblyVersionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -198,7 +285,6 @@ - @@ -230,7 +316,6 @@ - @@ -247,7 +332,6 @@ - @@ -281,8 +365,6 @@ - - @@ -301,12 +383,9 @@ - - - @@ -414,6 +493,7 @@ + @@ -429,13 +509,17 @@ + + + + + - - - + + @@ -519,8 +603,13 @@ Nop_Services_EuropaCheckVatService_checkVatService + + + + + - - + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.DevTools/AdminMenu.cs b/src/Plugins/SmartStore.DevTools/AdminMenu.cs index e0893067e1..25ae68e381 100644 --- a/src/Plugins/SmartStore.DevTools/AdminMenu.cs +++ b/src/Plugins/SmartStore.DevTools/AdminMenu.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Routing; -using System.Web.Mvc; +using SmartStore.Collections; using SmartStore.Web.Framework.UI; -using SmartStore.Collections; namespace SmartStore.DevTools { @@ -18,8 +12,17 @@ protected override void BuildMenuCore(TreeNode pluginsNode) .Icon("code") .Action("ConfigurePlugin", "Plugin", new { systemName = "SmartStore.DevTools", area = "Admin" }) .ToItem(); - + pluginsNode.Prepend(menuItem); + + // uncomment to add to admin menu (see plugin sub-menu) + //var backendExtensionItem = new MenuItem().ToBuilder() + // .Text("Backend extension") + // .Icon("area-chart") + // .Action("BackendExtension", "DevTools", new { area = "SmartStore.DevTools" }) + // .ToItem(); + + //pluginsNode.Append(backendExtensionItem); } } } diff --git a/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs b/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs index 42d23e84a8..5700d55470 100644 --- a/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs +++ b/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using SmartStore.Core; +using System.Web.Mvc; +using SmartStore.DevTools.Models; using SmartStore.Services; -using SmartStore.Services.Configuration; -using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; namespace SmartStore.DevTools.Controllers @@ -15,40 +10,27 @@ namespace SmartStore.DevTools.Controllers public class DevToolsController : SmartController { - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; - private readonly IStoreService _storeService; - private readonly ISettingService _settingService; - - public DevToolsController( - IWorkContext workContext, - IStoreContext storeContext, - IStoreService storeService, - ISettingService settingService) + private readonly ICommonServices _services; + + public DevToolsController(ICommonServices services) { - _workContext = workContext; - _storeContext = storeContext; - _storeService = storeService; - _settingService = settingService; + _services = services; } - [AdminAuthorize] - [ChildActionOnly] + [AdminAuthorize, ChildActionOnly] public ActionResult Configure() { // load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var settings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var settings = _services.Settings.LoadSetting(storeScope); var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - storeDependingSettingHelper.GetOverrideKeys(settings, settings, storeScope, _settingService); + storeDependingSettingHelper.GetOverrideKeys(settings, settings, storeScope, _services.Settings); return View(settings); } - [AdminAuthorize] - [HttpPost] - [ChildActionOnly] + [HttpPost, AdminAuthorize, ChildActionOnly] public ActionResult Configure(ProfilerSettings model, FormCollection form) { if (!ModelState.IsValid) @@ -58,10 +40,10 @@ public ActionResult Configure(ProfilerSettings model, FormCollection form) // load settings for a chosen store scope var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); - storeDependingSettingHelper.UpdateSettings(model /*settings*/, form, storeScope, _settingService); - _settingService.ClearCache(); + storeDependingSettingHelper.UpdateSettings(model /*settings*/, form, storeScope, _services.Settings); + _services.Settings.ClearCache(); return Configure(); } @@ -71,5 +53,30 @@ public ActionResult MiniProfiler() return View(); } + public ActionResult WidgetZone(string widgetZone) + { + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var settings = _services.Settings.LoadSetting(storeScope); + + if (settings.DisplayWidgetZones) + { + ViewData["widgetZone"] = widgetZone; + + return View(); + } + + return new EmptyResult(); + } + + [AdminAuthorize] + public ActionResult BackendExtension() + { + var model = new BackendExtensionModel + { + Welcome = "Hello world!" + }; + + return View(model); + } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs b/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs index e562ef1d9f..4e4cb6d96d 100644 --- a/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs +++ b/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Web.Mvc; -using System.Reflection; using Autofac; -using Autofac.Core; using Autofac.Integration.Mvc; -using SmartStore.Core.Plugins; using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; using SmartStore.Core.Infrastructure.DependencyManagement; @@ -16,17 +9,18 @@ namespace SmartStore.DevTools { - public class DependencyRegistrar : IDependencyRegistrar + public class DependencyRegistrar : IDependencyRegistrar { public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, bool isActiveModule) { builder.RegisterType().As().InstancePerRequest(); - if (isActiveModule) + if (isActiveModule && DataSettings.DatabaseIsInstalled()) { // intercept ALL public store controller actions builder.RegisterType().AsActionFilterFor(); - + builder.RegisterType().AsActionFilterFor(); + //// intercept CatalogController's Product action //builder.RegisterType().AsResultFilterFor(x => x.Product(default(int), default(string))).InstancePerRequest(); //builder.RegisterType().AsActionFilterFor().InstancePerRequest(); diff --git a/src/Plugins/SmartStore.DevTools/Description.txt b/src/Plugins/SmartStore.DevTools/Description.txt index b08e4238d7..b169d8b16a 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: 1.1 -MinAppVersion: 2.1.0 +Version: 2.6.0 +MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.DevTools.dll ResourceRootKey: Plugins.Developer.DevTools \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Filters/ProfilerFilter.cs b/src/Plugins/SmartStore.DevTools/Filters/ProfilerFilter.cs index 85a252e7f1..2971dabe08 100644 --- a/src/Plugins/SmartStore.DevTools/Filters/ProfilerFilter.cs +++ b/src/Plugins/SmartStore.DevTools/Filters/ProfilerFilter.cs @@ -54,7 +54,10 @@ public void OnActionExecuted(ActionExecutedContext filterContext) if (!_profilerSettings.EnableMiniProfilerInPublicStore) return; - this._profiler.Value.StepStop("ActionFilter"); + if (!(filterContext.Result is ViewResultBase)) + { + this._profiler.Value.StepStop("ActionFilter"); + } } public void OnResultExecuting(ResultExecutingContext filterContext) @@ -74,15 +77,6 @@ public void OnResultExecuting(ResultExecutingContext filterContext) return; } - if (!filterContext.IsChildAction) - { - _widgetProvider.Value.RegisterAction( - "head_html_tag", - "MiniProfiler", - "DevTools", - new { area = "SmartStore.DevTools" }); - } - var viewName = result.ViewName; if (viewName.IsEmpty()) { @@ -91,6 +85,15 @@ public void OnResultExecuting(ResultExecutingContext filterContext) } this._profiler.Value.StepStart("ResultFilter", string.Format("{0}: {1}", result is PartialViewResult ? "Partial" : "View", viewName)); + + if (!filterContext.IsChildAction) + { + _widgetProvider.Value.RegisterAction( + "head_html_tag", + "MiniProfiler", + "DevTools", + new { area = "SmartStore.DevTools" }); + } } public void OnResultExecuted(ResultExecutedContext filterContext) @@ -110,6 +113,7 @@ public void OnResultExecuted(ResultExecutedContext filterContext) } this._profiler.Value.StepStop("ResultFilter"); + this._profiler.Value.StepStop("ActionFilter"); } private bool ShouldProfile(HttpContextBase ctx) diff --git a/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleResultFilter.cs b/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleResultFilter.cs index 4c565260c9..941ef81a1a 100644 --- a/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleResultFilter.cs +++ b/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleResultFilter.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using System.Web; using System.Web.Mvc; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.DevTools.Filters { diff --git a/src/Plugins/SmartStore.DevTools/Filters/WidgetZoneFilter.cs b/src/Plugins/SmartStore.DevTools/Filters/WidgetZoneFilter.cs new file mode 100644 index 0000000000..253fdd0b60 --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Filters/WidgetZoneFilter.cs @@ -0,0 +1,123 @@ +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.Utilities; +using System.IO; + +namespace SmartStore.DevTools.Filters +{ + public class WidgetZoneFilter : IActionFilter, IResultFilter + { + private readonly ICommonServices _services; + private readonly Lazy _widgetProvider; + private readonly ProfilerSettings _profilerSettings; + + public WidgetZoneFilter( + ICommonServices services, + Lazy widgetProvider, + ProfilerSettings profilerSettings) + { + this._services = services; + this._widgetProvider = widgetProvider; + this._profilerSettings = profilerSettings; + } + + public void OnActionExecuting(ActionExecutingContext filterContext) + { + if (!_profilerSettings.DisplayWidgetZones) + return; + } + + 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) + { + return; + } + + if (!this.ShouldRender(filterContext.HttpContext)) + { + return; + } + + if (!filterContext.IsChildAction) + { + _widgetProvider.Value.RegisterAction( + new Wildcard("*"), + "WidgetZone", + "DevTools", + new { area = "SmartStore.DevTools" }); + } + + var viewName = result.ViewName; + + if (viewName.IsEmpty()) + { + string action = (filterContext.RouteData.Values["action"] as string).EmptyNull(); + viewName = action; + + if (action == "WidgetsByZone") + { + var model = result.Model as WidgetZoneModel; + + filterContext.Result = new ViewResult + { + ViewName = "~/Plugins/SmartStore.DevTools/Views/DevTools/WidgetZone.cshtml", + }; + 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) + { + if (!_services.WorkContext.CurrentCustomer.IsAdmin()) + { + return ctx.Request.IsLocal; + } + + return true; + } + + } +} diff --git a/src/Plugins/SmartStore.DevTools/Models/BackendExtensionModel.cs b/src/Plugins/SmartStore.DevTools/Models/BackendExtensionModel.cs new file mode 100644 index 0000000000..2db540028f --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Models/BackendExtensionModel.cs @@ -0,0 +1,9 @@ +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.DevTools.Models +{ + public class BackendExtensionModel : ModelBase + { + public string Welcome { 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 1cbc8981b3..c2e585d0eb 100644 --- a/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs +++ b/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs @@ -1,25 +1,12 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Web; -using Microsoft.Web.Infrastructure.DynamicModuleHelper; +using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; -using SmartStore.Core.Plugins; -using SmartStore.Web.Framework; using StackExchange.Profiling; namespace SmartStore.DevTools { - public class ProfilerStarter : IPreApplicationStart - { - public void Start() - { - DynamicModuleUtility.RegisterModule(typeof(ProfilerHttpModule)); - SmartUrlRoutingModule.RegisterRoutablePath("/mini-profiler-resources/(.*)"); - } - } - public class ProfilerHttpModule : IHttpModule { private const string MP_KEY = "sm.miniprofiler.started"; @@ -57,6 +44,11 @@ private static bool ShouldProfile(HttpApplication app) if (app.Context == null || app.Context.Request == null) return false; + if (!DataSettings.DatabaseIsInstalled()) + { + return false; + } + var url = app.Context.Request.AppRelativeCurrentExecutionFilePath; if (url.StartsWith("~/admin", StringComparison.InvariantCultureIgnoreCase) || url.StartsWith("~/mini-profiler", StringComparison.InvariantCultureIgnoreCase) || url.StartsWith("~/bundles", StringComparison.InvariantCultureIgnoreCase)) { diff --git a/src/Plugins/SmartStore.DevTools/RouteProvider.cs b/src/Plugins/SmartStore.DevTools/RouteProvider.cs index 004bea97c8..607fff468d 100644 --- a/src/Plugins/SmartStore.DevTools/RouteProvider.cs +++ b/src/Plugins/SmartStore.DevTools/RouteProvider.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.DevTools { diff --git a/src/Plugins/SmartStore.DevTools/Services/ProfilerService.cs b/src/Plugins/SmartStore.DevTools/Services/ProfilerService.cs index 462451a123..e490e592f2 100644 --- a/src/Plugins/SmartStore.DevTools/Services/ProfilerService.cs +++ b/src/Plugins/SmartStore.DevTools/Services/ProfilerService.cs @@ -43,9 +43,12 @@ public void StepStop(string key) } IDisposable step; - if (this.steps[key].TryPop(out step)) + if (this.steps.ContainsKey(key)) { - step.Dispose(); + if (this.steps[key].TryPop(out step)) + { + step.Dispose(); + } } } diff --git a/src/Plugins/SmartStore.DevTools/Settings/ProfilerSettings.cs b/src/Plugins/SmartStore.DevTools/Settings/ProfilerSettings.cs index be2df2e227..88da20c658 100644 --- a/src/Plugins/SmartStore.DevTools/Settings/ProfilerSettings.cs +++ b/src/Plugins/SmartStore.DevTools/Settings/ProfilerSettings.cs @@ -9,5 +9,8 @@ namespace SmartStore.DevTools public class ProfilerSettings : ISettings { public bool EnableMiniProfilerInPublicStore { get; set; } + + public bool DisplayWidgetZones { 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 e9f7f57751..c8f46ee502 100644 --- a/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj +++ b/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj @@ -1,5 +1,5 @@  - + @@ -20,7 +20,7 @@ Properties SmartStore.DevTools SmartStore.DevTools - v4.5 + v4.5.1 512 ..\..\ @@ -35,6 +35,7 @@ + true @@ -74,19 +75,19 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - - ..\..\packages\Autofac.Mvc5.3.3.1\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll - - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.dll - False + + False + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll - False + + False + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll @@ -96,39 +97,47 @@ ..\..\packages\MiniProfiler.3.1.1.140\lib\net40\MiniProfiler.dll True + + False + ..\..\packages\MiniProfiler.EF6.3.0.11\lib\net40\MiniProfiler.EntityFramework6.dll + True + - + + + + + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - + @@ -141,17 +150,20 @@ + + + @@ -195,6 +207,12 @@ Designer PreserveNewest + + PreserveNewest + + + PreserveNewest + diff --git a/src/Plugins/SmartStore.DevTools/Starter.cs b/src/Plugins/SmartStore.DevTools/Starter.cs new file mode 100644 index 0000000000..b8b9d3c97c --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Starter.cs @@ -0,0 +1,31 @@ +using Microsoft.Web.Infrastructure.DynamicModuleHelper; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Plugins; +using SmartStore.Web.Framework; + +namespace SmartStore.DevTools +{ + + public class ProfilerPreApplicationStart : IPreApplicationStart + { + public void Start() + { + DynamicModuleUtility.RegisterModule(typeof(ProfilerHttpModule)); + SmartUrlRoutingModule.RegisterRoutablePath("/mini-profiler-resources/(.*)"); + } + } + + public class ProfilerStartupTask : IStartupTask + { + public void Execute() + { + StackExchange.Profiling.EntityFramework6.MiniProfilerEF6.Initialize(); + } + + public int Order + { + get { return int.MinValue; } + } + } + +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Views/DevTools/BackendExtension.cshtml b/src/Plugins/SmartStore.DevTools/Views/DevTools/BackendExtension.cshtml new file mode 100644 index 0000000000..d2e818b648 --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Views/DevTools/BackendExtension.cshtml @@ -0,0 +1,18 @@ +@using SmartStore.DevTools.Models; +@model BackendExtensionModel +@{ + Layout = "~/Administration/Views/Shared/_AdminLayout.cshtml"; + + ViewBag.Title = "My SmartStore.NET backend extension page"; +} + +
+
+ + My SmartStore.NET backend extension page +
+
+ +
+ @Model.Welcome +
diff --git a/src/Plugins/SmartStore.DevTools/Views/DevTools/Configure.cshtml b/src/Plugins/SmartStore.DevTools/Views/DevTools/Configure.cshtml index ebeb4a5ae8..7f1bdc6101 100644 --- a/src/Plugins/SmartStore.DevTools/Views/DevTools/Configure.cshtml +++ b/src/Plugins/SmartStore.DevTools/Views/DevTools/Configure.cshtml @@ -14,6 +14,15 @@ @Html.SettingEditorFor(model => model.EnableMiniProfilerInPublicStore) @Html.ValidationMessageFor(model => model.EnableMiniProfilerInPublicStore)
+ @Html.SmartLabelFor(model => model.DisplayWidgetZones) + + @Html.SettingEditorFor(model => model.DisplayWidgetZones) + @Html.ValidationMessageFor(model => model.DisplayWidgetZones) +
diff --git a/src/Plugins/SmartStore.DevTools/Views/DevTools/WidgetZone.cshtml b/src/Plugins/SmartStore.DevTools/Views/DevTools/WidgetZone.cshtml new file mode 100644 index 0000000000..222fe683e0 --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Views/DevTools/WidgetZone.cshtml @@ -0,0 +1,3 @@ + + Widget Zone + diff --git a/src/Plugins/SmartStore.DevTools/Views/Web.config b/src/Plugins/SmartStore.DevTools/Views/Web.config index d6ec073c8e..1812533ab9 100644 --- a/src/Plugins/SmartStore.DevTools/Views/Web.config +++ b/src/Plugins/SmartStore.DevTools/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.DevTools/Web.config b/src/Plugins/SmartStore.DevTools/Web.config index 928d273b2d..b076d239ac 100644 --- a/src/Plugins/SmartStore.DevTools/Web.config +++ b/src/Plugins/SmartStore.DevTools/Web.config @@ -1,131 +1,135 @@ - + -
+
- - + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + - + - + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.DevTools/packages.config b/src/Plugins/SmartStore.DevTools/packages.config index 7180887bd1..325d93b6d4 100644 --- a/src/Plugins/SmartStore.DevTools/packages.config +++ b/src/Plugins/SmartStore.DevTools/packages.config @@ -1,11 +1,12 @@  - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.DiscountRules/Controllers/DiscountRulesController.cs b/src/Plugins/SmartStore.DiscountRules/Controllers/DiscountRulesController.cs index c004670e23..7e6605a2f3 100644 --- a/src/Plugins/SmartStore.DiscountRules/Controllers/DiscountRulesController.cs +++ b/src/Plugins/SmartStore.DiscountRules/Controllers/DiscountRulesController.cs @@ -9,6 +9,7 @@ using SmartStore.Services.Discounts; using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; namespace SmartStore.DiscountRules.Controllers { @@ -31,13 +32,8 @@ public DiscountRulesController( this._customerService = customerService; this._countryService = countryService; this._storeService = storeService; - - T = NullLocalizer.Instance; } - public Localizer T { get; set; } - - #region Global [NonAction] diff --git a/src/Plugins/SmartStore.DiscountRules/Description.txt b/src/Plugins/SmartStore.DiscountRules/Description.txt index 4da6c4730b..f4bb29d728 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: 1.0 -MinAppVersion: 2.1.0 +Version: 2.6.0 +MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.DiscountRules.dll ResourceRootKey: Plugins.SmartStore.DiscountRules diff --git a/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs b/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs index b67d2a7efd..69d7f2614b 100644 --- a/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs +++ b/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs @@ -1,15 +1,16 @@ using System; +using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -using SmartStore.Services.Discounts; +using SmartStore.DiscountRules.Settings; +using SmartStore.Services.Catalog; using SmartStore.Services.Customers; +using SmartStore.Services.Discounts; using SmartStore.Services.Orders; -using SmartStore.Services.Catalog; using SmartStore.Services.Tax; -using SmartStore.Core.Localization; -using SmartStore.DiscountRules.Settings; -using Newtonsoft.Json; namespace SmartStore.DiscountRules { @@ -21,15 +22,18 @@ public partial class HadSpentAmountRule : DiscountRequirementRuleBase private readonly IOrderService _orderService; private readonly IPriceCalculationService _priceCalculationService; private readonly ITaxService _taxService; + private readonly ILogger _logger; public HadSpentAmountRule( IOrderService orderService, IPriceCalculationService priceCalculationService, - ITaxService taxService) + ITaxService taxService, + ILogger logger) { - this._orderService = orderService; - this._priceCalculationService = priceCalculationService; - this._taxService = taxService; + _orderService = orderService; + _priceCalculationService = priceCalculationService; + _taxService = taxService; + _logger = logger; } public override bool CheckRequirement(CheckDiscountRequirementRequest request) @@ -88,14 +92,34 @@ private bool CheckTotalHistoryRequirement(CheckDiscountRequirementRequest reques private bool CheckCurrentSubTotalRequirement(CheckDiscountRequirementRequest request) { - var cartItems = request.Customer.GetCartItems(ShoppingCartType.ShoppingCart, request.Store.Id); + var spentAmount = decimal.Zero; - decimal spentAmount = decimal.Zero; - decimal taxRate = decimal.Zero; - foreach (var sci in cartItems) + try { - // includeDiscounts == true produces a stack overflow! - spentAmount += sci.Item.Quantity * _taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, false), out taxRate); + var taxRate = decimal.Zero; + var cartItems = request.Customer.GetCartItems(ShoppingCartType.ShoppingCart, request.Store.Id); + + foreach (var cartItem in cartItems) + { + var product = cartItem.Item.Product; + Dictionary mergedValuesClone = null; + + // we must reapply merged values because CheckCurrentSubTotalRequirement uses price calculation and is called by it itself. + // this can cause wrong discount calculation if the cart contains a product several times. + if (product.MergedDataValues != null) + mergedValuesClone = new Dictionary(product.MergedDataValues); + + // includeDiscounts == true produces a stack overflow! + spentAmount += cartItem.Item.Quantity * _taxService.GetProductPrice(product, _priceCalculationService.GetUnitPrice(cartItem, false), out taxRate); + + if (mergedValuesClone != null) + product.MergedDataValues = new Dictionary(mergedValuesClone); + } + } + catch (Exception exception) + { + _logger.Error(exception); + return false; } return spentAmount >= request.DiscountRequirement.SpentAmount; diff --git a/src/Plugins/SmartStore.DiscountRules/RouteProvider.cs b/src/Plugins/SmartStore.DiscountRules/RouteProvider.cs index 996e585ae6..4f6c8fb679 100644 --- a/src/Plugins/SmartStore.DiscountRules/RouteProvider.cs +++ b/src/Plugins/SmartStore.DiscountRules/RouteProvider.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.DiscountRules { diff --git a/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj b/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj index ee71df5fdd..dc162de8da 100644 --- a/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj +++ b/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj @@ -1,5 +1,5 @@  - + @@ -20,7 +20,7 @@ Properties SmartStore.DiscountRules SmartStore.DiscountRules - v4.5 + v4.5.1 512 @@ -42,6 +42,7 @@ + true @@ -84,41 +85,45 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True - + + + + + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - + @@ -189,6 +194,7 @@ PreserveNewest + Designer PreserveNewest diff --git a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HadSpentAmount.cshtml b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HadSpentAmount.cshtml index aa501d7602..1dac222388 100644 --- a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HadSpentAmount.cshtml +++ b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HadSpentAmount.cshtml @@ -7,18 +7,6 @@ private string GetTrackingScript() { var googleAnalyticsSettings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); @@ -149,42 +137,6 @@ private string GetTrackingScript() return analyticsTrackingScript; } - // private string GetEcommerceScript(Order order) { var googleAnalyticsSettings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); @@ -206,6 +158,7 @@ private string GetEcommerceScript(Order order) 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 sb = new StringBuilder(); foreach (var item in order.OrderItems) diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt index a4a65ecd4c..59bf21b018 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt +++ b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt @@ -1,9 +1,9 @@ FriendlyName: Google Analytics SystemName: SmartStore.GoogleAnalytics Group: Analytics -Version: 1.7 -MinAppVersion: 2.1.0 +Version: 2.6.0 +MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.GoogleAnalytics.dll ResourceRootKey: Plugins.Widgets.GoogleAnalytics -Url: http://community.smartstore.com/index.php?/files/file/25-google-analytics/ \ No newline at end of file +Url: http://community.smartstore.com/marketplace/file/25-google-analytics/ \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs b/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs index fe48664ee6..185c203527 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs +++ b/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs @@ -31,9 +31,16 @@ public GoogleAnalyticPlugin(ISettingService settingService, /// Widget zones public IList GetWidgetZones() { - return !string.IsNullOrWhiteSpace(_googleAnalyticsSettings.WidgetZone) - ? new List() { _googleAnalyticsSettings.WidgetZone } - : new List() { "head_html_tag" }; + 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; } /// @@ -46,7 +53,7 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa { actionName = "Configure"; controllerName = "WidgetsGoogleAnalytics"; - routeValues = new RouteValueDictionary() { /*{ "Namespaces", "SmartStore.GoogleAnalytics.Controllers" },*/ { "area", "SmartStore.GoogleAnalytics" } }; + routeValues = new RouteValueDictionary() { { "area", "SmartStore.GoogleAnalytics" } }; } /// @@ -62,7 +69,6 @@ public void GetDisplayWidgetRoute(string widgetZone, object model, int storeId, controllerName = "WidgetsGoogleAnalytics"; routeValues = new RouteValueDictionary() { - //{"Namespaces", "SmartStore.GoogleAnalytics.Controllers"}, {"area", "SmartStore.GoogleAnalytics"}, {"widgetZone", widgetZone} }; @@ -76,23 +82,44 @@ public override void Install() var settings = new GoogleAnalyticsSettings() { GoogleId = "UA-0000000-0", - //TrackingScript = " ", TrackingScript = @" -", - EcommerceScript = @"_gaq.push(['_addTrans', '{ORDERID}', '{SITE}', '{TOTAL}', '{TAX}', '{SHIP}', '{CITY}', '{STATEPROVINCE}', '{COUNTRY}']); -{DETAILS} -_gaq.push(['_trackTrans']); ", - EcommerceDetailScript = @"_gaq.push(['_addItem', '{ORDERID}', '{PRODUCTSKU}', '{PRODUCTNAME}', '{CATEGORYNAME}', '{UNITPRICE}', '{QUANTITY}' ]); ", + ", + 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); diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml index dd63f56b5f..d7f6055f92 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml @@ -1,17 +1,18 @@  - + Google Analytics Google Analytics ist ein kostenloser Statistikdienst von Google. Der Dienst erfasst Statistiken über Besucher und Ecommerce-Konversion auf Ihrer Webseite

+

Google Analytics ist ein kostenloser Statistikdienst von Google. Der Dienst erfasst Statistiken über Besucher und Ecommerce-Konversion auf Ihrer Webseite.

Führen Sie die folgenden Schritte aus um Google Analytics auf Ihrer Webseite einzubinden:

    -
  • erstellen Sie hier einen "Google Analytics"-Account und folgen Sie dem Wizard um Ihre Webseite zuzufügen
  • -
  • Kopieren Sie die "Google Analytics"-ID in das entspechende Feld in folgendem Formular
  • -
  • Kopieren Sie den Tracking-Code in die "Tracking-Code"-Box in folgendem Formular
  • -
  • KLicken Sie den "Speichern"-Button und Google Analytics wird in Ihre Webseite integriert
  • +
  • Erstellen Sie hier einen Google-Analytics-Account und folgen Sie dem Wizard um Ihre Webseite zuzufügen.
  • +
  • Kopieren Sie die Google-Analytics-ID in das entspechende Feld in folgendem Formular.
  • +
  • Kopieren Sie den Tracking-Code in die Tracking-Code-Box in folgendem Formular.
  • +
  • Klicken Sie den Speichern-Button.
  • +
  • Aktivieren Sie das Google Analytics Widget unter CMS > Widgets und Google Analytics wird in Ihre Webseite integriert.
]]>
diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml index 2e97f01971..d54877d501 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml @@ -1,18 +1,18 @@  - + Google Analytics Google Analytics is a free website stats tool from Google. It keeps track of statistics - about the visitors and ecommerce conversion on your website.

+

Google Analytics is a free website statistics tool from Google. It keeps track of statistics about the visitors and ecommerce conversion on your website.

Follow the next steps to enable Google Analytics integration:

    -
  • Create a Google Analytics account and follow the wizard to add your website
  • -
  • Copy the Google Analytics ID into the 'ID' box below
  • -
  • Copy the tracking code from Google Analytics into the 'Tracking Code' box below
  • -
  • Click the 'Save' button below and Google Analytics will be integrated into your store
  • +
  • Create a Google Analytics account and follow the wizard to add your website.
  • +
  • Copy the Google Analytics ID into the ID field below.
  • +
  • Copy the tracking code from Google Analytics into the Tracking Code field below.
  • +
  • Click the Save button below.
  • +
  • Activate the Google Analytics widget under CMS > Widgets to integrate Google Analytics into your store.
]]>
diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Models/ConfigurationModel.cs b/src/Plugins/SmartStore.GoogleAnalytics/Models/ConfigurationModel.cs index a644421c92..61a1f37fda 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Models/ConfigurationModel.cs +++ b/src/Plugins/SmartStore.GoogleAnalytics/Models/ConfigurationModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.GoogleAnalytics.Models { diff --git a/src/Plugins/SmartStore.GoogleAnalytics/RouteProvider.cs b/src/Plugins/SmartStore.GoogleAnalytics/RouteProvider.cs index 73a1a9a68c..02140b8d4e 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/RouteProvider.cs +++ b/src/Plugins/SmartStore.GoogleAnalytics/RouteProvider.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.GoogleAnalytics { diff --git a/src/Plugins/SmartStore.GoogleAnalytics/SmartStore.GoogleAnalytics.csproj b/src/Plugins/SmartStore.GoogleAnalytics/SmartStore.GoogleAnalytics.csproj index 14021491af..0b26e4d1d5 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/SmartStore.GoogleAnalytics.csproj +++ b/src/Plugins/SmartStore.GoogleAnalytics/SmartStore.GoogleAnalytics.csproj @@ -1,5 +1,5 @@  - + @@ -20,7 +20,7 @@ Properties SmartStore.GoogleAnalytics SmartStore.GoogleAnalytics - v4.5 + v4.5.1 512 @@ -85,37 +85,40 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - + + + + + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - + diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Views/Web.config b/src/Plugins/SmartStore.GoogleAnalytics/Views/Web.config index 31dc8df754..e9d36a3c51 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Views/Web.config +++ b/src/Plugins/SmartStore.GoogleAnalytics/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.GoogleAnalytics/packages.config b/src/Plugins/SmartStore.GoogleAnalytics/packages.config index e5a5a1eb40..4bc06fc1a7 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 421a988c90..c0f82db177 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/web.config +++ b/src/Plugins/SmartStore.GoogleAnalytics/web.config @@ -1,116 +1,116 @@ - + - - + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs index 97b051cfbc..2dbc226f57 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Routing; -using System.Web.Mvc; +using SmartStore.Collections; using SmartStore.Web.Framework.UI; -using SmartStore.Collections; namespace SmartStore.GoogleMerchantCenter { @@ -13,17 +7,13 @@ public class AdminMenu : AdminMenuProvider { protected override void BuildMenuCore(TreeNode pluginsNode) { - var root = pluginsNode.SelectNode(x => x.Value.Id == "promotion-feeds"); - if (root == null) - return; - var menuItem = new MenuItem().ToBuilder() .Text("Google Merchant Center") .ResKey("Plugins.FriendlyName.SmartStore.GoogleMerchantCenter") - .Action("ConfigurePlugin", "Plugin", new { systemName = "SmartStore.GoogleMerchantCenter", area = "Admin" }) + .Action("ConfigurePlugin", "Plugin", new { systemName = GoogleMerchantCenterFeedPlugin.SystemName, area = "Admin" }) .ToItem(); - root.Append(menuItem); + pluginsNode.Prepend(menuItem); } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Content/images/mc_logo.png b/src/Plugins/SmartStore.GoogleMerchantCenter/Content/branding.png similarity index 100% rename from src/Plugins/SmartStore.GoogleMerchantCenter/Content/images/mc_logo.png rename to src/Plugins/SmartStore.GoogleMerchantCenter/Content/branding.png diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.feed.froogle.css b/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.feed.froogle.css deleted file mode 100644 index 3c4b0af7b7..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.feed.froogle.css +++ /dev/null @@ -1,10 +0,0 @@ -.config-logo { - width: 274px; - height: 31px; -} -.edit-taxonomy { - min-width: 460px; -} -.google-product-search { - margin-bottom: 8px !important; -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css b/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css new file mode 100644 index 0000000000..4a8bfa9c72 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css @@ -0,0 +1,6 @@ +.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/FeedFroogleController.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedFroogleController.cs deleted file mode 100644 index 222d2af9df..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedFroogleController.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System.Collections.Generic; -using System.Globalization; -using System.Web.Mvc; -using SmartStore.Core.Localization; -using SmartStore.GoogleMerchantCenter.Models; -using SmartStore.GoogleMerchantCenter.Services; -using SmartStore.Services.Configuration; -using SmartStore.Services.Security; -using SmartStore.Web.Framework.Controllers; -using Telerik.Web.Mvc; - -namespace SmartStore.GoogleMerchantCenter.Controllers -{ - [AdminAuthorize] - public class FeedFroogleController : PluginControllerBase - { - private readonly FroogleSettings _settings; - private readonly IGoogleFeedService _googleService; - private readonly ISettingService _settingService; - private readonly IPermissionService _permissionService; - - public FeedFroogleController( - FroogleSettings settings, - IGoogleFeedService googleService, - ISettingService settingService, - IPermissionService permissionService) - { - _settings = settings; - _googleService = googleService; - _settingService = settingService; - _permissionService = permissionService; - - T = NullLocalizer.Instance; - } - - public Localizer T { get; set; } - - private ActionResult RedirectToConfig() - { - return RedirectToAction("ConfigurePlugin", "Plugin", new { systemName = _googleService.Helper.SystemName, area = "Admin" }); - } - - public ActionResult ProductEditTab(int productId) - { - var model = new GoogleProductModel { ProductId = productId }; - var entity = _googleService.GetGoogleProductRecord(productId); - - if (entity != null) - { - model.Taxonomy = entity.Taxonomy; - model.Gender = entity.Gender; - model.AgeGroup = entity.AgeGroup; - model.Color = entity.Color; - model.Size = entity.Size; - model.Material = entity.Material; - model.Pattern = entity.Pattern; - } - - ViewBag.DefaultCategory = _settings.DefaultGoogleCategory; - ViewBag.DefaultGender = T("Common.Auto"); - ViewBag.DefaultAgeGroup = T("Common.Auto"); - ViewBag.DefaultColor = _settings.Color; - ViewBag.DefaultSize = _settings.Size; - ViewBag.DefaultMaterial = _settings.Material; - ViewBag.DefaultPattern = _settings.Pattern; - - var ci = CultureInfo.InvariantCulture; - - if (_settings.Gender.HasValue()) - { - ViewBag.DefaultGender = T("Plugins.Feed.Froogle.Gender" + ci.TextInfo.ToTitleCase(_settings.Gender)); - } - - if (_settings.AgeGroup.HasValue()) - { - ViewBag.DefaultAgeGroup = T("Plugins.Feed.Froogle.AgeGroup" + ci.TextInfo.ToTitleCase(_settings.AgeGroup)); - } - - - ViewBag.AvailableGenders = new List - { - new SelectListItem { Value = "male", Text = T("Plugins.Feed.Froogle.GenderMale") }, - new SelectListItem { Value = "female", Text = T("Plugins.Feed.Froogle.GenderFemale") }, - new SelectListItem { Value = "unisex", Text = T("Plugins.Feed.Froogle.GenderUnisex") } - }; - - ViewBag.AvailableAgeGroups = new List - { - new SelectListItem { Value = "adult", Text = T("Plugins.Feed.Froogle.AgeGroupAdult") }, - new SelectListItem { Value = "kids", Text = T("Plugins.Feed.Froogle.AgeGroupKids") }, - }; - - var result = PartialView(model); - result.ViewData.TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "CustomProperties[GMC]" }; - return result; - } - - public ActionResult GoogleCategories() - { - var categories = _googleService.GetTaxonomyList(); - return Json(categories, JsonRequestBehavior.AllowGet); - } - - public ActionResult Configure() - { - var model = new FeedFroogleModel(); - model.Copy(_googleService.Settings, true); - - if (TempData["GenerateFeedRunning"] != null) - model.IsRunning = (bool)TempData["GenerateFeedRunning"]; - - _googleService.SetupModel(model); - - return View(model); - } - - [HttpPost] - [FormValueRequired("save")] - public ActionResult Configure(FeedFroogleModel model) - { - if (!ModelState.IsValid) - return Configure(); - - model.Copy(_googleService.Settings, false); - _settingService.SaveSetting(_googleService.Settings); - - _googleService.Helper.UpdateScheduleTask(model.TaskEnabled, model.GenerateStaticFileEachMinutes * 60); - - NotifySuccess(_googleService.Helper.GetResource("ConfigSaveNote"), true); - - _googleService.SetupModel(model); - - return View(model); - } - - public ActionResult GenerateFeed() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks)) - return AccessDeniedView(); - - if (_googleService.Helper.RunScheduleTask()) - TempData["GenerateFeedRunning"] = true; - - return RedirectToConfig(); - } - - [HttpPost] - public ActionResult GenerateFeedProgress() - { - string message = _googleService.Helper.GetProgressInfo(true); - return Json(new { message = message }, JsonRequestBehavior.DenyGet); - } - - public ActionResult DeleteFiles() - { - _googleService.Helper.DeleteFeedFiles(); - - return RedirectToConfig(); - } - - [HttpPost] - public ActionResult GoogleProductEdit(int pk, string name, string value) - { - _googleService.UpdateInsert(pk, name, value); - - return this.Content(""); - } - - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult GoogleProductList(GridCommand command, string searchProductName, string touched) - { - return new JsonResult - { - Data = _googleService.GetGridModel(command, searchProductName, touched) - }; - } - } -} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs new file mode 100644 index 0000000000..6c37461c87 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs @@ -0,0 +1,161 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Web.Mvc; +using SmartStore.Core; +using SmartStore.Core.Domain.Common; +using SmartStore.GoogleMerchantCenter.Models; +using SmartStore.GoogleMerchantCenter.Providers; +using SmartStore.GoogleMerchantCenter.Services; +using SmartStore.Services.DataExchange.Export; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; +using Telerik.Web.Mvc; + +namespace SmartStore.GoogleMerchantCenter.Controllers +{ + [AdminAuthorize] + public class FeedGoogleMerchantCenterController : PluginControllerBase + { + private readonly IGoogleFeedService _googleFeedService; + private readonly AdminAreaSettings _adminAreaSettings; + private readonly IExportProfileService _exportService; + + public FeedGoogleMerchantCenterController( + IGoogleFeedService googleFeedService, + AdminAreaSettings adminAreaSettings, + IExportProfileService exportService) + { + _googleFeedService = googleFeedService; + _adminAreaSettings = adminAreaSettings; + _exportService = exportService; + } + + public ActionResult ProductEditTab(int productId) + { + var culture = CultureInfo.InvariantCulture; + var model = new GoogleProductModel { ProductId = productId }; + var entity = _googleFeedService.GetGoogleProductRecord(productId); + string notSpecified = T("Common.Unspecified"); + + if (entity != null) + { + model.Taxonomy = entity.Taxonomy; + model.Gender = entity.Gender; + model.AgeGroup = entity.AgeGroup; + model.IsAdult = entity.IsAdult; + model.Color = entity.Color; + model.Size = entity.Size; + model.Material = entity.Material; + model.Pattern = entity.Pattern; + model.Export2 = entity.Export; + model.Multipack2 = entity.Multipack; + model.IsBundle = entity.IsBundle; + model.EnergyEfficiencyClass = entity.EnergyEfficiencyClass; + model.CustomLabel0 = entity.CustomLabel0; + model.CustomLabel1 = entity.CustomLabel1; + model.CustomLabel2 = entity.CustomLabel2; + model.CustomLabel3 = entity.CustomLabel3; + model.CustomLabel4 = entity.CustomLabel4; + } + else + { + model.Export2 = true; + } + + ViewBag.DefaultCategory = ""; + ViewBag.DefaultColor = ""; + ViewBag.DefaultSize = ""; + ViewBag.DefaultMaterial = ""; + ViewBag.DefaultPattern = ""; + ViewBag.DefaultGender = notSpecified; + ViewBag.DefaultAgeGroup = notSpecified; + ViewBag.DefaultIsAdult = ""; + ViewBag.DefaultMultipack2 = ""; + ViewBag.DefaultIsBundle = ""; + ViewBag.DefaultEnergyEfficiencyClass = notSpecified; + ViewBag.DefaultCustomLabel = ""; + + // we do not have export profile context here, so we simply use the first profile + var profile = _exportService.GetExportProfilesBySystemName(GmcXmlExportProvider.SystemName).FirstOrDefault(); + if (profile != null) + { + var config = XmlHelper.Deserialize(profile.ProviderConfigData, typeof(ProfileConfigurationModel)) as ProfileConfigurationModel; + if (config != null) + { + ViewBag.DefaultCategory = config.DefaultGoogleCategory; + ViewBag.DefaultColor = config.Color; + ViewBag.DefaultSize = config.Size; + ViewBag.DefaultMaterial = config.Material; + ViewBag.DefaultPattern = config.Pattern; + + if (config.Gender.HasValue() && config.Gender != GmcXmlExportProvider.Unspecified) + { + ViewBag.DefaultGender = T("Plugins.Feed.Froogle.Gender" + culture.TextInfo.ToTitleCase(config.Gender)); + } + + if (config.AgeGroup.HasValue() && config.AgeGroup != GmcXmlExportProvider.Unspecified) + { + ViewBag.DefaultAgeGroup = T("Plugins.Feed.Froogle.AgeGroup" + culture.TextInfo.ToTitleCase(config.AgeGroup)); + } + } + } + + ViewBag.AvailableGenders = new List + { + new SelectListItem { Value = "male", Text = T("Plugins.Feed.Froogle.GenderMale") }, + new SelectListItem { Value = "female", Text = T("Plugins.Feed.Froogle.GenderFemale") }, + new SelectListItem { Value = "unisex", Text = T("Plugins.Feed.Froogle.GenderUnisex") } + }; + + ViewBag.AvailableAgeGroups = new List + { + new SelectListItem { Value = "adult", Text = T("Plugins.Feed.Froogle.AgeGroupAdult") }, + new SelectListItem { Value = "kids", Text = T("Plugins.Feed.Froogle.AgeGroupKids") }, + }; + + ViewBag.AvailableEnergyEfficiencyClasses = T("Plugins.Feed.Froogle.EnergyEfficiencyClasses").Text + .SplitSafe(",") + .Select(x => new SelectListItem { Value = x, Text = x }) + .ToList(); + + var result = PartialView(model); + result.ViewData.TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "CustomProperties[GMC]" }; + 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.EnergyEfficiencyClasses = T("Plugins.Feed.Froogle.EnergyEfficiencyClasses").Text.SplitSafe(","); + + return View(model); + } + + [HttpPost] + public ActionResult GoogleProductEdit(int pk, string name, string value) + { + _googleFeedService.Upsert(pk, name, value); + + return this.Content(""); + } + + [HttpPost, GridAction(EnableCustomBinding = true)] + public ActionResult GoogleProductList(GridCommand command, string searchProductName, string touched) + { + return new JsonResult + { + Data = _googleFeedService.GetGridModel(command, searchProductName, touched) + }; + } + } +} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductObjectContext.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductObjectContext.cs index 2ff4e54b47..2d822a76b9 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductObjectContext.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductObjectContext.cs @@ -10,9 +10,7 @@ namespace SmartStore.GoogleMerchantCenter.Data { - /// - /// Object context - /// + public class GoogleProductObjectContext : ObjectContextBase { public const string ALIASKEY = "sm_object_context_google_product"; @@ -43,9 +41,6 @@ public GoogleProductObjectContext(string nameOrConnectionString) protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new GoogleProductRecordMap()); - - //disable EdmMetadata generation - //modelBuilder.Conventions.Remove(); base.OnModelCreating(modelBuilder); } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductRecordMap.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductRecordMap.cs index 5b4e0817ab..98aaec1aa4 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductRecordMap.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductRecordMap.cs @@ -9,6 +9,14 @@ public GoogleProductRecordMap() { this.ToTable("GoogleProduct"); this.HasKey(x => x.Id); - } + + this.Property(x => x.EnergyEfficiencyClass).HasMaxLength(50); + + this.Property(x => x.CustomLabel0).HasMaxLength(100); + this.Property(x => x.CustomLabel1).HasMaxLength(100); + this.Property(x => x.CustomLabel2).HasMaxLength(100); + this.Property(x => x.CustomLabel3).HasMaxLength(100); + this.Property(x => x.CustomLabel4).HasMaxLength(100); + } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201504211854125_IsActive.Designer.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201504211854125_IsActive.Designer.cs new file mode 100644 index 0000000000..981e039008 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201504211854125_IsActive.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.0-30225")] + public sealed partial class IsActive : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(IsActive)); + + string IMigrationMetadata.Id + { + get { return "201504211854125_IsActive"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201504211854125_IsActive.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201504211854125_IsActive.cs new file mode 100644 index 0000000000..6d62461bb1 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201504211854125_IsActive.cs @@ -0,0 +1,18 @@ +namespace SmartStore.GoogleMerchantCenter.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class IsActive : DbMigration + { + public override void Up() + { + AddColumn("dbo.GoogleProduct", "Export", c => c.Boolean(nullable: false, defaultValue: true)); + } + + public override void Down() + { + DropColumn("dbo.GoogleProduct", "Export"); + } + } +} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201504211854125_IsActive.resx b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201504211854125_IsActive.resx new file mode 100644 index 0000000000..ad02e61da9 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201504211854125_IsActive.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 + + + H4sIAAAAAAAEAM1ZzW7jNhC+F+g7CDq1QNZynEsbyLvIOj8Iuo6DyNk7LY0dthSpklRg76v10EfqK+xQ/5JlW4rjRW/2iPPNHzXfjP3fP/+6n9Yhs15BKir42D4fDG0LuC8CyldjO9bLD7/Znz7+/JN7E4Rr62t+7sKcQ02uxvaL1tGl4yj/BUKiBiH1pVBiqQe+CB0SCGc0HP7unJ87gBA2YlmW+xRzTUNIvuDXieA+RDombCoCYCqT4xMvQbUeSAgqIj6MbS8kUntaSBjcCbFiMAXpvxCuJ8A1yME10cS2rhgl6JsHbGlbhHOhiUbPL58VeFoKvvIiFBA230SA55aEKcgiuiyPdw1uODLBOaViDuXHSouwJ+D5RZYtp6n+ppzbRTYxnzeYd70xUSc5HdtpDh+lCGJfP4EvZGBbTbuXEyaNTofsoxrlgxbUM+uA7llxvUaD0WA4GJ5Zk5jpWMKYQ6wlYWfWY7xg1P8DNnPxF/AxjxmrhocB4rOaAEXoRgRSb55gmQV9jzE6dT2nqVioVXTSHNxzfTGyrQc0ThYMittTyVcS5R1wkERD8Eg0xscNBiT537LesJUl7rDJ/TBzshZchJscBW8+vta2NSXrL8BX+mVs40fbuqVrCHJJhvzMKXYBVNIyPmgIIw1AntzM1QrupIijkxuaCCZOH45Hv8HJjUzxAkpsdCc3lF3yk9u51xAmt6B8O05nS81FjD22sPRZCAaE934TJxJMI5jxZ+3nWEhUMEcS7A32HAXvB3azjoTU/eJznZJGtskFyVwjB4BsY5jZ4k/wtTkCa91CNMjPGdeorER171MTHujd9KVsq3QvHQHa+Kg9pML5cgRx0hkkn1WcHcOKOyVRhNewMrxkEstLJ5fJB68/gYcphuOrFh4vvC0sIe+QFTSemoYWwC2VSpvpaEFMQSdBuHVsT6l2lCE3vKcaTToui5Mrm89ZS+ww3LUWs2GkTP4t5iNE5SQ1UPjdPvhsoSQTKGFEtgwDSBJxyHcNFPu0K/ReBamIu2OVHF+FKqXdkXISr+Lksu4oJUdXcUppd6SMhKswmag7RkqxVYhU0h2h5M8qSintUfWcIGs1z4XdcWoEWLuE1Qc98EqSq6GV4h41q/FcrXS1J90R62RXRaw/6Y6YM14VK5dto7hOo5E025mz1c8ai0WzV+5jneaRwnrBPg2WcbOOf3hv3qKA9IhtYXpeaWDav7dReIXSJuv9zSaMYrzlgSnhdAlKp7uXjYvqqLFo/3+WXkepgPXffH/4LklNgg9uiz1nuq31MbFy5PLIX4mhYvlLSNa/VtH6L4hHQTWXwKPAaoveUUjVZe4ooObCdhRYYyk7Cqtl8ToOr7lcLWj/W9q2WBla0O+1WL0ZrL5YHY6t31K1PdD33pO6rkkpT2AmFgKjSf2vHXzzJrXNXa5T/WXYvQZFVyWE+Z2Y406CpFCC5mfu+VLkJcC4qx7lR5pvGmiC5SVXUtMl8TU+9kGp5KeEr4TFpojhAoJ7Pot1FOsrpSBcsNpQ7Tr77SfrYt1ndxaZb+o9QkA3qbmhM/45piwo/L5tuV87IMxNyrgHvfK04aDVpkB6ELwjUJa+a4hMs+d6DmHEEEzNuEde4S2+PSv4Aivib/IRZDfI4ULU0+5eU7KSJFQZRqlv/u1wzN8dH78D4O1TvSAZAAA= + + + dbo + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.Designer.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.Designer.cs new file mode 100644 index 0000000000..652bba012c --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.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 IsBundle : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(IsBundle)); + + string IMigrationMetadata.Id + { + get { return "201601061649324_IsBundle"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.cs new file mode 100644 index 0000000000..bc8af7c35e --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.cs @@ -0,0 +1,34 @@ +namespace SmartStore.GoogleMerchantCenter.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class IsBundle : DbMigration + { + public override void Up() + { + AddColumn("dbo.GoogleProduct", "Multipack", c => c.Int(nullable: false)); + AddColumn("dbo.GoogleProduct", "IsBundle", c => c.Boolean()); + AddColumn("dbo.GoogleProduct", "IsAdult", c => c.Boolean()); + AddColumn("dbo.GoogleProduct", "EnergyEfficiencyClass", c => c.String(maxLength: 50)); + AddColumn("dbo.GoogleProduct", "CustomLabel0", c => c.String(maxLength: 100)); + AddColumn("dbo.GoogleProduct", "CustomLabel1", c => c.String(maxLength: 100)); + AddColumn("dbo.GoogleProduct", "CustomLabel2", c => c.String(maxLength: 100)); + AddColumn("dbo.GoogleProduct", "CustomLabel3", c => c.String(maxLength: 100)); + AddColumn("dbo.GoogleProduct", "CustomLabel4", c => c.String(maxLength: 100)); + } + + public override void Down() + { + DropColumn("dbo.GoogleProduct", "CustomLabel4"); + DropColumn("dbo.GoogleProduct", "CustomLabel3"); + DropColumn("dbo.GoogleProduct", "CustomLabel2"); + DropColumn("dbo.GoogleProduct", "CustomLabel1"); + DropColumn("dbo.GoogleProduct", "CustomLabel0"); + DropColumn("dbo.GoogleProduct", "EnergyEfficiencyClass"); + DropColumn("dbo.GoogleProduct", "IsAdult"); + DropColumn("dbo.GoogleProduct", "IsBundle"); + DropColumn("dbo.GoogleProduct", "Multipack"); + } + } +} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.resx b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.resx new file mode 100644 index 0000000000..ebf95a7c54 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.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 + + + H4sIAAAAAAAEAM2a227jNhCG7wv0HQRdtUDW8iEF2sDeReIki6BxHETJ3tPS2FGXIlWSCuy+Wi/6SH2Fjs6iJNuSHQe9s0fkNwdS1K+x//37n/GXtU+NNxDS42xiDnp90wDmcNdjq4kZquWnX80vn3/8YXzj+mvjWzZuFI3DmUxOzFelggvLks4r+ET2fM8RXPKl6jnct4jLrWG//5s1GFiACBNZhjF+CpnyfIi/4NcpZw4EKiR0xl2gMrXjFTumGg/EBxkQByam7ROhbMUF9L5yvqIwA+G8EqamwBSI3jVRxDQuqUcwNhvo0jQIY1wRhZFfvEiwleBsZQdoIPR5EwCOWxIqIc3oohjeNrn+MErOKiZmKCeUivsdgYNRWi2rOv2gmpt5NbGeN1h3tYmyjms6MZMaPgruho56AocL1zSqfi+mVERzWlQfp3ms10A9M/bMPcu317A37PV7/TNjGlIVCpgwCJUg9Mx4DBfUc36HzTP/DmzCQkrL6WGCeE0zoAnDCECozRMs06TvMEdLn2dVJ+bTSnOSGtwxNRqaxgM6JwsK+e4p1SvO8iswEESB+0gU5sciBsT1r3mv+EoLt9/lbswzWXPG/U1GwZ2Pt7VpzMj6HthKvU5M/Ggat94a3MySkl+Yh6cATlIi3OsIM3VBnNzN5Qq+Ch4GJ3c05ZSfPh3b+wtO7mSGG1DgQXdyR+kmP7mfOwV+vAuKu+N0vuQzD/GMzT1dcU6BsM534lRAdBDM2YtyMhY+qOAZH4KdYS+B+36wm3XAhTo2vxke1B4+n78fd2DdyauQuRRq4eybduliAB1n3eDpvNrcLJee46Hk2UwpkXLHlvql/x4nS/xkvScLoP0dvgb9d3Y2+Ehnw490NvpIZ+fv7GxsFYKsLtNQFitUUyCatNp88Qc4KhoCa9Ug2VDppqpNpp71JBMXNqjtQhDvhiK8REw3KbvmlPLgCzFvJWo+U/3WFtk/npEgwOqWXgNSi2En7wDTT3Z3KewnDMuRDYo4jzb3hAqOrKByNZIGLtx6QqroPWNBovWdun5t2I6l2rIMmeMdq1EVtsXiZJOjz6m4aPGa1LiYFSdF8W+xHj5OjksDedzNrxA1SvwuRygRDbIa5Vbos23SfNfsklAuQ0rm9qxCLZdRhbU9KZPDZU5ma08p1G6ZU1jbk1I5W8akpvaMRKyWEYmlPaFQomVKYe2w6pnU1NY8M7bnaFJS24TlCx14hVzUaIW5w5ppilFbOu1Ke6IuG8tE/Up7YqYdy6zM1mFfFNpR2xiFucsKZAJSX4DM2oWUakodlBo71KhZZWolax7SYa9oolLbK9qVg4iDrcTBgcThVuLwQOJoK3F0IPF8K/G8iTi2Kg/J6qPaqj2rK+2nqg7YpaiqQ3LvubKqKKhxqmb2d1dr8iYZYhpYqjfPjaSNvZF4PCYCwv6TTiluWlUMmBHmLUGqpENnDvuDYaUd+/9pjVpSurR7f/TDO45eVOC9PcWOL9u1JmPs5cgWI3sjkcwUP/lk/XOZ1r2NeBSq2io8Cqa1A48ilVt+R4Gqbb2jYJXW3VGshvbccbxqC27hdd+lTe23SPKo92q/HQzT22+H5FZrvR1yG1cbb5VA2i2U1oQ7gLCzIZdtolpLruOt3NB/a0TH3ZyD2YMTsocnZI9OyD4/jt2tVVZv03TufrVtfiUKCc+ABccMk/C1gQf3x+qqbWyVfzkfX4P0VgUi+h2dgRPJoQKajbljS54tDuZdjigbUj1ZQBE82MilUN6SOAovOyBl3Pf8RmgY3bL+Atw7Ng9VEKpLKcFfUK1VMrZ2+4+bgHrM43kQfZPvkQKG6UVn85xdhR5187hv64fiNkS0k1LVhVHZKlJfq01OeuCsJSgt3zUEkcxh6hn8gCJMzplN3uCQ2F4k3MOKOJtMfG+H7F8Ivezja4+sBPFlyijmR/8GsaK/g3z+D2Zr4jJAIgAA + + + dbo + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/DependencyRegistrar.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/DependencyRegistrar.cs index b7a1b25c54..ac6e3dd393 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/DependencyRegistrar.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/DependencyRegistrar.cs @@ -1,6 +1,5 @@ using Autofac; using Autofac.Core; -using Autofac.Integration.Mvc; using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; using SmartStore.Core.Infrastructure.DependencyManagement; diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index d20b39b8a3..6f343b6b69 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -1,10 +1,10 @@ -FriendlyName: Google Merchant Center (GMC) +FriendlyName: Google Merchant Center (GMC) feed SystemName: SmartStore.GoogleMerchantCenter Group: Marketing -Version: 2.31 -MinAppVersion: 2.1.0 +Version: 2.6.0.1 +MinAppVersion: 2.5.0 Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.GoogleMerchantCenter.dll ResourceRootKey: Plugins.Feed.Froogle -Url: http://community.smartstore.com/index.php?/files/file/37-google-merchant-center-gmc-feed/ \ No newline at end of file +Url: http://community.smartstore.com/marketplace/file/37-google-merchant-center-gmc-feed/ diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs index 43d5537c0f..7064e84880 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs @@ -8,6 +8,11 @@ namespace SmartStore.GoogleMerchantCenter.Domain ///
public partial class GoogleProductRecord : BaseEntity { + public GoogleProductRecord() + { + Export = true; + } + public int ProductId { get; set; } public string Taxonomy { get; set; } @@ -22,5 +27,18 @@ public partial class GoogleProductRecord : BaseEntity public bool IsTouched { get; set; } public DateTime CreatedOnUtc { get; set; } public DateTime UpdatedOnUtc { get; set; } - } + + public bool Export { get; set; } + + public int Multipack { get; set; } + public bool? IsBundle { get; set; } + public bool? IsAdult { get; set; } + public string EnergyEfficiencyClass { get; set; } + + public string CustomLabel0 { get; set; } + public string CustomLabel1 { get; set; } + public string CustomLabel2 { get; set; } + public string CustomLabel3 { get; set; } + public string CustomLabel4 { get; set; } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs index 3a534605c5..10b5732247 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs @@ -4,7 +4,7 @@ using SmartStore.GoogleMerchantCenter.Models; using SmartStore.GoogleMerchantCenter.Services; using SmartStore.Web.Framework.Events; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.GoogleMerchantCenter { @@ -18,19 +18,20 @@ public Events(IGoogleFeedService googleService) { this._googleService = googleService; } - + public void HandleEvent(TabStripCreated eventMessage) { if (eventMessage.TabStripName == "product-edit") { var productId = ((TabbableModel)eventMessage.Model).Id; - eventMessage.ItemFactory.Add().Text("GMC") - .Name("tab-gmc") - .Icon("fa fa-google fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "GMC" }) - .Route("SmartStore.GoogleMerchantCenter", new { action = "ProductEditTab", productId = productId }) - .Ajax(); - } + + eventMessage.ItemFactory.Add().Text("GMC") + .Name("tab-gmc") + .Icon("fa fa-google fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "GMC" }) + .Route("SmartStore.GoogleMerchantCenter", new { action = "ProductEditTab", productId = productId }) + .Ajax(); + } } public void HandleEvent(ModelBoundEvent eventMessage) @@ -48,7 +49,7 @@ public void HandleEvent(ModelBoundEvent eventMessage) if (entity == null) { - entity = new GoogleProductRecord() + entity = new GoogleProductRecord { ProductId = model.ProductId, CreatedOnUtc = utcNow @@ -63,7 +64,17 @@ public void HandleEvent(ModelBoundEvent eventMessage) entity.Taxonomy = model.Taxonomy; entity.Material = model.Material; entity.Pattern = model.Pattern; + entity.Export = model.Export2; entity.UpdatedOnUtc = utcNow; + entity.Multipack = model.Multipack2 ?? 0; + entity.IsBundle = model.IsBundle; + entity.IsAdult = model.IsAdult; + entity.EnergyEfficiencyClass = model.EnergyEfficiencyClass; + entity.CustomLabel0 = model.CustomLabel0; + entity.CustomLabel1 = model.CustomLabel1; + entity.CustomLabel2 = model.CustomLabel2; + entity.CustomLabel3 = model.CustomLabel3; + entity.CustomLabel4 = model.CustomLabel4; entity.IsTouched = entity.IsTouched(); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs index a0bd84ae10..5a8dd0168d 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs @@ -9,25 +9,28 @@ public static string XEditableLink(this HtmlHelper hlp, string fieldName, string { string displayText = null; - if (fieldName == "Gender" || fieldName == "AgeGroup") - displayText = "<#= {0}Localize #>".FormatWith(fieldName); + if (fieldName == "Gender" || fieldName == "AgeGroup" || fieldName == "Export2" || fieldName == "IsBundle" || fieldName == "IsAdult") + displayText = "<#= {0}Localize #>".FormatInvariant(fieldName); else - displayText = "<#= {0} #>".FormatWith(fieldName); + 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.FormatWith(fieldName, fieldName.ToLower(), type, displayText); + return skeleton.FormatInvariant(fieldName, fieldName.ToLower(), type, displayText); } - public static bool IsTouched(this GoogleProductRecord product) + public static bool IsTouched(this GoogleProductRecord p) { - if (product != null) + if (p != null) { - return product.Taxonomy.HasValue() || product.Gender.HasValue() || product.AgeGroup.HasValue() || product.Color.HasValue() || - product.Size.HasValue() || product.Material.HasValue() || product.Pattern.HasValue() || product.ItemGroupId.HasValue(); + return + p.Taxonomy.HasValue() || p.Gender.HasValue() || p.AgeGroup.HasValue() || p.Color.HasValue() || + p.Size.HasValue() || p.Material.HasValue() || p.Pattern.HasValue() || p.ItemGroupId.HasValue() || + !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 c21aaa3e62..6be270d3aa 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.de-DE.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.de-DE.txt @@ -1,13 +1,13 @@ Baby & Kleinkind -Baby & Kleinkind > Baby baden -Baby & Kleinkind > Baby baden > Babybadewannen -Baby & Kleinkind > Baby baden > Shampoo-Schutzschilder -Baby & Kleinkind > Babygesundheit -Baby & Kleinkind > Babygesundheit > Baby-Gesundheits- & Pflege-Sets -Baby & Kleinkind > Babygesundheit > Nasensauger -Baby & Kleinkind > Babygesundheit > Reinigungstücher für Schnuller -Baby & Kleinkind > Babygesundheit > Schnuller & Beißringe -Baby & Kleinkind > Babygesundheit > Schnullerclips & -ketten +Baby & Kleinkind > Baby Badebedarf +Baby & Kleinkind > Baby Badebedarf > Babybadewannen +Baby & Kleinkind > Baby Badebedarf > Shampoo-Schutzschilder +Baby & Kleinkind > Baby Gesundheitsbedarf +Baby & Kleinkind > Baby Gesundheitsbedarf > Baby-Gesundheits- & Pflegesets +Baby & Kleinkind > Baby Gesundheitsbedarf > Nasensauger +Baby & Kleinkind > Baby Gesundheitsbedarf > Reinigungstücher für Schnuller +Baby & Kleinkind > Baby Gesundheitsbedarf > Schnuller & Beißringe +Baby & Kleinkind > Baby Gesundheitsbedarf > Schnullerclips & -ketten Baby & Kleinkind > Babysicherheit Baby & Kleinkind > Babysicherheit > Baby- & Haustierschutzgitter Baby & Kleinkind > Babysicherheit > Baby-Schutzleisten @@ -17,28 +17,30 @@ Baby & Kleinkind > Babysicherheit > Babysicherungen & Schutzvorrichtungen Baby & Kleinkind > Babysicherheit > Zubehör für Baby- & Haustierschutzgitter Baby & Kleinkind > Babyspielwaren Baby & Kleinkind > Babyspielwaren > ABC-Lernspielzeuge -Baby & Kleinkind > Babyspielwaren > Baby-Activity-Spielzeug +Baby & Kleinkind > Babyspielwaren > Baby-Aktiv-Spielzeug Baby & Kleinkind > Babyspielwaren > Baby-Mobile-Zubehör Baby & Kleinkind > Babyspielwaren > Baby-Mobiles Baby & Kleinkind > Babyspielwaren > Babylauflernhilfen Baby & Kleinkind > Babyspielwaren > Babyrasseln Baby & Kleinkind > Babyspielwaren > Babyschaukeln & Türhopser Baby & Kleinkind > Babyspielwaren > Babywippen -Baby & Kleinkind > Babyspielwaren > Lauf- & Spielgitter -Baby & Kleinkind > Babyspielwaren > Schnuller und Beruhigung +Baby & Kleinkind > Babyspielwaren > Lauf- & Spielgitterreinigungst +Baby & Kleinkind > Babyspielwaren > Schnuller & Beruhigung Baby & Kleinkind > Babyspielwaren > Sortier-, Stapel- & Steckspielzeug Baby & Kleinkind > Babyspielwaren > Spiel- & Krabbeldecken Baby & Kleinkind > Babyspielwaren > Zieh- & Schiebespielzeug Baby & Kleinkind > Babytransport Baby & Kleinkind > Babytransport > Baby- & Kleinkindautositze -Baby & Kleinkind > Babytransport > Babytragen +Baby & Kleinkind > Babytransport > Babyträger Baby & Kleinkind > Babytransport > Kinderwagen Baby & Kleinkind > Babytransportzubehör Baby & Kleinkind > Babytransportzubehör > Einkaufswagen- & Hochstuhlbezüge +Baby & Kleinkind > Babytransportzubehör > Fußsäcke Baby & Kleinkind > Babytransportzubehör > Zubehör für Baby- & Kleinkindautositze -Baby & Kleinkind > Babytransportzubehör > Zubehör für Babytragen +Baby & Kleinkind > Babytransportzubehör > Zubehör für Babyträger Baby & Kleinkind > Babytransportzubehör > Zubehör für Kinderwagen -Baby & Kleinkind > Geschenksets für Babys und Kleinkinder +Baby & Kleinkind > Geschenksets für Babys +Baby & Kleinkind > Puckdecken Baby & Kleinkind > Stillen & Füttern Baby & Kleinkind > Stillen & Füttern > Baby-Spucktücher Baby & Kleinkind > Stillen & Füttern > Babyflaschen @@ -47,17 +49,19 @@ Baby & Kleinkind > Stillen & Füttern > Flaschenwärmer & Sterilisatoren Baby & Kleinkind > Stillen & Füttern > Lerntassen Baby & Kleinkind > Stillen & Füttern > Lätzchen Baby & Kleinkind > Stillen & Füttern > Milchpumpen +Baby & Kleinkind > Stillen & Füttern > Milchpumpenzubehör Baby & Kleinkind > Stillen & Füttern > Muttermilchbehälter Baby & Kleinkind > Stillen & Füttern > Nahrung für Babys & Kleinkinder Baby & Kleinkind > Stillen & Füttern > Nahrung für Babys & Kleinkinder > Baby-Snacks -Baby & Kleinkind > Stillen & Füttern > Nahrung für Babys & Kleinkinder > Babymüsli +Baby & Kleinkind > Stillen & Füttern > Nahrung für Babys & Kleinkinder > Babygetreide Baby & Kleinkind > Stillen & Füttern > Nahrung für Babys & Kleinkinder > Babynahrung Baby & Kleinkind > Stillen & Füttern > Nahrung für Babys & Kleinkinder > Babysäfte +Baby & Kleinkind > Stillen & Füttern > Nahrung für Babys & Kleinkinder > Getränke und Milchmischgetränke Baby & Kleinkind > Stillen & Füttern > Nahrung für Babys & Kleinkinder > Milchnahrung Baby & Kleinkind > Stillen & Füttern > Sauger für Babyflaschen -Baby & Kleinkind > Stillen & Füttern > Stilleinlagen -Baby & Kleinkind > Stillen & Füttern > Stillhütchen +Baby & Kleinkind > Stillen & Füttern > Stillhütchen & -einlagen Baby & Kleinkind > Stillen & Füttern > Stillkissen +Baby & Kleinkind > Stillen & Füttern > Stillkissenbezüge Baby & Kleinkind > Stillen & Füttern > Stilltücher Baby & Kleinkind > Töpfchentraining Baby & Kleinkind > Töpfchentraining > Töpfchen @@ -67,6 +71,7 @@ Baby & Kleinkind > Wickelbedarf > Baby-Reinigungstücher Baby & Kleinkind > Wickelbedarf > Behandlung von Windeldermatitis Baby & Kleinkind > Wickelbedarf > Behälter & Warmhalter für Baby-Reinigungstücher Baby & Kleinkind > Wickelbedarf > Wickelauflagen +Baby & Kleinkind > Wickelbedarf > Wickelauflagen & Bezüge Baby & Kleinkind > Wickelbedarf > Windel-Organizer Baby & Kleinkind > Wickelbedarf > Windelbeutel Baby & Kleinkind > Wickelbedarf > Windeleimer @@ -74,19 +79,17 @@ Baby & Kleinkind > Wickelbedarf > Windeleimerzubehör Baby & Kleinkind > Wickelbedarf > Windeleinlagen Baby & Kleinkind > Wickelbedarf > Windeln Baby & Kleinkind > Wickelbedarf > Windelsets -Baby & Kleinkind > Wickeldecken Bekleidung & Accessoires Bekleidung & Accessoires > Bekleidung Bekleidung & Accessoires > Bekleidung > Anzüge -Bekleidung & Accessoires > Bekleidung > Anzüge > Hosenanzüge +Bekleidung & Accessoires > Bekleidung > Anzüge > Anzüge & Hosenanzüge Bekleidung & Accessoires > Bekleidung > Anzüge > Kostüme Bekleidung & Accessoires > Bekleidung > Anzüge > Smokings -Bekleidung & Accessoires > Bekleidung > Anzüge > Westen & Sakkos Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung > Baby- & Kleinkind-Kombis Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung > Baby- & Kleinkind-Oberbekleidung Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung > Baby- & Kleinkindsocken -Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung > Baby-Bodies +Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung > Baby-Bodys Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung > Baby-Schlafkleidung & -Schlafsäcke Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung > Babyüberhosen Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung > Bademode für Babys & Kleinkinder @@ -95,14 +98,6 @@ Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung > Kleinkindu Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung > Oberteile für Babys & Kleinkinder Bekleidung & Accessoires > Bekleidung > Baby- & Kleinkindbekleidung > Unterteile für Babys & Kleinkinder Bekleidung & Accessoires > Bekleidung > Bademode -Bekleidung & Accessoires > Bekleidung > Bademode > Badeshorts -Bekleidung & Accessoires > Bekleidung > Bademode > Badeslips -Bekleidung & Accessoires > Bekleidung > Bademode > Bikini Oberteile -Bekleidung & Accessoires > Bekleidung > Bademode > Bikini Unterteile -Bekleidung & Accessoires > Bekleidung > Bademode > Bikinis -Bekleidung & Accessoires > Bekleidung > Bademode > Einteiler-Badeanzüge -Bekleidung & Accessoires > Bekleidung > Bademode > Strandkleider -Bekleidung & Accessoires > Bekleidung > Bademode > Tankinis Bekleidung & Accessoires > Bekleidung > Brautmoden Bekleidung & Accessoires > Bekleidung > Brautmoden > Brautjungfernkleider Bekleidung & Accessoires > Bekleidung > Brautmoden > Brautkleider @@ -111,19 +106,7 @@ Bekleidung & Accessoires > Bekleidung > Einteiler > Latzhosen Bekleidung & Accessoires > Bekleidung > Einteiler > Overalls Bekleidung & Accessoires > Bekleidung > Einteiler > Turn- & Gymnastikanzüge Bekleidung & Accessoires > Bekleidung > Hosen -Bekleidung & Accessoires > Bekleidung > Hosen > Anzughosen -Bekleidung & Accessoires > Bekleidung > Hosen > Cargo-Hosen -Bekleidung & Accessoires > Bekleidung > Hosen > Cordhosen -Bekleidung & Accessoires > Bekleidung > Hosen > Jeans -Bekleidung & Accessoires > Bekleidung > Hosen > Khakihosen -Bekleidung & Accessoires > Bekleidung > Hosen > Leder- & Wildlederhosen -Bekleidung & Accessoires > Bekleidung > Hosen > Leggings -Bekleidung & Accessoires > Bekleidung > Hosen > Leinenhosen Bekleidung & Accessoires > Bekleidung > Kleider -Bekleidung & Accessoires > Bekleidung > Kleider > Abend- & Ballkleider -Bekleidung & Accessoires > Bekleidung > Kleider > Alltagskleider -Bekleidung & Accessoires > Bekleidung > Kleider > Cocktail- & Partykleider -Bekleidung & Accessoires > Bekleidung > Kleider > Kleines Schwarzes Bekleidung & Accessoires > Bekleidung > Kombinationen Bekleidung & Accessoires > Bekleidung > Nachtwäsche & Loungewear Bekleidung & Accessoires > Bekleidung > Nachtwäsche & Loungewear > Bademäntel @@ -132,28 +115,16 @@ Bekleidung & Accessoires > Bekleidung > Nachtwäsche & Loungewear > Nachthemden Bekleidung & Accessoires > Bekleidung > Nachtwäsche & Loungewear > Schlafanzüge Bekleidung & Accessoires > Bekleidung > Röcke Bekleidung & Accessoires > Bekleidung > Shirts & Tops -Bekleidung & Accessoires > Bekleidung > Shirts & Tops > Fantrikots -Bekleidung & Accessoires > Bekleidung > Shirts & Tops > Hemden & Blusen -Bekleidung & Accessoires > Bekleidung > Shirts & Tops > Leibchen -Bekleidung & Accessoires > Bekleidung > Shirts & Tops > Poloshirts -Bekleidung & Accessoires > Bekleidung > Shirts & Tops > Sweater & Strickjacken -Bekleidung & Accessoires > Bekleidung > Shirts & Tops > Sweatshirts -Bekleidung & Accessoires > Bekleidung > Shirts & Tops > T-Shirts Bekleidung & Accessoires > Bekleidung > Shorts -Bekleidung & Accessoires > Bekleidung > Shorts > Bermudashorts -Bekleidung & Accessoires > Bekleidung > Shorts > Cargo-Shorts -Bekleidung & Accessoires > Bekleidung > Shorts > Hot Pants Bekleidung & Accessoires > Bekleidung > Skorts Bekleidung & Accessoires > Bekleidung > Sportbekleidung Bekleidung & Accessoires > Bekleidung > Sportbekleidung > American Football-Hosen Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Boxshorts -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Gymnastik- & Ballettanzüge Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Jagdbekleidung Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Jagdbekleidung > Ghillie-Anzüge +Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Jagdbekleidung > Jagd- & Angelwesten Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Jagdbekleidung > Jagd- & taktische Hosen -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Jagdbekleidung > Jagd- & taktische Westen Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Kampfsporthosen -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Lange Unterhosen Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Motorradbekleidung Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Motorradbekleidung > Motorrad-Kombis Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Motorradbekleidung > Motorradhosen @@ -165,43 +136,17 @@ Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Radsport-Funktionsbekl Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Radsport-Funktionsbekleidung > Fahrradtrikots Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Radsport-Funktionsbekleidung > Fahrradträgerhosen Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Radsport-Funktionsbekleidung > Radsport-Zeitfahranzüge -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Saunaanzüge -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Schneehosen und - anzüge -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sport-Tanktops -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sporthosen -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sporthosen (kurz) -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sportjacken -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sportjacken > Windbreakers -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sportkleider -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sportröcke -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sportshirts -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sportunterkleidung -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sportunterkleidung > Kompressionshose (kurz) -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sportunterkleidung > Sport-BHs -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sportunterkleidung > Sportunterwäsche -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Sportunterkleidung > Suspensorien -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Tanzkleidung -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Tanzkleidung > Tanz-Leggings -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Tanzkleidung > Tanz-Oberteile -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Tanzkleidung > Tanzhosen -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Tanzkleidung > Tanzkleider -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Tanzkleidung > Tanzröcke -Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Trainingsanzüge +Bekleidung & Accessoires > Bekleidung > Sportbekleidung > Tanzkleider, Tanzröcke und Tanzkostüme Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Dirndl Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Hakama-Hosen Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Kimono-Oberbekleidung -Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Kimono-Oberbekleidung > Haori-Jacken -Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Kimono-Oberbekleidung > Kimono-Mäntel Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Kimonos -Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Kimonos > Furisode-Kimonos -Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Kimonos > Hochzeitskimonos -Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Kimonos > Informelle Kimonos -Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Kimonos > Iromuji-Kimonos -Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Kimonos > Komon-Kimonos -Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Kimonos > Tomesode- & Houmongi-Kimonos Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Religiöse Festkleidung +Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Religiöse Festkleidung > Tauf- & Kommunionskleider +Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Saris Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Trachten Lederhosen +Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Traditionelle japanische formelle Kleidung Bekleidung & Accessoires > Bekleidung > Traditionelle & Festkleidung > Yukata Bekleidung & Accessoires > Bekleidung > Uniformen Bekleidung & Accessoires > Bekleidung > Uniformen > Arbeitshosen @@ -222,62 +167,45 @@ Bekleidung & Accessoires > Bekleidung > Uniformen > Sportuniformen > Ringerunifo Bekleidung & Accessoires > Bekleidung > Uniformen > Sportuniformen > Schiedsrichteruniformen Bekleidung & Accessoires > Bekleidung > Uniformen > Sportuniformen > Softballuniformen Bekleidung & Accessoires > Bekleidung > Uniformen > Uniformen für das Restaurantgewerbe -Bekleidung & Accessoires > Bekleidung > Uniformen > Uniformen für das Restaurantgewerbe > Hosen für Köche -Bekleidung & Accessoires > Bekleidung > Uniformen > Uniformen für das Restaurantgewerbe > Hüte für Köche +Bekleidung & Accessoires > Bekleidung > Uniformen > Uniformen für das Restaurantgewerbe > Kochhosen Bekleidung & Accessoires > Bekleidung > Uniformen > Uniformen für das Restaurantgewerbe > Kochjacken +Bekleidung & Accessoires > Bekleidung > Uniformen > Uniformen für das Restaurantgewerbe > Kochmützen Bekleidung & Accessoires > Bekleidung > Uniformen > Ärztekittel Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > BH-Zubehör Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > BH-Zubehör > BH-Einlagen Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > BH-Zubehör > BH-Träger & -Erweiterungen Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > BH-Zubehör > BH-Träger-Pads -Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > BH-Zubehör > Brustaufkleber & -Concealer +Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > BH-Zubehör > Brustaufkleber Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Büstenhalter -Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Büstenhalter > Still-BHs Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Dessous Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Dessous-Accessoires Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Dessous-Accessoires > Strumpfbänder Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Dessous-Accessoires > Strumpfhalter -Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Hochzeitsdessous +Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Lange Unterhosen Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Petticoats & Rüschenhöschen Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Shapewear Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Socken Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Strumpfhosen +Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Suspensorien Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Unterhemden Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Unterhosen Bekleidung & Accessoires > Bekleidung > Unterwäsche & Socken > Unterwäsche Bekleidung & Accessoires > Bekleidung > Überbekleidung Bekleidung & Accessoires > Bekleidung > Überbekleidung > Chaps Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Anoraks -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Boleros -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Capes & Ponchos -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > College Jacken -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Daunenjacken -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Fleecejacken -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Jeansjacken -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Leder- & Wildlederjacken -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Overcoats -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Pelzmäntel -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Sakkos -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Trenchcoats -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Mäntel & Jacken > Tweedjacken -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Regenbekleidung -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Regenbekleidung > Regenanzüge -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Regenbekleidung > Regenhosen -Bekleidung & Accessoires > Bekleidung > Überbekleidung > Regenbekleidung > Regenmäntel +Bekleidung & Accessoires > Bekleidung > Überbekleidung > Regenanzüge +Bekleidung & Accessoires > Bekleidung > Überbekleidung > Regenhosen +Bekleidung & Accessoires > Bekleidung > Überbekleidung > Schneehosen und - anzüge Bekleidung & Accessoires > Bekleidung > Überbekleidung > Westen Bekleidung & Accessoires > Bekleidungsaccessoires Bekleidung & Accessoires > Bekleidungsaccessoires > Accessoires für traditionelle Kleidung -Bekleidung & Accessoires > Bekleidungsaccessoires > Accessoires für traditionelle Kleidung > Kimono-Unterkleidung -Bekleidung & Accessoires > Bekleidungsaccessoires > Accessoires für traditionelle Kleidung > Obi-Accessoires Bekleidung & Accessoires > Bekleidungsaccessoires > Accessoires für traditionelle Kleidung > Obis Bekleidung & Accessoires > Bekleidungsaccessoires > Accessoires für traditionelle Kleidung > Tabi-Socken Bekleidung & Accessoires > Bekleidungsaccessoires > Ansteckbuttons -Bekleidung & Accessoires > Bekleidungsaccessoires > Arm- & Beinstulpen -Bekleidung & Accessoires > Bekleidungsaccessoires > Arm- & Beinstulpen > Armstulpen -Bekleidung & Accessoires > Bekleidungsaccessoires > Arm- & Beinstulpen > Beinstulpen +Bekleidung & Accessoires > Bekleidungsaccessoires > Armstulpen Bekleidung & Accessoires > Bekleidungsaccessoires > Bandanas & Stofftücher +Bekleidung & Accessoires > Bekleidungsaccessoires > Beinstulpen Bekleidung & Accessoires > Bekleidungsaccessoires > Braut-Accessoires Bekleidung & Accessoires > Bekleidungsaccessoires > Braut-Accessoires > Brautschleier Bekleidung & Accessoires > Bekleidungsaccessoires > Einstecktücher @@ -286,34 +214,23 @@ Bekleidung & Accessoires > Bekleidungsaccessoires > Gürtel Bekleidung & Accessoires > Bekleidungsaccessoires > Gürtelschnallen Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Diademe +Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Duttkissen Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Haargabeln Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Haargummis -Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Haarklammern +Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Haarklammern, -nadeln & -clips Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Haarkränze Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Haarkämme -Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Haarnadeln & -clips Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Haarnetze Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Haarverlängerungen +Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Perücken +Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Perücken-Accessoires +Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Perücken-Accessoires > Perückenkleber +Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Perücken-Accessoires > Perückennetze Bekleidung & Accessoires > Bekleidungsaccessoires > Haaraccessoires > Stirnbänder Bekleidung & Accessoires > Bekleidungsaccessoires > Halswärmer Bekleidung & Accessoires > Bekleidungsaccessoires > Handschuhe & Fausthandschuhe -Bekleidung & Accessoires > Bekleidungsaccessoires > Handschuhe & Fausthandschuhe > Abendhandschuhe -Bekleidung & Accessoires > Bekleidungsaccessoires > Handschuhe & Fausthandschuhe > Fausthandschuhe -Bekleidung & Accessoires > Bekleidungsaccessoires > Handschuhe & Fausthandschuhe > Handschuhe -Bekleidung & Accessoires > Bekleidungsaccessoires > Handschuhe & Fausthandschuhe > Ski- & Snowboardhandschuhe Bekleidung & Accessoires > Bekleidungsaccessoires > Hosenträger Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte -Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte > Barette -Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte > Cowboyhüte -Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte > Filzhüte -Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte > Kappen -Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte > Kappen > Ballonmützen -Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte > Kappen > Baseballmützen -Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte > Melonen (Hut) -Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte > Mützen -Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte > Schirmhüte -Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte > Sonnenhüte -Bekleidung & Accessoires > Bekleidungsaccessoires > Hüte > Sonnenvisiere Bekleidung & Accessoires > Bekleidungsaccessoires > Knopfnieten Bekleidung & Accessoires > Bekleidungsaccessoires > Kopfbekleidung & -tücher Bekleidung & Accessoires > Bekleidungsaccessoires > Kopfbekleidung & -tücher > Fascinators @@ -321,18 +238,15 @@ Bekleidung & Accessoires > Bekleidungsaccessoires > Kopfbekleidung & -tücher > Bekleidung & Accessoires > Bekleidungsaccessoires > Kopfbekleidung & -tücher > Turbane Bekleidung & Accessoires > Bekleidungsaccessoires > Kragenstäbchen Bekleidung & Accessoires > Bekleidungsaccessoires > Krawatten -Bekleidung & Accessoires > Bekleidungsaccessoires > Krawatten > Ascotkrawatte -Bekleidung & Accessoires > Bekleidungsaccessoires > Krawatten > Bolotie -Bekleidung & Accessoires > Bekleidungsaccessoires > Krawatten > Fliegen -Bekleidung & Accessoires > Bekleidungsaccessoires > Krawatten > Schlipse Bekleidung & Accessoires > Bekleidungsaccessoires > Krawattennadeln Bekleidung & Accessoires > Bekleidungsaccessoires > Leis Bekleidung & Accessoires > Bekleidungsaccessoires > Manschettenknöpfe Bekleidung & Accessoires > Bekleidungsaccessoires > Muffe Bekleidung & Accessoires > Bekleidungsaccessoires > Ohrenschützer Bekleidung & Accessoires > Bekleidungsaccessoires > Schals & Halstücher -Bekleidung & Accessoires > Bekleidungsaccessoires > Schleier +Bekleidung & Accessoires > Bekleidungsaccessoires > Schwangerschaftsgürtel & Bauchstützen Bekleidung & Accessoires > Bekleidungsaccessoires > Schweißbänder +Bekleidung & Accessoires > Bekleidungsaccessoires > Schärpen Bekleidung & Accessoires > Bekleidungsaccessoires > Sonnenbrillen Bekleidung & Accessoires > Bekleidungsaccessoires > Sturmhauben Bekleidung & Accessoires > Bekleidungsaccessoires > Zubehör für Baby- & Kleinkindbekleidung @@ -349,36 +263,25 @@ Bekleidung & Accessoires > Handtaschen, Geldbörsen & Etuis Bekleidung & Accessoires > Handtaschen, Geldbörsen & Etuis > Ausweistaschen Bekleidung & Accessoires > Handtaschen, Geldbörsen & Etuis > Geldbeutel & Geldklammern Bekleidung & Accessoires > Handtaschen, Geldbörsen & Etuis > Handtaschen -Bekleidung & Accessoires > Handtaschen, Geldbörsen & Etuis > Handtaschen > Clutches & Taschen für besondere Anlässe -Bekleidung & Accessoires > Handtaschen, Geldbörsen & Etuis > Handtaschen > Henkeltaschen -Bekleidung & Accessoires > Handtaschen, Geldbörsen & Etuis > Handtaschen > Hobo-Handtaschen -Bekleidung & Accessoires > Handtaschen, Geldbörsen & Etuis > Handtaschen > Schultertaschen -Bekleidung & Accessoires > Handtaschen, Geldbörsen & Etuis > Handtaschen > Tragetaschen -Bekleidung & Accessoires > Handtaschen, Geldbörsen & Etuis > Handtaschen > Umhängetaschen Bekleidung & Accessoires > Handtaschen, Geldbörsen & Etuis > Visitenkartenetuis Bekleidung & Accessoires > Kostüme & Accessoires -Bekleidung & Accessoires > Kostüme & Accessoires > Accessoires für Perücken -Bekleidung & Accessoires > Kostüme & Accessoires > Accessoires für Perücken > Kappen für Perücken -Bekleidung & Accessoires > Kostüme & Accessoires > Accessoires für Perücken > Kleber für Perücken Bekleidung & Accessoires > Kostüme & Accessoires > Kostümaccessoires Bekleidung & Accessoires > Kostüme & Accessoires > Kostümaccessoires > Glatzen -Bekleidung & Accessoires > Kostüme & Accessoires > Kostümaccessoires > Kostümaccessoire-Sets +Bekleidung & Accessoires > Kostüme & Accessoires > Kostümaccessoires > Kostüm-Accessoire-Sets +Bekleidung & Accessoires > Kostüme & Accessoires > Kostümaccessoires > Kostüm-Handschuhe Bekleidung & Accessoires > Kostüme & Accessoires > Kostümaccessoires > Kostümhüte Bekleidung & Accessoires > Kostüme & Accessoires > Kostümaccessoires > Plastikschmuck +Bekleidung & Accessoires > Kostüme & Accessoires > Kostümaccessoires > Spezial-Schminkeffekte +Bekleidung & Accessoires > Kostüme & Accessoires > Kostümaccessoires > Tabakwarenimitate Bekleidung & Accessoires > Kostüme & Accessoires > Kostümaccessoires > Umhänge für Kostüme Bekleidung & Accessoires > Kostüme & Accessoires > Kostüme & Verkleidungen Bekleidung & Accessoires > Kostüme & Accessoires > Kostümschuhe Bekleidung & Accessoires > Kostüme & Accessoires > Masken -Bekleidung & Accessoires > Kostüme & Accessoires > Perücken Bekleidung & Accessoires > Schmuck -Bekleidung & Accessoires > Schmuck > Armbanduhren -Bekleidung & Accessoires > Schmuck > Armbanduhren > Analoguhren -Bekleidung & Accessoires > Schmuck > Armbanduhren > Digitaluhren +Bekleidung & Accessoires > Schmuck > Armbanduhren & Taschenuhren Bekleidung & Accessoires > Schmuck > Armbänder -Bekleidung & Accessoires > Schmuck > Broschen & Anstecknadeln +Bekleidung & Accessoires > Schmuck > Broschen und Anstecknadeln Bekleidung & Accessoires > Schmuck > Charms & Anhänger -Bekleidung & Accessoires > Schmuck > Edelsteine -Bekleidung & Accessoires > Schmuck > Edelsteine > Diamanten Bekleidung & Accessoires > Schmuck > Fußkettchen Bekleidung & Accessoires > Schmuck > Halsketten Bekleidung & Accessoires > Schmuck > Körperschmuck @@ -396,72 +299,6 @@ Bekleidung & Accessoires > Schuh-Accessoires > Sporen Bekleidung & Accessoires > Schuh-Accessoires > Stiefelsocken Bekleidung & Accessoires > Schuh-Accessoires > Überschuhe Bekleidung & Accessoires > Schuhe -Bekleidung & Accessoires > Schuhe > Abendschuhe & Anzugschuhe -Bekleidung & Accessoires > Schuhe > Abendschuhe & Anzugschuhe > Brautschuhe -Bekleidung & Accessoires > Schuhe > Abendschuhe & Anzugschuhe > Pumps -Bekleidung & Accessoires > Schuhe > Abendschuhe & Anzugschuhe > Pumps > Klassische Pumps -Bekleidung & Accessoires > Schuhe > Abendschuhe & Anzugschuhe > Pumps > Spectator-Pumps -Bekleidung & Accessoires > Schuhe > Abendschuhe & Anzugschuhe > Pumps > d'Orsay Pumps -Bekleidung & Accessoires > Schuhe > Abendschuhe & Anzugschuhe > Schnürschuhe -Bekleidung & Accessoires > Schuhe > Clogs & Pantoletten -Bekleidung & Accessoires > Schuhe > Espadrilles -Bekleidung & Accessoires > Schuhe > Flache Schuhe -Bekleidung & Accessoires > Schuhe > Hausschuhe -Bekleidung & Accessoires > Schuhe > Loafers -Bekleidung & Accessoires > Schuhe > Mokassins -Bekleidung & Accessoires > Schuhe > Outdoor Schuhe -Bekleidung & Accessoires > Schuhe > Outdoor Schuhe > Angel- & Jagdschuhe -Bekleidung & Accessoires > Schuhe > Outdoor Schuhe > Gartenschuhe -Bekleidung & Accessoires > Schuhe > Outdoor Schuhe > Kletterschuhe -Bekleidung & Accessoires > Schuhe > Outdoor Schuhe > Leistungsruderschuhe -Bekleidung & Accessoires > Schuhe > Outdoor Schuhe > Wanderschuhe -Bekleidung & Accessoires > Schuhe > Outdoor Schuhe > Wassersportschuhe -Bekleidung & Accessoires > Schuhe > Sandalen -Bekleidung & Accessoires > Schuhe > Sandalen > Flipflops -Bekleidung & Accessoires > Schuhe > Sandalen > Outdoor Sandalen/vorne geschlossen -Bekleidung & Accessoires > Schuhe > Sandalen > Schlupfsandalen -Bekleidung & Accessoires > Schuhe > Schuhe für Babys & Kleinkinder -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sneakers -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sneakers > Fitness-Schuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sneakers > Laufschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sneakers > Sneakers & Freizeitschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sneakers > Walking-Schuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Ballet- & Tanzschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Basketball-Schuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Bowlingschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Boxschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Cheerleading-Schuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Fahrradschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Gewichtheberschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Golfschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Hallenfussballschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Hallenschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Hallenschuhe > Badmintonschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Hallenschuhe > Handballschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Hallenschuhe > Volleyball-Schuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Ringerschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Skateboard-Schuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Tennis-Schuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Sportschuhe > Tischtennis-Schuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Stollen- & Nockenschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Stollen- & Nockenschuhe > American Football-Schuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Stollen- & Nockenschuhe > Baseball- & Softballschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Stollen- & Nockenschuhe > Fußballschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Stollen- & Nockenschuhe > Lacrosse-Stollenschuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Stollen- & Nockenschuhe > Rugby-Schuhe -Bekleidung & Accessoires > Schuhe > Sportschuhe & Sneakers > Stollen- & Nockenschuhe > Spikes -Bekleidung & Accessoires > Schuhe > Stiefel -Bekleidung & Accessoires > Schuhe > Stiefel > Arbeits- & Sicherheitsstiefel -Bekleidung & Accessoires > Schuhe > Stiefel > Gefütterte Stiefel -Bekleidung & Accessoires > Schuhe > Stiefel > Gummistiefel -Bekleidung & Accessoires > Schuhe > Stiefel > Motorradstiefel -Bekleidung & Accessoires > Schuhe > Stiefel > Reitstiefel -Bekleidung & Accessoires > Schuhe > Stiefel > Schneestiefel -Bekleidung & Accessoires > Schuhe > Stiefel > Stiefeletten -Bekleidung & Accessoires > Schuhe > Stiefel > Western-Stiefel -Bekleidung & Accessoires > Schuhe > Stiefel > Wüsten- & Chukka-Boots Bürobedarf Bürobedarf > Ablage & Organisation Bürobedarf > Ablage & Organisation > Adressbücher @@ -477,24 +314,16 @@ Bürobedarf > Ablage & Organisation > Bindesysteme > Aktenordnerzubehör > Regis Bürobedarf > Ablage & Organisation > Bindesysteme > Bindegeräte Bürobedarf > Ablage & Organisation > Bindesysteme > Binderücken Bürobedarf > Ablage & Organisation > CD-/DVD-Ordnungssysteme -Bürobedarf > Ablage & Organisation > CD-/DVD-Ordnungssysteme > CD- & DVD-Verpackungen -Bürobedarf > Ablage & Organisation > CD-/DVD-Ordnungssysteme > CD/DVD-Hüllen -Bürobedarf > Ablage & Organisation > CD-/DVD-Ordnungssysteme > CD/DVD-Taschen & -Sonnenblendentaschen Bürobedarf > Ablage & Organisation > Federmappen Bürobedarf > Ablage & Organisation > Geldkassetten Bürobedarf > Ablage & Organisation > Kalender, Organizer & Zeitplaner Bürobedarf > Ablage & Organisation > Karteikästen Bürobedarf > Ablage & Organisation > Kartenhüllen -Bürobedarf > Ablage & Organisation > Laminieren -Bürobedarf > Ablage & Organisation > Laminieren > Laminierfolie, Schutzhüllen -Bürobedarf > Ablage & Organisation > Laminieren > Laminiergeräte Bürobedarf > Ablage & Organisation > Postkartenorganizer Bürobedarf > Ablage & Organisation > Postsortiersysteme Bürobedarf > Ablage & Organisation > Register- & Hängemappen Bürobedarf > Ablage & Organisation > Rezeptkästen Bürobedarf > Ablage & Organisation > Schreibtischorganizer -Bürobedarf > Ablage & Organisation > Schreibtischorganizer > Ablagekästen -Bürobedarf > Ablage & Organisation > Staufächer und -schränke Bürobedarf > Ablage & Organisation > Visitenkarten-Ständer Bürobedarf > Ablage & Organisation > Visitenkartenbücher Bürobedarf > Bücherzubehör @@ -504,21 +333,22 @@ Bürobedarf > Bücherzubehör > Leselampen Bürobedarf > Bücherzubehör > Lesezeichen Bürobedarf > Büroarbeitsmittel Bürobedarf > Büroarbeitsmittel > Bleistiftspitzer +Bürobedarf > Büroarbeitsmittel > Brieföffner +Bürobedarf > Büroarbeitsmittel > Bürostempel Bürobedarf > Büroarbeitsmittel > Heftgeräte Bürobedarf > Büroarbeitsmittel > Heftklammernzieher +Bürobedarf > Büroarbeitsmittel > Klebebandabroller Bürobedarf > Büroarbeitsmittel > Klemmbretter -Bürobedarf > Büroarbeitsmittel > Lineale Bürobedarf > Büroarbeitsmittel > Lupen -Bürobedarf > Büroarbeitsmittel > Scheren Bürobedarf > Büroarbeitsmittel > Schreibgeräte Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Füller & Bleistifte Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Füller & Bleistifte > Bleistifte Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Füller & Bleistifte > Bleistifte > Schreibbleistifte Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Füller & Bleistifte > Bleistifte > Zeichenbleistifte & Buntstifte Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Füller & Bleistifte > Füller- & Bleistift-Sets -Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Füller & Bleistifte > Multifunktions-Schreibgeräte Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Füller & Bleistifte > Schreibstifte, Kugelschreiber & Füller Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Markierstifte & Textmarker +Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Multifunktions-Schreibgeräte Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Pastellkreiden Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Tafelkreide Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Wachsmalstifte @@ -526,11 +356,7 @@ Bürobedarf > Büroarbeitsmittel > Schreibgeräte > Zeichenkohle Bürobedarf > Büroarbeitsmittel > Schreibwerkzeugzubehör Bürobedarf > Büroarbeitsmittel > Schreibwerkzeugzubehör > Bleistiftminen Bürobedarf > Büroarbeitsmittel > Schreibwerkzeugzubehör > Füllertinte & -patronen -Bürobedarf > Büroarbeitsmittel > Schreibwerkzeugzubehör > Kugelschreiberminen Bürobedarf > Büroarbeitsmittel > Schreibwerkzeugzubehör > Marker-Nachfülltinte -Bürobedarf > Büroarbeitsmittel > Stempel -Bürobedarf > Büroarbeitsmittel > Stempel > Bürostempel -Bürobedarf > Büroarbeitsmittel > Stempel > Motivstempel Bürobedarf > Büroarbeitsmittel > Tischglocken Bürobedarf > Bürogeräte Bürobedarf > Bürogeräte > Aktenvernichter @@ -538,9 +364,11 @@ Bürobedarf > Bürogeräte > Diktiergeräte & Geräte zur Transkription Bürobedarf > Bürogeräte > Elektronische Wörterbücher & Übersetzungsgeräte Bürobedarf > Bürogeräte > Etikettendrucker Bürobedarf > Bürogeräte > Frankiermaschinen +Bürobedarf > Bürogeräte > Laminiergeräte Bürobedarf > Bürogeräte > Schreibmaschinen Bürobedarf > Bürogeräte > Stempeluhren Bürobedarf > Bürogeräte > Taschenrechner +Bürobedarf > Bürogeräte > Taschenrechner-Zubehör Bürobedarf > Büromatten & Stuhlunterlagen Bürobedarf > Büromatten & Stuhlunterlagen > Arbeitsplatzmatten Bürobedarf > Büromatten & Stuhlunterlagen > Büromatten @@ -552,20 +380,14 @@ Bürobedarf > Bürowagen > Medienwagen Bürobedarf > Bürowagen > Mehrzweckwagen Bürobedarf > Bürowagen > Postwagen Bürobedarf > Impulsschweißgeräte +Bürobedarf > Laptophalter Bürobedarf > Namensschilder Bürobedarf > Papierbearbeitung -Bürobedarf > Papierbearbeitung > Bastelmesser Bürobedarf > Papierbearbeitung > Blattwender Bürobedarf > Papierbearbeitung > Briefbeschwerer Bürobedarf > Papierbearbeitung > Locher -Bürobedarf > Papierbearbeitung > Locher > Bürolocher -Bürobedarf > Papierbearbeitung > Locher > Dekolocher Bürobedarf > Papierbearbeitung > Papierfalzmaschinen Bürobedarf > Papierbearbeitung > Papierrüttler -Bürobedarf > Papierbearbeitung > Papierschneider -Bürobedarf > Papierbearbeitung > Papierschneiderzubehör -Bürobedarf > Papierbearbeitung > Papierschneiderzubehör > Papierschneidetische -Bürobedarf > Papierbearbeitung > Schneidematten Bürobedarf > Papierbearbeitung > Schreibunterlagen Bürobedarf > Präsentationsbedarf Bürobedarf > Präsentationsbedarf > Ausstellungstafeln @@ -573,8 +395,8 @@ Bürobedarf > Präsentationsbedarf > Ausstellungstafeln > Hartschaumstoffplatten Bürobedarf > Präsentationsbedarf > Ausstellungstafeln > Pinnwände Bürobedarf > Präsentationsbedarf > Ausstellungstafeln > Plakatkartons Bürobedarf > Präsentationsbedarf > Ausstellungstafeln > Trägerplatten -Bürobedarf > Präsentationsbedarf > Ausstellungstafeln > Zubehör für Ausstellungstafeln -Bürobedarf > Präsentationsbedarf > Ausstellungstafeln > Zubehör für Ausstellungstafeln > Dekorative Elemente für Ausstellungstafeln +Bürobedarf > Präsentationsbedarf > Ausstellungstafeln > Zubehör für Anschlagbretter +Bürobedarf > Präsentationsbedarf > Ausstellungstafeln > Zubehör für Anschlagbretter > Dekorative Elemente für Anschlagbretter Bürobedarf > Präsentationsbedarf > Dokumentenkameras Bürobedarf > Präsentationsbedarf > Flipchart-Blöcke Bürobedarf > Präsentationsbedarf > Flipcharts @@ -584,8 +406,10 @@ Bürobedarf > Präsentationsbedarf > Lesepulte Bürobedarf > Präsentationsbedarf > Präsentationsfernbedienungen Bürobedarf > Präsentationsbedarf > Schreibtafeln Bürobedarf > Präsentationsbedarf > Whiteboards +Bürobedarf > Schreibtischunterlagen Bürobedarf > Schreibwaren Bürobedarf > Schreibwaren > Büroklammern & Papierklammern +Bürobedarf > Schreibwaren > Büroklebeband Bürobedarf > Schreibwaren > Etiketten & Anhängerschilder Bürobedarf > Schreibwaren > Etiketten & Anhängerschilder > Adressaufkleber Bürobedarf > Schreibwaren > Etiketten & Anhängerschilder > Klebebänder für Etikettiergeräte @@ -595,18 +419,15 @@ Bürobedarf > Schreibwaren > Etiketten & Anhängerschilder > Versandanhänger Bürobedarf > Schreibwaren > Etiketten & Anhängerschilder > Versandaufkleber & -etiketten Bürobedarf > Schreibwaren > Gummibänder Bürobedarf > Schreibwaren > Heftklammern -Bürobedarf > Schreibwaren > Klebstoffe -Bürobedarf > Schreibwaren > Klebstoffe > Büroklebeband -Bürobedarf > Schreibwaren > Korrektur -Bürobedarf > Schreibwaren > Korrektur > Korrekturflüssigkeiten, Korrekturstifte & Korrekturbänder -Bürobedarf > Schreibwaren > Korrektur > Radiergummis +Bürobedarf > Schreibwaren > Klebepads +Bürobedarf > Schreibwaren > Korrekturflüssigkeiten, Korrekturstifte & Korrekturbänder +Bürobedarf > Schreibwaren > Laminierfolie & Schutzhüllen Bürobedarf > Schreibwaren > Musterklammern Bürobedarf > Schreibwaren > Papierprodukte Bürobedarf > Schreibwaren > Papierprodukte > Blanko-Ausweise Bürobedarf > Schreibwaren > Papierprodukte > Briefpapier Bürobedarf > Schreibwaren > Papierprodukte > Briefumschläge Bürobedarf > Schreibwaren > Papierprodukte > Drucker- & Kopierpapier -Bürobedarf > Schreibwaren > Papierprodukte > Drucker- & Kopierpapier > Fotodruckerpapier Bürobedarf > Schreibwaren > Papierprodukte > Feinkarton Bürobedarf > Schreibwaren > Papierprodukte > Geschäftsformulare & -belege Bürobedarf > Schreibwaren > Papierprodukte > Haftnotizen @@ -617,15 +438,13 @@ Bürobedarf > Schreibwaren > Papierprodukte > Papier für Hefter Bürobedarf > Schreibwaren > Papierprodukte > Postkarten Bürobedarf > Schreibwaren > Papierprodukte > Scheckformulare Bürobedarf > Schreibwaren > Papierprodukte > Visitenkarten +Bürobedarf > Schreibwaren > Radiergummis Bürobedarf > Schreibwaren > Reißzwecken & Pins -Bürobedarf > Schreibwaren > Stempelkissen & Tinten Bürobedarf > Versandbedarf -Bürobedarf > Versandbedarf > Briefwaagen Bürobedarf > Versandbedarf > Packband +Bürobedarf > Versandbedarf > Umzugs- & Versandkartons Bürobedarf > Versandbedarf > Verpackungsmaterialien -Bürobedarf > Versandbedarf > Versandschachteln Elektronik -Elektronik > 3D-Drucker Elektronik > Audio Elektronik > Audio > Audiokomponenten Elektronik > Audio > Audiokomponenten > AV-Receiver @@ -638,13 +457,9 @@ Elektronik > Audio > Audiokomponenten > Audioverstärker > Kopfhörerverstärker Elektronik > Audio > Audiokomponenten > Audioverstärker > Stereoverstärker Elektronik > Audio > Audiokomponenten > Channel Strips Elektronik > Audio > Audiokomponenten > DI-Boxen -Elektronik > Audio > Audiokomponenten > Kopfhörer +Elektronik > Audio > Audiokomponenten > Kopfhörer & Headsets Elektronik > Audio > Audiokomponenten > Lautsprecher -Elektronik > Audio > Audiokomponenten > Lautsprecher > Computerlautsprecher -Elektronik > Audio > Audiokomponenten > Lautsprecher > Studiomonitore -Elektronik > Audio > Audiokomponenten > Lautsprecher > Subwoofer Elektronik > Audio > Audiokomponenten > Mikrofone -Elektronik > Audio > Audiokomponenten > Mikrofone > Videokamera-Mikrofone Elektronik > Audio > Audiokomponenten > Signalprozessoren Elektronik > Audio > Audiokomponenten > Signalprozessoren > Effektprozessoren Elektronik > Audio > Audiokomponenten > Signalprozessoren > Equalizer @@ -653,6 +468,7 @@ Elektronik > Audio > Audiokomponenten > Signalprozessoren > Mikrofonvorverstärk Elektronik > Audio > Audiokomponenten > Signalprozessoren > Noise Gates & Kompressoren Elektronik > Audio > Audiokomponenten > Signalprozessoren > Phono-Vorverstärker Elektronik > Audio > Audiokomponenten > Signalprozessoren > Steuerungssysteme für PA-Anlagen +Elektronik > Audio > Audiokomponenten > Studio-Aufnahmesets Elektronik > Audio > Audioplayer & -rekorder Elektronik > Audio > Audioplayer & -rekorder > CD-Player & -Rekorder Elektronik > Audio > Audioplayer & -rekorder > Diktiergeräte @@ -665,262 +481,227 @@ Elektronik > Audio > Audioplayer & -rekorder > MP3-Player Elektronik > Audio > Audioplayer & -rekorder > Mehrspurrekorder Elektronik > Audio > Audioplayer & -rekorder > MiniDisc-Player Elektronik > Audio > Audioplayer & -rekorder > Plattenspieler -Elektronik > Audio > Audioplayer & -rekorder > Plattenspieler > DJ-Plattenspieler Elektronik > Audio > Audioplayer & -rekorder > Radios -Elektronik > Audio > Audioplayer & -rekorder > Radios > Satellitenradios -Elektronik > Audio > Audioplayer & -rekorder > Radios > WLAN-Radios Elektronik > Audio > Audioplayer & -rekorder > Stereoanlagen +Elektronik > Audio > Audioplayer & -rekorder > Tonbandgeräte Elektronik > Audio > Audiozubehör -Elektronik > Audio > Audiozubehör > Kopfhörerzubehör -Elektronik > Audio > Audiozubehör > Kopfhörerzubehör > In-Ear-Ohrhörer Polster -Elektronik > Audio > Audiozubehör > Kopfhörerzubehör > Kopfhörer-Polster +Elektronik > Audio > Audiozubehör > Audio- & Video-Receiver-Zubehör +Elektronik > Audio > Audiozubehör > Kopfhörer- & Headset-Zubehör +Elektronik > Audio > Audiozubehör > Kopfhörer- & Headset-Zubehör > Kopfhörer- & In-Ear-Ohrhörer-Polster Elektronik > Audio > Audiozubehör > Lautsprecherzubehör -Elektronik > Audio > Audiozubehör > Lautsprecherzubehör > Bespannstoff für Lautsprecher +Elektronik > Audio > Audiozubehör > Lautsprecherzubehör > Bass-Shaker +Elektronik > Audio > Audiozubehör > Lautsprecherzubehör > Lautsprecherkomponenten & -sets Elektronik > Audio > Audiozubehör > Lautsprecherzubehör > Lautsprecherständer +Elektronik > Audio > Audiozubehör > Lautsprecherzubehör > Lautsprechertaschen, -abdeckungen & -cases Elektronik > Audio > Audiozubehör > Lautsprecherzubehör > Taschen für Lautsprecherständer +Elektronik > Audio > Audiozubehör > Mikrofonständer Elektronik > Audio > Audiozubehör > Mikrofonzubehör -Elektronik > Audio > Audiozubehör > Mikrofonzubehör > Mikrofon-Windschutz -Elektronik > Audio > Audiozubehör > Mikrofonzubehör > Mikrofonständer Elektronik > Audio > Audiozubehör > Plattenspielerzubehör -Elektronik > Audio > Audiozubehör > Plattenspielerzubehör > Plattenspieler-Tonabnehmer & -Nadeln Elektronik > Audio > Audiozubehör > Satellitenradio-Zubehör Elektronik > Audio > Audiozubehör > Zubehör für Karaoke-Systeme Elektronik > Audio > Audiozubehör > Zubehör für Karaoke-Systeme > Karaoke-Speicherchips Elektronik > Audio > Audiozubehör > Zubehör für MP3-Player Elektronik > Audio > Audiozubehör > Zubehör für MP3-Player > MP3-Player-Taschen -Elektronik > Audio > Audiozubehör > Zubehör für MP3-Player > Zubehör für MP3-Player & Mobiltelefone +Elektronik > Audio > Audiozubehör > Zubehör für MP3-Player > Zubehörsets für MP3-Player & Mobiltelefone Elektronik > Audio > Beschallungsanlagen Elektronik > Audio > Bühnenausstattung Elektronik > Audio > Bühnenausstattung > Drahtlossender Elektronik > Audio > DJ- & Spezialaudiogeräte Elektronik > Audio > DJ- & Spezialaudiogeräte > DJ CD-Player Elektronik > Audio > DJ- & Spezialaudiogeräte > DJ Systeme -Elektronik > Audio > DJ- & Spezialaudiogeräte > Zubehör für DJ-Anlagen +Elektronik > Audio > Megafone Elektronik > Computer Elektronik > Computer > Barebone-Computer -Elektronik > Computer > Computerkomponenten -Elektronik > Computer > Computerkomponenten > Ausgabegeräte -Elektronik > Computer > Computerkomponenten > Blade-Server-Gehäuse -Elektronik > Computer > Computerkomponenten > Computer-Starter-Kits -Elektronik > Computer > Computerkomponenten > Computernetzteile -Elektronik > Computer > Computerkomponenten > Computerregale & -Befestigungssysteme -Elektronik > Computer > Computerkomponenten > Eingabegeräte -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Computer Mäuse & Trackballs -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Computertastaturen -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Digitale Stifte -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Fingerabdruck-Lesegeräte -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Gamecontroller -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Grafiktabletts -Elektronik > Computer > Computerkomponenten > Eingabegeräte > KVM-Switches -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Smartcard-Leser -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Speicherkartenlesegerät -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Strichcode-Scanner -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Tastatur- & Maussets -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Touchpads -Elektronik > Computer > Computerkomponenten > Eingabegeräte > Ziffernblöcke -Elektronik > Computer > Computerkomponenten > I/O-Karten -Elektronik > Computer > Computerkomponenten > I/O-Karten > FireWire-Karten -Elektronik > Computer > Computerkomponenten > I/O-Karten > Grafikkarten -Elektronik > Computer > Computerkomponenten > I/O-Karten > Parallele Karten -Elektronik > Computer > Computerkomponenten > I/O-Karten > Serielle Karten -Elektronik > Computer > Computerkomponenten > I/O-Karten > Soundkarten -Elektronik > Computer > Computerkomponenten > I/O-Karten > TV-Karten und -Adapter -Elektronik > Computer > Computerkomponenten > I/O-Karten > USB-Karten -Elektronik > Computer > Computerkomponenten > Kühlung von Computersystemen -Elektronik > Computer > Computerkomponenten > Laptopteile -Elektronik > Computer > Computerkomponenten > Laptopteile > Laptop-Ersatzbildschirme -Elektronik > Computer > Computerkomponenten > Laptopteile > Laptop-Ersatzkabel -Elektronik > Computer > Computerkomponenten > Laptopteile > Laptop-Ersatztastaturen -Elektronik > Computer > Computerkomponenten > Laptopteile > Laptop-Scharniere -Elektronik > Computer > Computerkomponenten > Laptopteile > Laptopgehäuse -Elektronik > Computer > Computerkomponenten > Motherboards -Elektronik > Computer > Computerkomponenten > PC-Gehäuse -Elektronik > Computer > Computerkomponenten > Prozessoren -Elektronik > Computer > Computerkomponenten > Speichergeräte -Elektronik > Computer > Computerkomponenten > Speichergeräte > Bandlaufwerke -Elektronik > Computer > Computerkomponenten > Speichergeräte > Datenspeicher-Duplizierer -Elektronik > Computer > Computerkomponenten > Speichergeräte > Datenspeicher-Duplizierer > CD-/DVD-Brenner -Elektronik > Computer > Computerkomponenten > Speichergeräte > Datenspeicher-Duplizierer > Festplatten-Duplizierer -Elektronik > Computer > Computerkomponenten > Speichergeräte > Datenspeicher-Duplizierer > USB-Laufwerk-Duplizierer -Elektronik > Computer > Computerkomponenten > Speichergeräte > Disk-Arrays -Elektronik > Computer > Computerkomponenten > Speichergeräte > Diskettenlaufwerke -Elektronik > Computer > Computerkomponenten > Speichergeräte > Festplatten -Elektronik > Computer > Computerkomponenten > Speichergeräte > Festplatten > Solid-State-Laufwerke -Elektronik > Computer > Computerkomponenten > Speichergeräte > Festplatten > XBox 360 Festplatten -Elektronik > Computer > Computerkomponenten > Speichergeräte > Netzwerkspeichersysteme -Elektronik > Computer > Computerkomponenten > Speichergeräte > Netzwerkspeichersysteme > NAS-Server -Elektronik > Computer > Computerkomponenten > Speichergeräte > Netzwerkspeichersysteme > Speichernetzwerke -Elektronik > Computer > Computerkomponenten > Speichergeräte > Optische Laufwerke -Elektronik > Computer > Computerkomponenten > Speichergeräte > Optische Laufwerke > CD-Laufwerke -Elektronik > Computer > Computerkomponenten > Speichergeräte > Optische Laufwerke > DVD-Laufwerke -Elektronik > Computer > Computerkomponenten > Speichergeräte > Optische Laufwerke > MO-Disketten -Elektronik > Computer > Computerkomponenten > Speichergeräte > Speicherlaufwerkzubehör -Elektronik > Computer > Computerkomponenten > Speichergeräte > Speicherlaufwerkzubehör > Externe Festplatten -Elektronik > Computer > Computerkomponenten > Speichergeräte > Speicherlaufwerkzubehör > Festplatten-Caddies -Elektronik > Computer > Computerkomponenten > Speichergeräte > Speicherlaufwerkzubehör > Festplatten-Dockingstationen -Elektronik > Computer > Computerkomponenten > Speichergeräte > Speicherlaufwerkzubehör > Festplatten-Transporttaschen -Elektronik > Computer > Computerkomponenten > Speichergeräte > Speicherlaufwerkzubehör > Festplattenhalterungen -Elektronik > Computer > Computerkomponenten > Speichergeräte > USB-Massenspeicher -Elektronik > Computer > Computerkomponenten > USB- & FireWire-Hubs -Elektronik > Computer > Computerkomponenten > Zubehör für Eingabegeräte -Elektronik > Computer > Computerkomponenten > Zubehör für Eingabegeräte > Grafiktablett-Zubehör -Elektronik > Computer > Computerkomponenten > Zubehör für Eingabegeräte > Halterung für Strichcodeleser -Elektronik > Computer > Computerkomponenten > Zubehör für Eingabegeräte > Zubehör für Gamecontroller -Elektronik > Computer > Computerkomponenten > Zubehör für Eingabegeräte > Zubehör für Gamecontroller > Joysticks & Tasten Elektronik > Computer > Computerserver -Elektronik > Computer > Computerzubehör -Elektronik > Computer > Computerzubehör > Ersatzspitzen für Stylus Pens -Elektronik > Computer > Computerzubehör > Keyboard- und Mausauflagen -Elektronik > Computer > Computerzubehör > Laptopzubehör -Elektronik > Computer > Computerzubehör > Laptopzubehör > Laptop-Dockingstationen -Elektronik > Computer > Computerzubehör > Monitorständer -Elektronik > Computer > Computerzubehör > Mousepads -Elektronik > Computer > Computerzubehör > Stylus Pens -Elektronik > Computer > Computerzubehör > Tablet-Computer-Zubehör -Elektronik > Computer > Computerzubehör > Tablet-Computer-Zubehör > Tablet-Computer-Ersatzteile -Elektronik > Computer > Computerzubehör > Tablet-Computer-Zubehör > Tablet-Computer-Ersatzteile > Digitalisierer -Elektronik > Computer > Computerzubehör > Tablet-Computer-Zubehör > Tablet-PC-Halter -Elektronik > Computer > Computerzubehör > Tastatur- & Maus-Handballenauflage -Elektronik > Computer > Computerzubehör > Zubehör für E-Book-Reader & Handheld-Computer -Elektronik > Computer > Computerzubehör > Zubehör für E-Book-Reader & Handheld-Computer > PDA-Zubehör -Elektronik > Computer > Computerzubehör > Zubehör für E-Book-Reader & Handheld-Computer > PDA-Zubehör > PDA-Taschen -Elektronik > Computer > Computerzubehör > Zubehör für E-Book-Reader & Handheld-Computer > Zubehör für E-Book-Reader -Elektronik > Computer > Computerzubehör > Zubehör für E-Book-Reader & Handheld-Computer > Zubehör für E-Book-Reader > E-Book-Reader-Taschen +Elektronik > Computer > Datenbrillen Elektronik > Computer > Desktop-Computer Elektronik > Computer > E-Book-Reader & Handheld-Computer -Elektronik > Computer > E-Book-Reader & Handheld-Computer > Datenerfassungsterminals +Elektronik > Computer > E-Book-Reader & Handheld-Computer > Datenerfassungsgeräte Elektronik > Computer > E-Book-Reader & Handheld-Computer > E-Book-Reader Elektronik > Computer > E-Book-Reader & Handheld-Computer > PDAs Elektronik > Computer > Interaktive Kioske Elektronik > Computer > Laptops -Elektronik > Computer > Laptops > Netbooks +Elektronik > Computer > Multitouch-Tischcomputer Elektronik > Computer > Tablet-PCs +Elektronik > Computer > Thin Clients & Zero Clients Elektronik > Drucken, Kopieren, Scannen & Faxen -Elektronik > Drucken, Kopieren, Scannen & Faxen > Drucker & Kopierer -Elektronik > Drucken, Kopieren, Scannen & Faxen > Drucker & Kopierer > Drucker -Elektronik > Drucken, Kopieren, Scannen & Faxen > Drucker & Kopierer > Drucker > Fotodrucker -Elektronik > Drucken, Kopieren, Scannen & Faxen > Drucker & Kopierer > Drucker > Laserdrucker -Elektronik > Drucken, Kopieren, Scannen & Faxen > Drucker & Kopierer > Drucker > Matrixdrucker -Elektronik > Drucken, Kopieren, Scannen & Faxen > Drucker & Kopierer > Drucker > Tintenstrahldrucker -Elektronik > Drucken, Kopieren, Scannen & Faxen > Drucker & Kopierer > Kopierer -Elektronik > Drucken, Kopieren, Scannen & Faxen > Faxgeräte +Elektronik > Drucken, Kopieren, Scannen & Faxen > 3D-Drucker +Elektronik > Drucken, Kopieren, Scannen & Faxen > 3D-Drucker-Zubehör +Elektronik > Drucken, Kopieren, Scannen & Faxen > Drucker, Kopierer & Faxgeräte Elektronik > Drucken, Kopieren, Scannen & Faxen > Scanner -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Drucker-Duplexeinheiten -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Drucker-Papierfächer -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Drucker-Verbrauchsmaterial -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Drucker-Verbrauchsmaterial > Drucker-Farbbänder -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Drucker-Verbrauchsmaterial > Druckerfilter -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Drucker-Verbrauchsmaterial > Druckköpfe -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Drucker-Verbrauchsmaterial > Toner & Tintenpatronen -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Drucker-Verbrauchsmaterial > Toner- & Inkjet-Kartuschen -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Drucker-Verbrauchsmaterial > Toner- & Inkjet-Kartuschen > Druckertrommelsets -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Drucker-Verbrauchsmaterial > Toner- & Inkjet-Kartuschen > Ölwalzen -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Druckerspeicher -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Druckertische -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Druckerzubehör > Druckerwartungssets -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Kopiererzubehör -Elektronik > Drucker-, Kopierer-, Scanner- & Faxzubehör > Scannerzubehör +Elektronik > Drucken, Kopieren, Scannen & Faxen > Scannerzubehör +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Drucker-Duplexeinheiten +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Drucker-Verbrauchsmaterial +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Drucker-Verbrauchsmaterial > Drucker-Farbbänder +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Drucker-Verbrauchsmaterial > Druckerfilter +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Drucker-Verbrauchsmaterial > Druckertrommeln & Sets +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Drucker-Verbrauchsmaterial > Druckerwartungssets +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Drucker-Verbrauchsmaterial > Druckköpfe +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Drucker-Verbrauchsmaterial > Toner & Tintenpatronen +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Drucker-Verbrauchsmaterial > Toner- & Inkjet-Kartuschen +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Druckerspeicher +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Druckertische +Elektronik > Drucken, Kopieren, Scannen & Faxen > Zubehör für Drucker, Kopierer & Faxgeräte > Ersatzteile für Drucker, Kopierer & Faxgeräte Elektronik > Elektrische Instrumente Seefahrt +Elektronik > Elektrische Instrumente Seefahrt > Audio- und Videoemfpänger Elektronik > Elektrische Instrumente Seefahrt > Fischfinder +Elektronik > Elektrische Instrumente Seefahrt > Marine-Lautsprecher Elektronik > Elektrische Instrumente Seefahrt > Schiffsradar Elektronik > Elektrische Instrumente Seefahrt > Seefunkgeräte Elektronik > Elektrische Instrumente Seefahrt > Seekartenplotter & GPS -Elektronik > Elektromotoren -Elektronik > Elektronische Bauelemente -Elektronik > Elektronische Bauelemente > Decoder & Encoder für Schaltkreise -Elektronik > Elektronische Bauelemente > Elektronische Filter -Elektronik > Elektronische Bauelemente > Halbleiter -Elektronik > Elektronische Bauelemente > Halbleiter > Dioden -Elektronik > Elektronische Bauelemente > Halbleiter > Integrierte Schaltkreise & Chips -Elektronik > Elektronische Bauelemente > Halbleiter > Integrierte Schaltkreise & Chips > Operationsverstärker -Elektronik > Elektronische Bauelemente > Halbleiter > Integrierte Schaltkreise & Chips > Spannungsregler (ICs) -Elektronik > Elektronische Bauelemente > Halbleiter > Mikrocontroller -Elektronik > Elektronische Bauelemente > Halbleiter > Transistoren -Elektronik > Elektronische Bauelemente > Leiterplatten -Elektronik > Elektronische Bauelemente > Leiterplatten > Entwicklungsboard -Elektronik > Elektronische Bauelemente > Optoelektronik -Elektronik > Elektronische Bauelemente > Optoelektronik > Laserdioden -Elektronik > Elektronische Bauelemente > Passive elektronische Bauelemente -Elektronik > Elektronische Bauelemente > Passive elektronische Bauelemente > Induktoren -Elektronik > Elektronische Bauelemente > Passive elektronische Bauelemente > Kondensatoren -Elektronik > Elektronische Bauelemente > Passive elektronische Bauelemente > Oszillatoren -Elektronik > Elektronische Bauelemente > Passive elektronische Bauelemente > Widerstände -Elektronik > Elektronische Bauelemente > Passive elektronische Bauelemente > Widerstände > Potentiometer -Elektronik > Elektronische Bauelemente > Platinenentwicklung -Elektronik > Elektronische Bauelemente > Platinenentwicklung > Steckplatinen +Elektronik > Elektronische Leiterplatten & Bauteile +Elektronik > Elektronische Leiterplatten & Bauteile > Decoder & Encoder für Schaltkreise +Elektronik > Elektronische Leiterplatten & Bauteile > Elektronische Filter +Elektronik > Elektronische Leiterplatten & Bauteile > Halbleiter +Elektronik > Elektronische Leiterplatten & Bauteile > Halbleiter > Dioden +Elektronik > Elektronische Leiterplatten & Bauteile > Halbleiter > Integrierte Schaltkreise & Chips +Elektronik > Elektronische Leiterplatten & Bauteile > Halbleiter > Mikrocontroller +Elektronik > Elektronische Leiterplatten & Bauteile > Halbleiter > Transistoren +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Computer-Leiterplatten +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Computer-Leiterplatten > Computer-Inverterplatine +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Computer-Leiterplatten > Festplatten-Leiterplatten +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Computer-Leiterplatten > Motherboards +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Entwicklungsboard +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Kamera-Leiterplatten +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Leiterplatten für Drucker, Kopierer & Faxgeräte +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Leiterplatten für Fernsehgeräte +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Leiterplatten für Fitnessgeräte +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Leiterplatten für Haushaltsgeräte +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Leiterplatten für Pools und Spas +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplatten > Leiterplatten für Scanner +Elektronik > Elektronische Leiterplatten & Bauteile > Leiterplattenzubehör +Elektronik > Elektronische Leiterplatten & Bauteile > Passive elektronische Bauelemente +Elektronik > Elektronische Leiterplatten & Bauteile > Passive elektronische Bauelemente > Induktoren +Elektronik > Elektronische Leiterplatten & Bauteile > Passive elektronische Bauelemente > Kondensatoren +Elektronik > Elektronische Leiterplatten & Bauteile > Passive elektronische Bauelemente > Oszillatoren +Elektronik > Elektronische Leiterplatten & Bauteile > Passive elektronische Bauelemente > Widerstände +Elektronik > Elektronische Leiterplatten & Bauteile > Platinenentwicklung +Elektronik > Elektronische Leiterplatten & Bauteile > Platinenentwicklung > Steckplatinen Elektronik > Elektronisches Zubehör Elektronik > Elektronisches Zubehör > Adapter Elektronik > Elektronisches Zubehör > Adapter > Audio/Video-Adapter Elektronik > Elektronisches Zubehör > Adapter > Speicheradapter -Elektronik > Elektronisches Zubehör > Adapter > Storage Adapter Elektronik > Elektronisches Zubehör > Adapter > USB-Adapter Elektronik > Elektronisches Zubehör > Antennen -Elektronik > Elektronisches Zubehör > Antennen > Funkantennen -Elektronik > Elektronisches Zubehör > Antennen > Kommunikationsantennen -Elektronik > Elektronisches Zubehör > Antennen > TV-Antennen Elektronik > Elektronisches Zubehör > Antennenzubehör Elektronik > Elektronisches Zubehör > Antennenzubehör > Antennenhalterungen & -befestigungen Elektronik > Elektronisches Zubehör > Antennenzubehör > Dreheinrichtungen Elektronik > Elektronisches Zubehör > Antennenzubehör > Satelliten-LNBs +Elektronik > Elektronisches Zubehör > Audio- und Video-Splitter & -Switches +Elektronik > Elektronisches Zubehör > Audio- und Video-Splitter & -Switches > DVI-Splitter und -Switches +Elektronik > Elektronisches Zubehör > Audio- und Video-Splitter & -Switches > HDMI-Splitter & -Switches +Elektronik > Elektronisches Zubehör > Audio- und Video-Splitter & -Switches > VGA-Splitter und -Switches +Elektronik > Elektronisches Zubehör > Computerkomponenten +Elektronik > Elektronisches Zubehör > Computerkomponenten > Blade-Server-Gehäuse +Elektronik > Elektronisches Zubehör > Computerkomponenten > Computer-Backplates & I/O Shields +Elektronik > Elektronisches Zubehör > Computerkomponenten > Computer-Kühlkomponenten +Elektronik > Elektronisches Zubehör > Computerkomponenten > Computer-Starter-Kits +Elektronik > Elektronisches Zubehör > Computerkomponenten > Computer-Steckkarten +Elektronik > Elektronisches Zubehör > Computerkomponenten > Computer-Steckkarten > Computer-Steckkarten & -Adapter +Elektronik > Elektronisches Zubehör > Computerkomponenten > Computer-Steckkarten > Grafikkarten & Videoadapter +Elektronik > Elektronisches Zubehör > Computerkomponenten > Computer-Steckkarten > Riser-Karten +Elektronik > Elektronisches Zubehör > Computerkomponenten > Computer-Steckkarten > Soundkarten +Elektronik > Elektronisches Zubehör > Computerkomponenten > Computer-Steckkarten > TV-Karten und -Adapter +Elektronik > Elektronisches Zubehör > Computerkomponenten > Computernetzteile +Elektronik > Elektronisches Zubehör > Computerkomponenten > Computerregale & -Befestigungssysteme +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Computer Mäuse & Trackballs +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Digitale Stifte +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Eingabegeräte zur Gestensteuerung +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Elektronische Kartenlesegeräte +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Fingerabdruck-Lesegeräte +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Gamecontroller +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Grafiktabletts +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > KVM-Switches +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Speicherkartenlesegerät +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Strichcode-Scanner +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Tastaturen +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Touchpads +Elektronik > Elektronisches Zubehör > Computerkomponenten > Eingabegeräte > Ziffernblöcke +Elektronik > Elektronisches Zubehör > Computerkomponenten > Ersatzteile für E-Book-Reader +Elektronik > Elektronisches Zubehör > Computerkomponenten > Ersatzteile für E-Book-Reader > Ersatzbildschirme für E-Book-Reader +Elektronik > Elektronisches Zubehör > Computerkomponenten > Laptopteile +Elektronik > Elektronisches Zubehör > Computerkomponenten > Laptopteile > Laptop-Bildschirmdigitalisierer +Elektronik > Elektronisches Zubehör > Computerkomponenten > Laptopteile > Laptop-Ersatzbildschirme +Elektronik > Elektronisches Zubehör > Computerkomponenten > Laptopteile > Laptop-Ersatzkabel +Elektronik > Elektronisches Zubehör > Computerkomponenten > Laptopteile > Laptop-Ersatzlautsprecher +Elektronik > Elektronisches Zubehör > Computerkomponenten > Laptopteile > Laptop-Ersatztastaturen +Elektronik > Elektronisches Zubehör > Computerkomponenten > Laptopteile > Laptop-Scharniere +Elektronik > Elektronisches Zubehör > Computerkomponenten > Laptopteile > Laptopgehäuse +Elektronik > Elektronisches Zubehör > Computerkomponenten > PC-Gehäuse +Elektronik > Elektronisches Zubehör > Computerkomponenten > Prozessoren +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Bandlaufwerke +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Datenspeicher-Duplizierer +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Datenspeicher-Duplizierer > CD-/DVD-Brenner +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Datenspeicher-Duplizierer > Festplatten-Duplizierer +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Datenspeicher-Duplizierer > USB-Laufwerk-Duplizierer +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Disk-Arrays +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Diskettenlaufwerke +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Festplatten +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Festplattenzubehör +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Festplattenzubehör > Festplatten-Dockingstationen +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Festplattenzubehör > Festplatten-Transporttaschen +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Festplattenzubehör > Festplattengehäuse & -steckplätze +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Netzwerkspeichersysteme +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > Optische Laufwerke +Elektronik > Elektronisches Zubehör > Computerkomponenten > Speichergeräte > USB-Massenspeicher +Elektronik > Elektronisches Zubehör > Computerkomponenten > Tablet-PC-Ersatzteile +Elektronik > Elektronisches Zubehör > Computerkomponenten > Tablet-PC-Ersatzteile > Tablet-Bildschirme und Bildschirm-Digitizer +Elektronik > Elektronisches Zubehör > Computerkomponenten > Tablet-PC-Ersatzteile > Tablet-Ersatzlautsprecher +Elektronik > Elektronisches Zubehör > Computerkomponenten > Tablet-PC-Ersatzteile > Tablet-Gehäuse & -Rahmen +Elektronik > Elektronisches Zubehör > Computerkomponenten > USB- & FireWire-Hubs +Elektronik > Elektronisches Zubehör > Computerkomponenten > Zubehör für Eingabegeräte +Elektronik > Elektronisches Zubehör > Computerkomponenten > Zubehör für Eingabegeräte > Halterung für Strichcodeleser +Elektronik > Elektronisches Zubehör > Computerkomponenten > Zubehör für Eingabegeräte > Mäuse und Trackball-Zubehör +Elektronik > Elektronisches Zubehör > Computerkomponenten > Zubehör für Eingabegeräte > Tastaturtasten & -kappen +Elektronik > Elektronisches Zubehör > Computerkomponenten > Zubehör für Eingabegeräte > Zubehör für Gamecontroller +Elektronik > Elektronisches Zubehör > Computerzubehör +Elektronik > Elektronisches Zubehör > Computerzubehör > Computer-Monitorständer +Elektronik > Elektronisches Zubehör > Computerzubehör > Computer-Schutzhüllen +Elektronik > Elektronisches Zubehör > Computerzubehör > Computerzubehör-Sets +Elektronik > Elektronisches Zubehör > Computerzubehör > Ersatzspitzen für Stylus Pens +Elektronik > Elektronisches Zubehör > Computerzubehör > Laptop-Dockingstationen +Elektronik > Elektronisches Zubehör > Computerzubehör > Mousepads +Elektronik > Elektronisches Zubehör > Computerzubehör > Stylus Pens +Elektronik > Elektronisches Zubehör > Computerzubehör > Ständer für Tablet-PCs +Elektronik > Elektronisches Zubehör > Computerzubehör > Tastatur- & Maus-Handballenauflage +Elektronik > Elektronisches Zubehör > Computerzubehör > Tastaturablagen +Elektronik > Elektronisches Zubehör > Computerzubehör > Zubehör für E-Book-Reader & Handheld-Computer +Elektronik > Elektronisches Zubehör > Computerzubehör > Zubehör für E-Book-Reader & Handheld-Computer > PDA-Zubehör +Elektronik > Elektronisches Zubehör > Computerzubehör > Zubehör für E-Book-Reader & Handheld-Computer > PDA-Zubehör > PDA-Taschen +Elektronik > Elektronisches Zubehör > Computerzubehör > Zubehör für E-Book-Reader & Handheld-Computer > Zubehör für E-Book-Reader +Elektronik > Elektronisches Zubehör > Computerzubehör > Zubehör für E-Book-Reader & Handheld-Computer > Zubehör für E-Book-Reader > E-Book-Reader-Taschen +Elektronik > Elektronisches Zubehör > Drei- und Einbeinstative für Mobiltelefone und Tablets Elektronik > Elektronisches Zubehör > Elektronik-Folien & -Überzüge Elektronik > Elektronisches Zubehör > Elektronik-Folien & -Überzüge > Blickschutzfilter Elektronik > Elektronisches Zubehör > Elektronik-Folien & -Überzüge > Display-Schutzfolien Elektronik > Elektronisches Zubehör > Elektronik-Folien & -Überzüge > Elektronik-Sticker & -Aufkleber -Elektronik > Elektronisches Zubehör > Elektronik-Folien & -Überzüge > Elektronik-Sticker & -Aufkleber > Aufkleber für Spielkonsolen -Elektronik > Elektronisches Zubehör > Elektronik-Folien & -Überzüge > Elektronik-Sticker & -Aufkleber > Handy-Aufkleber -Elektronik > Elektronisches Zubehör > Elektronik-Folien & -Überzüge > Elektronik-Sticker & -Aufkleber > Laptop-Sticker & -Aufkleber -Elektronik > Elektronisches Zubehör > Elektronik-Folien & -Überzüge > Elektronik-Sticker & -Aufkleber > MP3-Player-Sticker & -Aufkleber -Elektronik > Elektronisches Zubehör > Elektronik-Folien & -Überzüge > Elektronik-Sticker & -Aufkleber > Sticker für Computertastaturen -Elektronik > Elektronisches Zubehör > Elektronik-Folien & -Überzüge > Elektronik-Sticker & -Aufkleber > Tablet-Computer-Sticker & -Aufkleber Elektronik > Elektronisches Zubehör > Elektronik-Folien & -Überzüge > Tastaturschutz Elektronik > Elektronisches Zubehör > Elektronikreiniger -Elektronik > Elektronisches Zubehör > Elektronikreiniger > Audio- & Videoreiniger -Elektronik > Elektronisches Zubehör > Elektronikreiniger > Bildschirmreiniger -Elektronik > Elektronisches Zubehör > Elektronikreiniger > Kamerareiniger Elektronik > Elektronisches Zubehör > Fernbedienungen Elektronik > Elektronisches Zubehör > Kabel Elektronik > Elektronisches Zubehör > Kabel > Audio- & Videokabel -Elektronik > Elektronisches Zubehör > Kabel > Audio- & Videokabel > DVI-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Audio- & Videokabel > HDMI-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Audio- & Videokabel > Lautsprecherkabel -Elektronik > Elektronisches Zubehör > Kabel > Audio- & Videokabel > SCART-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Cinch-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Datenübertragungskabel -Elektronik > Elektronisches Zubehör > Kabel > Datenübertragungskabel > Firewire-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Datenübertragungskabel > USB-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Datenübertragungskabel > iOS-Kabel +Elektronik > Elektronisches Zubehör > Kabel > Datenübertragungs- & Speicherkabel Elektronik > Elektronisches Zubehör > Kabel > KVM-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Koaxialkabel Elektronik > Elektronisches Zubehör > Kabel > Netzwerkkabel -Elektronik > Elektronisches Zubehör > Kabel > Netzwerkkabel > Ethernet-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Netzwerkkabel > Serial-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Optische Kabel -Elektronik > Elektronisches Zubehör > Kabel > Speicherkabel -Elektronik > Elektronisches Zubehör > Kabel > Speicherkabel > IDE-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Speicherkabel > SAS-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Speicherkabel > SATA-Kabel -Elektronik > Elektronisches Zubehör > Kabel > Speicherkabel > SCSI-Kabel Elektronik > Elektronisches Zubehör > Kabel > System- & Netzkabel Elektronik > Elektronisches Zubehör > Kabel > Telefonkabel Elektronik > Elektronisches Zubehör > Kabel-Management Elektronik > Elektronisches Zubehör > Kabel-Management > Aderhülsen & Kabelmuffen Elektronik > Elektronisches Zubehör > Kabel-Management > Kabelbinder +Elektronik > Elektronisches Zubehör > Kabel-Management > Kabelbinderpistolen +Elektronik > Elektronisches Zubehör > Kabel-Management > Kabelmarkierungen Elektronik > Elektronisches Zubehör > Kabel-Management > Kabelschellen Elektronik > Elektronisches Zubehör > Kabel-Management > Kabelträger Elektronik > Elektronisches Zubehör > Kabel-Management > Schalttafeln -Elektronik > Elektronisches Zubehör > Leere Medien -Elektronik > Elektronisches Zubehör > Leere Medien > Blu-ray Rohlinge -Elektronik > Elektronisches Zubehör > Leere Medien > CD Rohlinge -Elektronik > Elektronisches Zubehör > Leere Medien > Computerbänder -Elektronik > Elektronisches Zubehör > Leere Medien > Computerdisks -Elektronik > Elektronisches Zubehör > Leere Medien > Computerdisks > Disketten -Elektronik > Elektronisches Zubehör > Leere Medien > DVD Rohlinge -Elektronik > Elektronisches Zubehör > Leere Medien > MiniDiscs -Elektronik > Elektronisches Zubehör > Leere Medien > Unbespielte Kassetten -Elektronik > Elektronisches Zubehör > Leere Medien > Unbespielte Kassetten > Mikrokassetten -Elektronik > Elektronisches Zubehör > Leere Medien > Unbespielte Videokassetten +Elektronik > Elektronisches Zubehör > Leermedien +Elektronik > Elektronisches Zubehör > Signalstörer +Elektronik > Elektronisches Zubehör > Signalstörer > GPS-Störsender +Elektronik > Elektronisches Zubehör > Signalstörer > Mobiltelefon-Störsender +Elektronik > Elektronisches Zubehör > Signalstörer > Radarstörer Elektronik > Elektronisches Zubehör > Signalverstärker Elektronik > Elektronisches Zubehör > Speicher Elektronik > Elektronisches Zubehör > Speicher > Cache-Speicher @@ -934,45 +715,34 @@ Elektronik > Elektronisches Zubehör > Speicherkartenzubehör > Speicherkartenet Elektronik > Elektronisches Zubehör > Stromversorgung Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > Akkus & Batterien für schnurlose Telefone -Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > Batterien für E-Book-Lesegeräte -Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > Batterien für Tablet-Computer +Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > Akkus für Tablet-Computer +Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > Batterien für E-Book-Reader +Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > Batterien für Videospielkonsolen & Game-Controller Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > Handy-Akkus Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > Kameraakkus & -batterien Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > Laptopakkus +Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > MP3-Player-Batterien Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > Mehrzweckbatterien Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > PDA-Batterien Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > USV-Batterien Elektronik > Elektronisches Zubehör > Stromversorgung > Akkus & Batterien > Videokameraakkus & -batterien Elektronik > Elektronisches Zubehör > Stromversorgung > Batteriezubehör Elektronik > Elektronisches Zubehör > Stromversorgung > Batteriezubehör > Akkuladegeräte für Kameras +Elektronik > Elektronisches Zubehör > Stromversorgung > Batteriezubehör > Batteriehalter Elektronik > Elektronisches Zubehör > Stromversorgung > Batteriezubehör > Batterieladegeräte +Elektronik > Elektronisches Zubehör > Stromversorgung > Batteriezubehör > Laderegler +Elektronik > Elektronisches Zubehör > Stromversorgung > Batteriezubehör > Universal-Batterieprüfgeräte Elektronik > Elektronisches Zubehör > Stromversorgung > Brennstoffzellen -Elektronik > Elektronisches Zubehör > Stromversorgung > Ladegeräte -Elektronik > Elektronisches Zubehör > Stromversorgung > Ladegeräte > E-Book-Reader Ladegeräte -Elektronik > Elektronisches Zubehör > Stromversorgung > Ladegeräte > MP3-Player-Ladegeräte -Elektronik > Elektronisches Zubehör > Stromversorgung > Ladegeräte > Mobiltelefonladegeräte -Elektronik > Elektronisches Zubehör > Stromversorgung > Ladegeräte > PDA-Ladegeräte -Elektronik > Elektronisches Zubehör > Stromversorgung > Ladegeräte > Solar-Ladegeräte -Elektronik > Elektronisches Zubehör > Stromversorgung > Ladegeräte > Tablet-Computer-Ladegeräte +Elektronik > Elektronisches Zubehör > Stromversorgung > Gehäuse für Stromversorgungsgeräte Elektronik > Elektronisches Zubehör > Stromversorgung > Leistungsregler -Elektronik > Elektronisches Zubehör > Stromversorgung > Netzadapter -Elektronik > Elektronisches Zubehör > Stromversorgung > Netzadapter > Laptop-Netzadapter -Elektronik > Elektronisches Zubehör > Stromversorgung > Netzadapter > Reiseadapter -Elektronik > Elektronisches Zubehör > Stromversorgung > Netzadapter > Schaltnetzteile -Elektronik > Elektronisches Zubehör > Stromversorgung > Netzadapter > Wechselrichter -Elektronik > Elektronisches Zubehör > Stromversorgung > Netzteilzubehör -Elektronik > Elektronisches Zubehör > Stromversorgung > Power-Injektoren & -Splitter -Elektronik > Elektronisches Zubehör > Stromversorgung > Spannungsstabilisator -Elektronik > Elektronisches Zubehör > Stromversorgung > Spannungswandler +Elektronik > Elektronisches Zubehör > Stromversorgung > Netzteil- & Ladegerätezubehör +Elektronik > Elektronisches Zubehör > Stromversorgung > Netzteile & Ladegeräte +Elektronik > Elektronisches Zubehör > Stromversorgung > Reiseadapter Elektronik > Elektronisches Zubehör > Stromversorgung > Steckdosenleisten & Überspannungsschutz -Elektronik > Elektronisches Zubehör > Stromversorgung > Stromkasten -Elektronik > Elektronisches Zubehör > Stromversorgung > Unterbrechungsfreie Stromversorgung -Elektronik > Elektronisches Zubehör > Stromversorgung > Zubehör für Unterbrechungsfreie Stromversorgung +Elektronik > Elektronisches Zubehör > Stromversorgung > USV +Elektronik > Elektronisches Zubehör > Stromversorgung > Zubehör für USV Elektronik > Elektronisches Zubehör > Stromversorgung > Überspannungsschutzgeräte -Elektronik > GPS -Elektronik > GPS > Auto-GPS-Systeme -Elektronik > GPS > Flugnavigation -Elektronik > GPS > Sport-GPS-Geräte +Elektronik > GPS-Navigationssysteme Elektronik > GPS-Tracker Elektronik > GPS-Zubehör Elektronik > GPS-Zubehör > GPS-Halterungen @@ -980,30 +750,26 @@ Elektronik > GPS-Zubehör > GPS-Taschen Elektronik > Geräte zur Mauterhebung Elektronik > Kommunikationsgeräte Elektronik > Kommunikationsgeräte > Anrufbeantworter -Elektronik > Kommunikationsgeräte > Anrufbeantworter > Telefonwarteschleifensysteme Elektronik > Kommunikationsgeräte > Anruferkennung Elektronik > Kommunikationsgeräte > Funkgeräte Elektronik > Kommunikationsgeräte > Funkgeräte > CB-Funkgeräte Elektronik > Kommunikationsgeräte > Funkgeräte > Funkscanner Elektronik > Kommunikationsgeräte > Funkgeräte > Funksprechgeräte Elektronik > Kommunikationsgeräte > Funkzubehör -Elektronik > Kommunikationsgeräte > Headsets Elektronik > Kommunikationsgeräte > Pager Elektronik > Kommunikationsgeräte > Sprechanlagen Elektronik > Kommunikationsgeräte > Sprechanlagen-Zubehör Elektronik > Kommunikationsgeräte > Telefone Elektronik > Kommunikationsgeräte > Telefone > Konferenztelefone Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefone -Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefone > Handyuhren -Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefone > Multifunktionstelefone -Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefone > Smartphones Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefonzubehör +Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefonzubehör > Auswurfhilfen für SIM-Karten Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefonzubehör > Ersatzteile für Mobiltelefone -Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefonzubehör > Ersatzteile für Mobiltelefone > Digitalisierer für Mobiltelefone -Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefonzubehör > Handy-SIM-Karten +Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefonzubehör > Handy-Prepaid- & -SIM-Karten Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefonzubehör > Handycharms & -anhänger Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefonzubehör > Mobiltelefonhalterungen & -ständer Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefonzubehör > Mobiltelefontaschen +Elektronik > Kommunikationsgeräte > Telefone > Mobiltelefonzubehör > Zubehör für Mobiltelefon-Kameras Elektronik > Kommunikationsgeräte > Telefone > Satellitentelefone Elektronik > Kommunikationsgeräte > Telefone > Schnurlostelefone Elektronik > Kommunikationsgeräte > Telefone > Schnurtelefone @@ -1011,26 +777,16 @@ Elektronik > Kommunikationsgeräte > Telefone > Telefonzubehör Elektronik > Kommunikationsgeräte > Telefone > Telefonzubehör > Telefonkarten Elektronik > Kommunikationsgeräte > Videokonferenzsysteme Elektronik > Komponenten -Elektronik > Komponenten > Automatisierungskomponenten -Elektronik > Komponenten > Automatisierungskomponenten > Antriebssysteme mit variabler Frequenz & einstellbarer Drehzahl -Elektronik > Komponenten > Automatisierungskomponenten > Speicherprogrammierbare Steuerungen Elektronik > Komponenten > Beschleunigungssensoren Elektronik > Komponenten > Modulatoren -Elektronik > Komponenten > Modulatoren > HF-Modulatoren Elektronik > Komponenten > Splitter -Elektronik > Komponenten > Splitter > HF-Splitter -Elektronik > Komponenten > Splitter > UHF-Splitter -Elektronik > Komponenten > Transformatoren -Elektronik > Komponenten > Transformatoren > Baluns -Elektronik > Komponenten > Verbinder -Elektronik > Komponenten > Verbinder > Keystone-Module -Elektronik > Komponenten > Verbinder > RCA-Klinkenstecker +Elektronik > Komponenten > Steckverbinder Elektronik > Komponenten > Wandler +Elektronik > Komponenten > Wandler > Audiowandler Elektronik > Komponenten > Wandler > Signalwandler Elektronik > Netzwerktechnik Elektronik > Netzwerktechnik > Bridges & Router Elektronik > Netzwerktechnik > Bridges & Router > Netzwerk-Bridges -Elektronik > Netzwerktechnik > Bridges & Router > Netzwerk-Bridges > WLAN-Bridges Elektronik > Netzwerktechnik > Bridges & Router > VoIP-Gateways & -Router Elektronik > Netzwerktechnik > Bridges & Router > Wireless Access Points Elektronik > Netzwerktechnik > Bridges & Router > Wireless Router @@ -1040,12 +796,13 @@ Elektronik > Netzwerktechnik > Konzentratoren & Multiplexer Elektronik > Netzwerktechnik > Modems Elektronik > Netzwerktechnik > Modemzubehör Elektronik > Netzwerktechnik > Netzwerkkarten -Elektronik > Netzwerktechnik > Netzwerkkarten > Xbox 360-Netzwerkkarten Elektronik > Netzwerktechnik > Netzwerksicherheitssysteme & Firewalls -Elektronik > Netzwerktechnik > Powerline-Netzwerkadapter +Elektronik > Netzwerktechnik > PoE-Adapter Elektronik > Netzwerktechnik > Repeater & Sende/-Empfangsgeräte -Elektronik > Plug & Play-TV-Spiele +Elektronik > Radargeschwindigkeitsmesser +Elektronik > Radarwarner Elektronik > Spielautomatenbedarf +Elektronik > Spielautomatenbedarf > Basketballspiele Elektronik > Spielautomatenbedarf > Flipper Elektronik > Spielautomatenbedarf > Flipper-Zubehör Elektronik > Spielautomatenbedarf > Skee-Ball-Maschinen @@ -1054,10 +811,6 @@ Elektronik > Spielautomatenbedarf > Zubehör für Spielautomaten Elektronik > Video Elektronik > Video > Computermonitore Elektronik > Video > Fernseher -Elektronik > Video > Fernseher > Flachbildschirmfernseher -Elektronik > Video > Fernseher > Projektions-TV-Geräte -Elektronik > Video > Fernseher > Röhrenfernsehgeräte -Elektronik > Video > Fernseher > Tragbare TV-Geräte Elektronik > Video > Projektoren Elektronik > Video > Projektoren > Diaprojektoren Elektronik > Video > Projektoren > Multimedia-Projektoren @@ -1069,15 +822,12 @@ Elektronik > Video > Video-Multiplexer Elektronik > Video > Videoabspielgeräte & -rekorder Elektronik > Video > Videoabspielgeräte & -rekorder > DVD- & Blu-ray-Player Elektronik > Video > Videoabspielgeräte & -rekorder > DVD-Rekorder -Elektronik > Video > Videoabspielgeräte & -rekorder > DVD-Rekorder > Blu-ray-Rekorder -Elektronik > Video > Videoabspielgeräte & -rekorder > DVD-Rekorder > HD-DVD-Rekorder Elektronik > Video > Videoabspielgeräte & -rekorder > Digitale Videorekorder -Elektronik > Video > Videoabspielgeräte & -rekorder > Home-Mediaplayer +Elektronik > Video > Videoabspielgeräte & -rekorder > Netzwerk-Mediaplayer Elektronik > Video > Videoabspielgeräte & -rekorder > Videorekorder Elektronik > Video > Videobearbeitungshardware & Videoproduktionsausrüstung Elektronik > Video > Videoserver Elektronik > Video > Videotransmitter -Elektronik > Video > Videotransmitter > Videosender Elektronik > Video > Videozubehör Elektronik > Video > Videozubehör > 3D-Brillen Elektronik > Video > Videozubehör > Computerbildschirmzubehör @@ -1093,374 +843,204 @@ Elektronik > Video > Videozubehör > TV-Zubehör Elektronik > Video > Videozubehör > TV-Zubehör > Ersatzlampen für Fernseher Elektronik > Video > Videozubehör > TV-Zubehör > TV- & Monitorhalterungen Elektronik > Video > Videozubehör > TV-Zubehör > TV-Konverter & Receiver +Elektronik > Video > Videozubehör > TV-Zubehör > TV-Lautsprecher Elektronik > Videospielkonsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Dreamcast-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > GameCube-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Jaguar-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Neo Geo-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Nintendo 64-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Nintendo Entertainment System-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > PC Engine-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > PlayStation 2-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > PlayStation 3-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > PlayStation-Originalkonsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Sega Genesis-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Sega Master System-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Super Nintendo-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Wii U -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Wii-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Xbox 360-Konsolen -Elektronik > Videospielkonsolen > Heim-Spielkonsolen > Xbox-Originalkonsolen -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > Atari Lynx-Konsolen -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > Game Gear-Konsolen -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > Gameboy-Konsolen (alle) -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > Gameboy-Konsolen (alle) > Game Boy Advance-Konsolen -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > Gameboy-Konsolen (alle) > Gameboy-Konsolen -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > LeapFrog -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > N-Gage-Konsolen -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > Neo Geo Pocket-Konsolen -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > Nintendo 3DS -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > Nintendo DS-Konsolen -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > PlayStation Portable-Konsolen -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > PlayStation Vita -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > Retrogen -Elektronik > Videospielkonsolen > Tragbare Spielkonsolen > WonderSwan Elektronik > Zubehör für Videospielkonsolen Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Dreamcast-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > GameCube-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Jaguar-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Neo Geo-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Nintendo 64-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Nintendo Entertainment System-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > PC Engine-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > PlayStation-Zubehör (alle) -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > PlayStation-Zubehör (alle) > PlayStation 2-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > PlayStation-Zubehör (alle) > PlayStation 3-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > PlayStation-Zubehör (alle) > PlayStation-Originalzubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Sega Genesis-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Sega Master System-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Super Nintendo-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Wii U-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Wii-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Xbox-Zubehör (komplett) -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Xbox-Zubehör (komplett) > Xbox 360-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für Spielkonsolen > Xbox-Zubehör (komplett) > Xbox-Originalzubehör Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > Atari Lynx-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > Game Gear-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > Gameboy-Zubehör (komplett) -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > Gameboy-Zubehör (komplett) > Game Boy Advance-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > Gameboy-Zubehör (komplett) > Game Boy-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > N-Gage-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > Neo Geo Pocket-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > Nintendo 3DS-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > Nintendo DS-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > PlayStation Vita-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > Playstation Portable-Zubehör -Elektronik > Zubehör für Videospielkonsolen > Zubehör für mobile Spielkonsolen > WonderSwan-Zubehör Fahrzeuge & Teile +Fahrzeuge & Teile > Fahrzeuge +Fahrzeuge & Teile > Fahrzeuge > Kraftfahrzeuge +Fahrzeuge & Teile > Fahrzeuge > Kraftfahrzeuge > Autos, LKWs & Lieferwagen +Fahrzeuge & Teile > Fahrzeuge > Kraftfahrzeuge > Gelände- & Allradfahrzeuge +Fahrzeuge & Teile > Fahrzeuge > Kraftfahrzeuge > Gelände- & Allradfahrzeuge > Go-Karts +Fahrzeuge & Teile > Fahrzeuge > Kraftfahrzeuge > Gelände- & Allradfahrzeuge > Quads +Fahrzeuge & Teile > Fahrzeuge > Kraftfahrzeuge > Golfwagen +Fahrzeuge & Teile > Fahrzeuge > Kraftfahrzeuge > Motorräder & -roller +Fahrzeuge & Teile > Fahrzeuge > Kraftfahrzeuge > Schneemobile +Fahrzeuge & Teile > Fahrzeuge > Kraftfahrzeuge > Wohnmobile +Fahrzeuge & Teile > Fahrzeuge > Luftfahrzeuge +Fahrzeuge & Teile > Fahrzeuge > Wasserfahrzeuge +Fahrzeuge & Teile > Fahrzeuge > Wasserfahrzeuge > Jetski +Fahrzeuge & Teile > Fahrzeuge > Wasserfahrzeuge > Motorboote +Fahrzeuge & Teile > Fahrzeuge > Wasserfahrzeuge > Segelboote +Fahrzeuge & Teile > Fahrzeuge > Wasserfahrzeuge > Yachten Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi > Adapterkassetten -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi > Auto-Hifi-CD-Wechsler -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi > Auto-Hifi-Empfänger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi > Auto-Hifi-Empfänger > Car-Hifi-Satellitenradios -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi > Auto-Hifi-Endstufen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi > Auto-Hifi-Equalizer & -Frequenzweichen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi > Auto-Hifi-Kassettenspieler -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi > Auto-Hifi-Lautsprecher -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi > Auto-Hifi-Subwoofer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi > Freisprechanlagen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi- & -Videokabel -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Hifi-Überspannungsschutz -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Multimedia -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Multimedia > Auto-DVD-Player -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Auto-Hifi & -Multimedia > Auto-Multimedia > Auto-Monitore +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > ATV- & UTV-Schutzausrüstung +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > ATV- & UTV-Schutzausrüstung > ATV- & UTV-Griffpolster +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Auto-Alarmanlagen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Fernbediente schlüssellose Systeme +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Kfz-Türschlösser +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Kfz-Türschlösser > Kfz-Türschlosssicherungen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Kfz-Türschlösser > Türknöpfe +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Kfz-Türschlösser > Türschlösser & Schließsysteme +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Kupplungsschlösser +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Lenkradschlösser +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Motorrad-Schlösser & -Alarmanlagen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Radkrallen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Wegfahrsperren +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Auto-Alarmanlagen & Schließsysteme > Zubehör für Auto-Alarmanlagen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Kfz-Sicherheitsausrüstung +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Kfz-Sicherheitsausrüstung > Airbagteile +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Kfz-Sicherheitsausrüstung > Fensternetze +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Kfz-Sicherheitsausrüstung > Gurtpolster +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Kfz-Sicherheitsausrüstung > Gurtschlösser +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Kfz-Sicherheitsausrüstung > Kfz-Warnflaggen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Kfz-Sicherheitsausrüstung > Sicherheitsgurtbänder +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Kfz-Sicherheitsausrüstung > Sicherheitsgurte +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Kfz-Sicherheitsausrüstung > Unterlegkeile +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Kfz-Sicherheitsausrüstung > Warnleuchten +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Kfz-Sicherheitsausrüstung > Überrollkäfige & -bügel +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Motorrad-Schutzausrüstung +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Motorrad-Schutzausrüstung > Motorrad-Genickschutz +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Motorrad-Schutzausrüstung > Motorrad-Handprotektoren +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Motorrad-Schutzausrüstung > Motorrad-Nierengurte +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Motorrad-Schutzausrüstung > Motorrad: Brust- und Rückenprotektoren +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Motorrad-Schutzausrüstung > Motorrad: Ellbogen- und Handgelenkschützer +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Motorrad-Schutzausrüstung > Motorrad: Knie- & Schienbeinschützer +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Motorrad-Schutzausrüstung > Motorradbrillen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Motorrad-Schutzausrüstung > Motorradhandschuhe +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Motorrad-Schutzausrüstung > Motorradhelme +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Fahrzeugsicherheit > Motorrad-Schutzausrüstung > Motorradhelmersatzteile & -zubehör Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Flugzeugteile & -Zubehör -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Abschlepphaken für Kfz -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Einstiegsleisten -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Ersatzteile für Pkw-Anhänger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Fahrzeugkühlergrills -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Fußrasten für Motorräder -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Hardtops -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Karosseriebausätze -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kennzeichen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Anhängerkupplung -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Diffusoren -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträger > Autogepäckträger für Angelruten -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträger > Autogepäckträger für Kanus & Kajaks -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträger > Autogepäckträger für Motorräder & Motorroller -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträger > Autogepäckträger für Skier & Snowboards -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträger > Autogepäckträger für Surfbretter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträger > Autogewehrhalter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträger > Fahrradträger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträger > Gepäckträger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträger > Grundgestelle für Autogepäckträger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträgerzubehör -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträgerzubehör > Fahrradträgerzubehör -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträgerzubehör > Fahrradträgerzubehör > Adapter für Fahrradgabelhalter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträgerzubehör > Fahrradträgerzubehör > Befestigungsriemen für Fahrräder -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Gepäckträgerzubehör > Fahrradträgerzubehör > Fahrradrahmenhalter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Ladenetze -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Türen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Kfz-Warnflaggen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Ladeflächen für Pickups -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Ladeflächenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Ladeflächenteile > Gestänge für Lkw-Betten -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Motorradkotflügel -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Motorradlenker- & Griffe -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Motorradsitze -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Motorradverkleidungen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Schalthebel für Motorräder -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Schiebedächer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Spoiler -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Stoßstangen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Stoßstangenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Teile für Kfz-Türen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Verdecke -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Winden -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Außenteile > Windenzubehör -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Armaturenbrett-Zubehör -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Autofußmatten -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Autositzauflagen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Autositze -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Dachhimmel -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Elektrisch verstellbare Sitze -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Elektrische Kfz-Sicherungen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Fahrzeugteppiche -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Getränkehalter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Kfz-Armstützen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Kfz-Rückenlehnen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Sonnenblenden -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Innenausstattung > Zigarettenanzünderstecker -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Schließsysteme -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Schließsysteme > Autoalarmanlagen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Schließsysteme > Autokameras -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Schließsysteme > Fernbediente schlüssellose Systeme -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Schließsysteme > Freilaufnaben -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Schließsysteme > Kindersicherungen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Schließsysteme > Lenkradschlösser -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Schließsysteme > Motorrad-Schlösser & -Alarmanlagen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Schließsysteme > Wegfahrsperren -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Schließsysteme > Zentralverriegelungen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Schließsysteme > Zubehör für Auto-Alarmanlagen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Sicherheitsausrüstung -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Sicherheitsausrüstung > Einparkhilfen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Elektronik +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Elektronik > A/V-Player & im Armaturenbrett integrierte Systeme für Autos +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Elektronik > Audioverstärker für Autos +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Elektronik > Equalizer & Frequenzweichen für Autos +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Elektronik > Freisprechanlagen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Elektronik > Halterungen für Kfz-Videomonitore +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Elektronik > Kassettenadapter +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Elektronik > Kassettenspieler für Autos +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Elektronik > Lautsprecher für Autos +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Elektronik > Parkkameras für Autos +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Elektronik > Subwoofer für Autos Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Achsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Antriebswellen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Antriebswellenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Differentiale -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Getriebe -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Getrieberäder -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Getriebeteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Kipphebel -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Kipphebelteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Kipphebelteile > Ventildeckel -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Kugellager -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Kupplungsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Pleuelstangen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Antriebsteile > Verteilergetriebeteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Aufhängungsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Aufhängungsteile > Stoßdämpfer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Brems- & Kupplungshebel +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Abschleppzubehör +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Aufhängungsteile für Autos +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Bodenbeläge & Polster +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Getriebe- & Antriebsteile Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Auspuff -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Auspuff > Abgasrückführungsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Auspuff > Auspuffe & Auspuffteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Auspuff > Auspuffkrümmer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Auspuff > Auspufftöpfe & Teile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Auspuff > Katalysatoren -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Auspuff > Sauerstoffsensoren & Teile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Batterien -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Batterien > Hybridbatterien Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Beleuchtung -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Beleuchtung > Kfz-Fahrtrichtungsanzeiger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Beleuchtung > Kfz-Glühbirnen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Beleuchtung > Kfz-Innenbeleuchtung -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Beleuchtung > Kfz-Nebelscheinwerfer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Beleuchtung > Kfz-Rück- & Bremslichter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Beleuchtung > Kfz-Rückfahrlichter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Beleuchtung > Kfz-Scheinwerfer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Beleuchtung > Kfz-Seitenleuchten -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Beleuchtung > Kfz-Standlichter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Beleuchtung > Scheinwerferabdeckungen für Fahrzeuge Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Bremsen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Bremsen > Bremsenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Bremsen > Feststellbremsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Bremsen > Teile für Kfz-Handbremsen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Keilriemen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Elektroniksysteme +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Innenausstattung Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Klimatisierung -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Klimatisierung > Kfz-Enteisungszubehör -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Klimatisierung > Kfz-Gebläseteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Klimatisierung > Kfz-Heizungsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Klimatisierung > Kfz-Klimaanlageteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Klimatisierung > Kfz-Temperaturregler +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motoren Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Anlasser -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Anlasserteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Generatorenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Heizsysteme -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Heizsystemteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Kolben -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Kolbenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Kolbenteile > Kfz-Kolbenbolzen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Kolbenteile > Kfz-Kolbenringe -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Kompressorteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Kurbelwellen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Kurbelwellenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Lichtmaschinen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Lichtmaschinenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Motordichtsätze -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Motorsteuerungsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Motorsteuerungsteile > Steuerketten -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Nockenwellen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Nockenwellenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Schwungräder & -Schwungradteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Vakuumpumpenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Verdichter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Verdichterteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Vergaser -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Vergaserteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Zylinderköpfe & -teile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Zündungsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Zündungsteile > Kfz-Verteilerteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Zündungsteile > Teile für Hochspannungskondensatorzündungen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Zündungsteile > Zündkerzen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Motorteile > Kfz-Zündungsteile > Zündspulen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Felgen & Reifen für Kfz Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Felgen & Reifen für Kfz > Autofelgen & -räder Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Felgen & Reifen für Kfz > Felgen & Räder für ATV & UTV -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Felgen & Reifen für Kfz > Felgen & Räder für ATV & UTV > Felgen & Räder für Quads/ATVs -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Felgen & Reifen für Kfz > Felgen & Räder für ATV & UTV > Felgen & Räder für UTV-Fahrzeuge Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Felgen & Reifen für Kfz > Motorradfelgen & -räder Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Kfz-Radteile Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Kfz-Reifen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Kfz-Reifen > Autoreifen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Kfz-Reifen > Motorradreifen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Kfz-Reifen > Reifen für ATV & UTV -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Kfz-Reifen > Reifen für ATV & UTV > ATV-Reifen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Kfz-Reifen > Reifen für ATV & UTV > Reifen für UTV-Fahrzeuge Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Kfz-Reifenzubehör -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Räder > Kfz-Reifenzubehör > Schneeketten +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Sensoren & -Messinstrumente +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Sitze Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Spiegel -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Spiegel > Anhänger- & Wohnwagenspiegel -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Spiegel > Fahrzeug-Außenspiegel -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Spiegel > Kfz-Rückspiegel -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Ventile & -teile +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kfz-Steuerelemente Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kraftstoffanlagen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kraftstoffanlagen > Benzinpumpen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kraftstoffanlagen > Einspritzdüsen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kraftstoffanlagen > Einspritzteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kraftstoffanlagen > Kraftstoffleitungen & Teile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kraftstoffanlagen > Kraftstoffpumpenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kraftstoffanlagen > Kraftstoffsiebe & -filter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kraftstoffanlagen > Lachgaseinspritzsysteme -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kraftstoffanlagen > Luftmassenmesser -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kraftstoffanlagen > Treibstofftanks -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Kraftstoffanlagen > Treibstofftankteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Lenkungsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Lenkungsteile > Achsschenkelbolzen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Lenkungsteile > Lenkradkontrolltasten -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Lenkungsteile > Lenkradkontrolltasten > Frontscheinwerferregelungselemente -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Lenkungsteile > Lenkradkontrolltasten > Kfz-Lichtdimmer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Lenkungsteile > Lenkradkontrolltasten > Tempomat-Teile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Lenkungsteile > Lenkradkontrolltasten > Wischergeschwindigkeitssteuerung -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Lenkungsteile > Lenkräder -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Lenkungsteile > Servolenkungsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Luftansaugungsteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Luftansaugungsteile > Kfz-Luftpumpen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Luftansaugungsteile > Kompressorenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Luftansaugungsteile > Luftfilter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Luftansaugungsteile > Luftverteiler -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Pedale für Kfz -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Scheibenwischer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Teile für Kfz-Batterien +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Rahmen- & Karosserieteile Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Teile für Kfz-Fenster -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Teile für Kfz-Fenster > Fenster- & Windschutzscheibenglas -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Teile für Kfz-Fenster > Teile für elektrische Fensterheber -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Ölkreislauf -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Ölkreislauf > Ölfilter -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Ölkreislauf > Ölpumpen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Ölkreislauf > Ölpumpenteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Ölkreislauf > Ölwannen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Teile > Teile für Kfz-Ölkreislauf Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Aufladegeräte für Fahrzeugbatterien Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugdiagnosegeräte +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen > Fahrzeug-Hardtops +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen > Fahrzeugabdeckplanen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen > Fahrzeugabdeckplanen > Bootsabdeckplanen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen > Fahrzeugabdeckplanen > Golfcart-Abdeckplanen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen > Fahrzeugabdeckplanen > Kfz-Abdeckplanen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen > Fahrzeugabdeckplanen > Motorradabdeckplanen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen > Fahrzeugabdeckplanen > Unterstände & Abdeckungen für Wohnmobile +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen > Fahrzeugverdecke +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen > Golfcart-Gehäuse +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen > Ladeflächenabdeckungen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugabdeckungen > Windschutzscheiben-Abdeckungen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeuglack +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeuglack > Bremssattellack +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeuglack > Rahmen- & Karosserielack Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreinigung Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreinigung > Autowaschbürsten Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreinigung > Autowaschmittel +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreinigung > Einspritzanlagenreinigungs-Sets Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreinigung > Fahrzeug-Glasreiniger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreinigung > Fahrzeug-Polsterreiniger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreinigung > Fahrzeug-Sprühreiniger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreinigung > Fahrzeug-Teppichreiniger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreinigung > Reifenreinigungs- & Glanzmittel +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreinigung > Kfz-Teppich- & Polsterreiniger Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreinigung > Wachse, Polituren & Schutzmittel für Fahrzeuge +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreparatur- & Spezialwerkzeuge +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreparatur- & Spezialwerkzeuge > Aufladegeräte für Fahrzeugbatterien +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreparatur- & Spezialwerkzeuge > Fahrzeugbatterie-Prüfgeräte +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreparatur- & Spezialwerkzeuge > Fahrzeugdiagnosegeräte +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreparatur- & Spezialwerkzeuge > Karosseriedichtmasse +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreparatur- & Spezialwerkzeuge > Kfz-Bremsreparatur-Sets +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreparatur- & Spezialwerkzeuge > Kfz-Starthilfen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreparatur- & Spezialwerkzeuge > Kfz-Überbrückungskabel +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreparatur- & Spezialwerkzeuge > Werkzeuge für Kupplungsausrichtung & -ausbau +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreparatur- & Spezialwerkzeuge > Werkzeuge für Reifenreparatur & -wechsel +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Fahrzeugreparatur- & Spezialwerkzeuge > Windschutzscheibenreparatursets Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Antennenbälle +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Abdeckungen für Anhängerkupplungen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Autofolien -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Autoscheibenfolien -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Autositzbezüge +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Fahrzeug-Außendesigns +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Fahrzeug-Dekosets Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Fahrzeug-Embleme +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Kennzeichen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Kennzeichenhalterungen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Kennzeichenrahmen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Kfz-Antennenbälle +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Kfz-Armaturenbrett-Zubehör Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Kfz-Aufkleber Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Kfz-Fahnen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Kfz-Kennzeichenabdeckungen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Kfz-Lufterfrischer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Magneten -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Rückenlehnentaschen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Lenkradbezüge +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Magnete +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Rückspiegel-Deko +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Schalthebelverkleidung +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Dekor > Schaltknäufe Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Benzin- & Ölzusätze Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Bremsflüssigkeit -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Flüssigkeit für Servolenkungen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Flüssigkeit für hydraulische Kupplungen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Frostschutzmittel Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Getriebeöl Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Kfz-Schmierstoffe Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Kühlsystemadditive -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Motorenöl Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Motorreiniger +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Motoröl +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Servolenkflüssigkeit Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Tankreiniger Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kfz-Flüssigkeiten > Wischwasser -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Reifenbedarf -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Reifenbedarf > Radkappen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Reifenbedarf > Radventilkappen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Reifenbedarf > Reifenabdeckungen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Reifenbedarf > Reifendrucksensoren & -messer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Reifenbedarf > Reifenstifte -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Starthilfe -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Windschutzscheibenreparatursets -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Überbrückungskabel -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Überwachungstechnik -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Überwachungstechnik > Benzinuhren -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Überwachungstechnik > Geschwindigkeitsanzeiger -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Überwachungstechnik > Kfz-Bordcomputer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Überwachungstechnik > Kilometerzähler -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Überwachungstechnik > Motorsteuergerätteile -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Überwachungstechnik > Tachometer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorrad-Schutzausrüstung -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorrad-Schutzausrüstung > Motorrad-Genickschutz -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorrad-Schutzausrüstung > Motorrad-Nierengurte -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorrad-Schutzausrüstung > Motorrad: Brust- und Rückenprotektoren -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorrad-Schutzausrüstung > Motorrad: Ellbogen- und Handgelenkschützer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorrad-Schutzausrüstung > Motorrad: Knie- & Schienbeinschützer -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorrad-Schutzausrüstung > Motorradbrillen -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorrad-Schutzausrüstung > Motorradhandschuhe -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorrad-Schutzausrüstung > Motorradhelme -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorrad-Schutzausrüstung > Motorradhelmersatzteile & -zubehör -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorrad-Schutzausrüstung > Motorradhelmersatzteile & -zubehör > Motorradhelm-Visiere -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Motorradzubehör > Motorradtaschen & -koffer +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Kfz-Wartung & -Pflege > Kraftstoffkanister +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Boxen & Sortiersysteme für Ladeflächen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Bügel & Haken für Kopfstützen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Anhänger +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Anhänger > Bootsanhänger +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Anhänger > Kleinanhänger & Transportanhänger +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Anhänger > Pferde- & Viehanhänger +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Anhänger > Wohnwagen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträger +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträger > Autogepäckträger für Angelruten +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträger > Autogepäckträger für Kanus & Kajaks +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträger > Autogepäckträger für Motorräder & Motorroller +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträger > Autogepäckträger für Skier & Snowboards +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträger > Autogepäckträger für Surfbretter +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträger > Autogewehrhalter +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträger > Fahrradträger +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträger > Gepäckträger +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträger > Grundgestelle für Autogepäckträger +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträgerzubehör +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträgerzubehör > Fahrradträgerzubehör +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Gepäckträgerzubehör > Ski- & Snowboardträger-Zubehör +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Kfz-Ladenetze +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Laderampen +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Motorradtaschen & -koffer +Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Stauraum- & Transportsysteme > Utensilientaschen fürs Auto Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zubehör Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zubehör > Anlegen & Ankern Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zubehör > Anlegen & Ankern > Anker @@ -1493,7 +1073,6 @@ Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zube Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zubehör > Wasserfahrzeugmotorteile > Wasserfahrzeugpropeller Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zubehör > Wasserfahrzeugmotorteile > Wasserfahrzeugvergaser & Teile Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zubehör > Wasserfahrzeugpflege -Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zubehör > Wasserfahrzeugpflege > Wasserfahrzeugabdeckungen Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zubehör > Wasserfahrzeugpflege > Wasserfahrzeugpolituren Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zubehör > Wasserfahrzeugpflege > Wasserfahrzeugreiniger Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zubehör > Wasserfahrzeugsteuerung @@ -1502,7 +1081,6 @@ Fahrzeuge & Teile > Fahrzeugersatzteile & -zubehör > Wasserfahrzeugteile & Zube Für Erwachsene Für Erwachsene > Erotik Für Erwachsene > Erotik > Erotikbücher -Für Erwachsene > Erotik > Erotikbücher > Erotik-Comics Für Erwachsene > Erotik > Erotische DVDs & Videos Für Erwachsene > Erotik > Erotische Kleidung Für Erwachsene > Erotik > Erotische Lebensmittel & Esswaren @@ -1510,71 +1088,101 @@ Für Erwachsene > Erotik > Erotische Spiele Für Erwachsene > Erotik > Erotische Zeitschriften Für Erwachsene > Erotik > Pole Dance-Tanzstangenkits Für Erwachsene > Erotik > Sexspielzeug +Für Erwachsene > Waffen +Für Erwachsene > Waffen > Elektroschockpistolen & Taser-Waffen +Für Erwachsene > Waffen > Fechtwaffen +Für Erwachsene > Waffen > Hieb- und Stichwaffen +Für Erwachsene > Waffen > Kampfmesser +Für Erwachsene > Waffen > Nunchakus +Für Erwachsene > Waffen > Peitschen +Für Erwachsene > Waffen > Schlagringe +Für Erwachsene > Waffen > Schlagstockwaffen +Für Erwachsene > Waffen > Schusswaffen +Für Erwachsene > Waffen > Schusswaffenzubehör +Für Erwachsene > Waffen > Schusswaffenzubehör > Material & Ausrüstung zum Nachladen von Schusswaffen +Für Erwachsene > Waffen > Schusswaffenzubehör > Material & Ausrüstung zum Nachladen von Schusswaffen > Ladepressen +Für Erwachsene > Waffen > Schusswaffenzubehör > Munition +Für Erwachsene > Waffen > Schusswaffenzubehör > Munitionskisten & -halter +Für Erwachsene > Waffen > Schusswaffenzubehör > Picatinny-Schienen +Für Erwachsene > Waffen > Schusswaffenzubehör > Pistolentaschen +Für Erwachsene > Waffen > Schusswaffenzubehör > Schusswaffengriffe +Für Erwachsene > Waffen > Schusswaffenzubehör > Schusswaffengurte +Für Erwachsene > Waffen > Schusswaffenzubehör > Schusswaffenreinigung +Für Erwachsene > Waffen > Schusswaffenzubehör > Schusswaffenreinigung > Schusswaffen-Reinigungsmittel +Für Erwachsene > Waffen > Schusswaffenzubehör > Schusswaffenreinigung > Schusswaffen-Reinigungspatches +Für Erwachsene > Waffen > Schusswaffenzubehör > Schusswaffenreinigung > Schusswaffen-Reinigungstücher +Für Erwachsene > Waffen > Schusswaffenzubehör > Waffenkoffer und -taschen +Für Erwachsene > Waffen > Schusswaffenzubehör > Waffenleuchten +Für Erwachsene > Waffen > Speerwaffen +Für Erwachsene > Waffen > Tränengas & Pfefferspray +Für Erwachsene > Waffen > Wurfsterne Gesundheit & Schönheit Gesundheit & Schönheit > Gesundheitspflege Gesundheit & Schönheit > Gesundheitspflege > Akupunktur Gesundheit & Schönheit > Gesundheitspflege > Akupunktur > Akupunkturmodelle Gesundheit & Schönheit > Gesundheitspflege > Akupunktur > Akupunkturnadeln -Gesundheit & Schönheit > Gesundheitspflege > Arthritisbehandlung +Gesundheit & Schönheit > Gesundheitspflege > Amputationsstrümpfe Gesundheit & Schönheit > Gesundheitspflege > Beatmungsmedizin +Gesundheit & Schönheit > Gesundheitspflege > Beatmungsmedizin > CPAP-Geräte +Gesundheit & Schönheit > Gesundheitspflege > Beatmungsmedizin > CPAP-Masken +Gesundheit & Schönheit > Gesundheitspflege > Beatmungsmedizin > Dampfinhalatoren Gesundheit & Schönheit > Gesundheitspflege > Beatmungsmedizin > Inhalatoren +Gesundheit & Schönheit > Gesundheitspflege > Beatmungsmedizin > Sauerstoffbehälter Gesundheit & Schönheit > Gesundheitspflege > Behälter für Verhütungsmittel Gesundheit & Schönheit > Gesundheitspflege > Bettpfannen -Gesundheit & Schönheit > Gesundheitspflege > Blutgruppentests -Gesundheit & Schönheit > Gesundheitspflege > CPAP-Ausrüstung -Gesundheit & Schönheit > Gesundheitspflege > CPAP-Ausrüstung > CPAP-Geräte -Gesundheit & Schönheit > Gesundheitspflege > CPAP-Ausrüstung > CPAP-Masken -Gesundheit & Schönheit > Gesundheitspflege > Diabetikerbedarf -Gesundheit & Schönheit > Gesundheitspflege > Drogentests +Gesundheit & Schönheit > Gesundheitspflege > Ergo- & Physiotherapeutische Hilfsmittel +Gesundheit & Schönheit > Gesundheitspflege > Ergo- & Physiotherapeutische Hilfsmittel > Elektrische Muskelstimulatoren +Gesundheit & Schönheit > Gesundheitspflege > Ergo- & Physiotherapeutische Hilfsmittel > Therapieschaukeln Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe > Antiseptika & Reinigung Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe > Augenwaschbedarf -Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe > Behandlung von Ausschlägen Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe > Erste-Hilfe-Koffer Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe > Medizinisches Klebeband & Verbandsmaterial Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe > Schutzhüllen für Gipsverbände Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe > Warm & Kalt Therapie Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe > Warm & Kalt Therapie > Eisanwendungen +Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe > Warm & Kalt Therapie > Wärmecremes Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe > Warm & Kalt Therapie > Wärmflaschen & Heizkissen -Gesundheit & Schönheit > Gesundheitspflege > Erste Hilfe > Wärmecremes Gesundheit & Schönheit > Gesundheitspflege > Fitness & Ernährung -Gesundheit & Schönheit > Gesundheitspflege > Fitness & Ernährung > Diätpräparate Gesundheit & Schönheit > Gesundheitspflege > Fitness & Ernährung > Energie-Gel & -Nahrung -Gesundheit & Schönheit > Gesundheitspflege > Fitness & Ernährung > Ergänzung zur künstlichen Ernährung +Gesundheit & Schönheit > Gesundheitspflege > Fitness & Ernährung > Flüssignahrung zur künstlichen Ernährung Gesundheit & Schönheit > Gesundheitspflege > Fitness & Ernährung > Mahlzeitenersatz-Getränke -Gesundheit & Schönheit > Gesundheitspflege > Fitness & Ernährung > Proteinpulver Gesundheit & Schönheit > Gesundheitspflege > Fitness & Ernährung > Pürierte Lebensmittel Gesundheit & Schönheit > Gesundheitspflege > Fitness & Ernährung > Sportriegel -Gesundheit & Schönheit > Gesundheitspflege > Fitness & Ernährung > Traditionelle chinesische Tonika Gesundheit & Schönheit > Gesundheitspflege > Fitness & Ernährung > Vitamine & Nahrungsergänzungsmittel -Gesundheit & Schönheit > Gesundheitspflege > HIV-Tests Gesundheit & Schönheit > Gesundheitspflege > Hausnotrufgeräte -Gesundheit & Schönheit > Gesundheitspflege > Hörgeräte Gesundheit & Schönheit > Gesundheitspflege > Hörhilfen Gesundheit & Schönheit > Gesundheitspflege > Inkontinenzartikel +Gesundheit & Schönheit > Gesundheitspflege > Kondome +Gesundheit & Schönheit > Gesundheitspflege > Kontaktgels & -lotionen +Gesundheit & Schönheit > Gesundheitspflege > Lichttherapielampen +Gesundheit & Schönheit > Gesundheitspflege > Medikamente & Arzneimittel Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte +Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Aktivitätsmonitore Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Alkoholmessgeräte Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Blutdruckmessgeräte Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Blutzuckermessgeräte Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Cholesterinmessgeräte Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Fieberthermometer -Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Fruchtbarkeitscomputer -Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Herzfrequenzmessgeräte +Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Fruchtbarkeitsmonitore & Ovulationstests Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Körperfettanalyse Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Personenwaagen Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Pränatale Herzfrequenzmesser Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Pulsoximeter -Gesundheit & Schönheit > Gesundheitspflege > Medizinische Messgeräte > Schrittzähler +Gesundheit & Schönheit > Gesundheitspflege > Medizinische Tests +Gesundheit & Schönheit > Gesundheitspflege > Medizinische Tests > Allergietest-Sets +Gesundheit & Schönheit > Gesundheitspflege > Medizinische Tests > Blutgruppentests +Gesundheit & Schönheit > Gesundheitspflege > Medizinische Tests > Drogentests +Gesundheit & Schönheit > Gesundheitspflege > Medizinische Tests > HIV-Tests +Gesundheit & Schönheit > Gesundheitspflege > Medizinische Tests > Harnwegsinfektionstests +Gesundheit & Schönheit > Gesundheitspflege > Medizinische Tests > Schwangerschaftstests Gesundheit & Schönheit > Gesundheitspflege > Medizinisches Gleitgel Gesundheit & Schönheit > Gesundheitspflege > Notfall-Armband -Gesundheit & Schönheit > Gesundheitspflege > Orthopädische Hilfsmittel -Gesundheit & Schönheit > Gesundheitspflege > Orthopädische Hilfsmittel > Handtrainer -Gesundheit & Schönheit > Gesundheitspflege > Rauchabgewöhnung -Gesundheit & Schönheit > Gesundheitspflege > Schwangerschaftstests +Gesundheit & Schönheit > Gesundheitspflege > Pillendosen +Gesundheit & Schönheit > Gesundheitspflege > Probenbehälter Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Behindertengerechte Möbel & Vorrichtungen Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Behindertengerechte Möbel & Vorrichtungen > Duschbänke & -sitze -Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Behindertenzubehör Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Gehhilfen Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Gehhilfen > Gehstöcke Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Gehhilfen > Krücken @@ -1583,58 +1191,59 @@ Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Ge Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Zubehör & Bedarf zur Barrierefreiheit Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Zubehör & Bedarf zur Barrierefreiheit > Elektromobile Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Zubehör & Bedarf zur Barrierefreiheit > Rollstühle -Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Zubehör & Bedarf zur Barrierefreiheit > Rollstühle > Elektrorollstühle -Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Zubehör & Bedarf zur Barrierefreiheit > Rollstühle > Standardrollstühle Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Zubehör & Bedarf zur Barrierefreiheit > Treppenlifte +Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Zubehör & Bedarf zur Barrierefreiheit > Umlagerungsbretter & -tücher +Gesundheit & Schönheit > Gesundheitspflege > Senioren- & Behindertenbedarf > Zubehör für Behindertengerechte Möbel & Vorrichtungen +Gesundheit & Schönheit > Gesundheitspflege > Spermizide Gesundheit & Schönheit > Gesundheitspflege > Stützen & Bandagen -Gesundheit & Schönheit > Gesundheitspflege > Verhütungsmittel -Gesundheit & Schönheit > Gesundheitspflege > Verhütungsmittel > Kondome -Gesundheit & Schönheit > Gesundheitspflege > Verhütungsmittel > Spermizide Gesundheit & Schönheit > Gesundheitspflege > Zubehör für biometrische Messgeräte Gesundheit & Schönheit > Gesundheitspflege > Zubehör für biometrische Messgeräte > Blutzuckermessung Gesundheit & Schönheit > Gesundheitspflege > Zubehör für biometrische Messgeräte > Blutzuckermessung > Blutzucker-Kontrolllösung Gesundheit & Schönheit > Gesundheitspflege > Zubehör für biometrische Messgeräte > Blutzuckermessung > Blutzuckerteststreifen Gesundheit & Schönheit > Gesundheitspflege > Zubehör für biometrische Messgeräte > Blutzuckermessung > Stechhilfen -Gesundheit & Schönheit > Gesundheitspflege > Zubehör für biometrische Messgeräte > Verhütungscomputerzubehör +Gesundheit & Schönheit > Gesundheitspflege > Zubehör für biometrische Messgeräte > Zubehör für Aktivitätsmonitore Gesundheit & Schönheit > Gesundheitspflege > Zubehör für biometrische Messgeräte > Zubehör für Badezimmerwaagen Gesundheit & Schönheit > Gesundheitspflege > Zubehör für biometrische Messgeräte > Zubehör für Blutdrucküberwachungsgeräte Gesundheit & Schönheit > Gesundheitspflege > Zubehör für biometrische Messgeräte > Zubehör für Blutdrucküberwachungsgeräte > Blutdruckmanschetten -Gesundheit & Schönheit > Gesundheitspflege > Zubehör für biometrische Messgeräte > Zubehör für Herzfrequenzmesser Gesundheit & Schönheit > Körperpflege Gesundheit & Schönheit > Körperpflege > Augenpflege Gesundheit & Schönheit > Körperpflege > Augenpflege > Augentropfen Gesundheit & Schönheit > Körperpflege > Augenpflege > Brillen Gesundheit & Schönheit > Körperpflege > Augenpflege > Brillengläser Gesundheit & Schönheit > Körperpflege > Augenpflege > Brillenzubehör -Gesundheit & Schönheit > Körperpflege > Augenpflege > Brillenzubehör > Brillenetuis & -ständer +Gesundheit & Schönheit > Körperpflege > Augenpflege > Brillenzubehör > Brillenbänder & -ketten +Gesundheit & Schönheit > Körperpflege > Augenpflege > Brillenzubehör > Brillenetuis & -halter +Gesundheit & Schönheit > Körperpflege > Augenpflege > Brillenzubehör > Kontaktlinsenreiniger Gesundheit & Schönheit > Körperpflege > Augenpflege > Kontaktlinsen Gesundheit & Schönheit > Körperpflege > Augenpflege > Kontaktlinsenpflege +Gesundheit & Schönheit > Körperpflege > Augenpflege > Kontaktlinsenpflege > Kontaktlinsen-Pflegesets Gesundheit & Schönheit > Körperpflege > Augenpflege > Kontaktlinsenpflege > Kontaktlinsenbehälter Gesundheit & Schönheit > Körperpflege > Augenpflege > Kontaktlinsenpflege > Kontaktlinsenpflegemittel -Gesundheit & Schönheit > Körperpflege > Augenpflege > Kontaktlinsenpflege > Pflegekits für Kontaktlinsen Gesundheit & Schönheit > Körperpflege > Augenpflege > Sonnenbrillengläser Gesundheit & Schönheit > Körperpflege > Damenhygieneartikel Gesundheit & Schönheit > Körperpflege > Damenhygieneartikel > Damen-Deodorant -Gesundheit & Schönheit > Körperpflege > Damenhygieneartikel > Intim-Waschlotionen +Gesundheit & Schönheit > Körperpflege > Damenhygieneartikel > Intim-Waschlotionen für Frauen +Gesundheit & Schönheit > Körperpflege > Damenhygieneartikel > Menstruationstassen Gesundheit & Schönheit > Körperpflege > Damenhygieneartikel > Slipeinlagen & Binden Gesundheit & Schönheit > Körperpflege > Damenhygieneartikel > Tampons Gesundheit & Schönheit > Körperpflege > Deodorants & Antitranspirante Gesundheit & Schönheit > Körperpflege > Einlaufsets & -zubehör Gesundheit & Schönheit > Körperpflege > Fußpflege -Gesundheit & Schönheit > Körperpflege > Fußpflege > Antipilzmittel +Gesundheit & Schönheit > Körperpflege > Fußpflege > Ballenpflege Gesundheit & Schönheit > Körperpflege > Fußpflege > Einlegesohlen & Einlagen Gesundheit & Schönheit > Körperpflege > Fußpflege > Fußgeruchskiller Gesundheit & Schönheit > Körperpflege > Fußpflege > Hühneraugen- & Hornhautprodukte -Gesundheit & Schönheit > Körperpflege > Fußpflege > Zehenpflege Gesundheit & Schönheit > Körperpflege > Fußpflege > Zehenspreizer Gesundheit & Schönheit > Körperpflege > Gleitmittel Gesundheit & Schönheit > Körperpflege > Haarkosmetik Gesundheit & Schönheit > Körperpflege > Haarkosmetik > Haar-Decoloration Gesundheit & Schönheit > Körperpflege > Haarkosmetik > Haarfärbemittel +Gesundheit & Schönheit > Körperpflege > Haarkosmetik > Haarpflege-Sets Gesundheit & Schönheit > Körperpflege > Haarkosmetik > Haarscheren Gesundheit & Schönheit > Körperpflege > Haarkosmetik > Haarstyling-Geräte Gesundheit & Schönheit > Körperpflege > Haarkosmetik > Haarstyling-Geräte > Haarglätter Gesundheit & Schönheit > Körperpflege > Haarkosmetik > Haarstyling-Geräte > Haartrockner +Gesundheit & Schönheit > Körperpflege > Haarkosmetik > Haarstyling-Geräte > Hairstyling-Sets Gesundheit & Schönheit > Körperpflege > Haarkosmetik > Haarstyling-Geräte > Kämme & Bürsten Gesundheit & Schönheit > Körperpflege > Haarkosmetik > Haarstyling-Geräte > Lockenstäbe Gesundheit & Schönheit > Körperpflege > Haarkosmetik > Haarstyling-Geräte > Lockenwickler @@ -1654,7 +1263,7 @@ Gesundheit & Schönheit > Körperpflege > Kosmetika > Badeartikel Gesundheit & Schönheit > Körperpflege > Kosmetika > Badeartikel > Bade- & Naturschwämme Gesundheit & Schönheit > Körperpflege > Kosmetika > Badeartikel > Badebürsten Gesundheit & Schönheit > Körperpflege > Kosmetika > Badeartikel > Duschhauben -Gesundheit & Schönheit > Körperpflege > Kosmetika > Badeartikel > Feuchttücher +Gesundheit & Schönheit > Körperpflege > Kosmetika > Badeartikel > Feuchttücher für Erwachsene Gesundheit & Schönheit > Körperpflege > Kosmetika > Badeartikel > Flüssigseifen Gesundheit & Schönheit > Körperpflege > Kosmetika > Badeartikel > Handdesinfektionsmittel Gesundheit & Schönheit > Körperpflege > Kosmetika > Badeartikel > Schaumbäder & Badesalze @@ -1668,64 +1277,68 @@ Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Aknebehandlun Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Anti-Aging-Hautpflegeprodukte Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Bräunungsprodukte Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Bräunungsprodukte > Selbstbräuner -Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Bräunungsprodukte > Selbstbräuner & Selbstbräuner-Entferner Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Bräunungsprodukte > Sonnenöle & -lotionen -Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Gesichtsmasken Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Gesichtspeeling Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Gesichtsreiniger -Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Gesichtsreinigungscremes +Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Gesichtsreinigersets Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Insektenschutzmittel +Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Komprimierte Gesichtsmasken Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Körperpuder Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Körperöle Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Lippenpflege Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Lotion & Feuchtigkeitscremes -Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Lotion & Feuchtigkeitscremes > Feuchtigkeitsspendende Gesichtscremes -Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Lotion & Feuchtigkeitscremes > Fußlotion -Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Lotion & Feuchtigkeitscremes > Handlotionen -Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Lotion & Feuchtigkeitscremes > Körperlotion Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Make-up-Entferner Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Porenreinigungspflaster Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Reinigungswasser & Adstringentien Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Sonnencreme Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Vaseline +Gesundheit & Schönheit > Körperpflege > Kosmetika > Hautpflege > Warzenentferner Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Hautpflegeutensilien +Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Hautpflegeutensilien > Applikatoren für Lotionen und Sonnenschutzcremes Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Hautpflegeutensilien > Bimssteine Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Hautpflegeutensilien > Fußfeilen Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Hautpflegeutensilien > Gesichtsbürsten Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Hautpflegeutensilien > Gesichtssaunas Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Hautpflegeutensilien > Hautpflegeroller +Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Hautpflegeutensilien > Hautreinigungs-Bürstenköpfe Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Hautpflegeutensilien > Mitesser-Entferner Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör -Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Applikatoren für künstliche Wimpern Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Augenbrauenschablonen Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Ersatzkissen für Wimpernzangen -Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Kleber für doppelte Augenlider -Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Kleber für künstliche Wimpern Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Make-up-Pinsel Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Make-up-Schwämme Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Make-up-Spiegel +Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Nachfüllbare Make-up-Paletten & -Koffer Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Pudertücher +Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Schlupflid-Kleber & -Klebestreifen Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Wimpernformer +Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Zubehör für künstliche Wimpern +Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Zubehör für künstliche Wimpern > Applikatoren für künstliche Wimpern +Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Zubehör für künstliche Wimpern > Entferner für künstliche Wimpern +Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Make-up Zubehör > Zubehör für künstliche Wimpern > Kleber für künstliche Wimpern Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre +Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre > Finger- & Zehenspreizer Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre > Maniküre-Sets Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre > Nagelfeilen & Sandblattfeilen Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre > Nagelfräser Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre > Nagelhautscheren Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre > Nagelhautschieber Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre > Nagelknipser +Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre > Nagelpiercing-Zubehör Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre > Nagelpolierer Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre > Nageltrockner -Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Maniküre & Pediküre > Zehenspreizer für Pediküre -Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetik-Zubehör > Pinzetten +Gesundheit & Schönheit > Körperpflege > Kosmetika > Kosmetiksets Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up -Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen-Make-up -Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen-Make-up > Augenbrauenstifte -Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen-Make-up > Behandlungen für Wimpernwachstum -Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen-Make-up > Kajalstifte -Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen-Make-up > Künstliche Wimpern -Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen-Make-up > Lidschatten -Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen-Make-up > Mascara +Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen +Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen > Augenbrauen Make-up +Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen > Eyeliner & Kajal +Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen > Künstliche Wimpern +Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen > Lidschatten +Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen > Lidschatten-Grundierung +Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen > Mascara +Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen > Mascara-Grundierung +Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Augen > Wimpern- & Augenbrauenbehandlung Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Kostüm- & Bühnen-Make-up Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Körper-Make-up Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Körper-Make-up > Bodypainting-Farbe @@ -1735,9 +1348,10 @@ Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Lippen > Lipglos Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Lippen > Lipliner Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Lippen > Lippen- & Wangentönung Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Lippen > Lippenstift -Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Makeup-Sets +Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Lippen > Lippenstift-Grundierung Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Spray zum Fixieren von Make-up Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Teint +Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Teint > Gesichtsgrundierung Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Teint > Grundierungen & Concealer Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Teint > Highlighter & Luminizer Gesundheit & Schönheit > Körperpflege > Kosmetika > Make-up > Teint > Puder @@ -1748,24 +1362,30 @@ Gesundheit & Schönheit > Körperpflege > Kosmetika > Nagelpflege > Künstliche Gesundheit & Schönheit > Körperpflege > Kosmetika > Nagelpflege > Nagelhautcremes & -Lotionen Gesundheit & Schönheit > Körperpflege > Kosmetika > Nagelpflege > Nagelkleber Gesundheit & Schönheit > Körperpflege > Kosmetika > Nagelpflege > Nagelkunst-Zubehör +Gesundheit & Schönheit > Körperpflege > Kosmetika > Nagelpflege > Nagellack-Überlacke und -Trockensprays Gesundheit & Schönheit > Körperpflege > Kosmetika > Nagelpflege > Nagellacke Gesundheit & Schönheit > Körperpflege > Kosmetika > Nagelpflege > Nagellackentferner Gesundheit & Schönheit > Körperpflege > Kosmetika > Nagelpflege > Nagellackverdünner Gesundheit & Schönheit > Körperpflege > Kosmetika > Reiniger für Kosmetikwerkzeuge Gesundheit & Schönheit > Körperpflege > Massage & Entspannung +Gesundheit & Schönheit > Körperpflege > Massage & Entspannung > Augenkissen Gesundheit & Schönheit > Körperpflege > Massage & Entspannung > Massagegeräte +Gesundheit & Schönheit > Körperpflege > Massage & Entspannung > Massagestein-Wärmer +Gesundheit & Schönheit > Körperpflege > Massage & Entspannung > Massagesteine Gesundheit & Schönheit > Körperpflege > Massage & Entspannung > Massagestühle Gesundheit & Schönheit > Körperpflege > Massage & Entspannung > Massagetische Gesundheit & Schönheit > Körperpflege > Massage & Entspannung > Massageöle +Gesundheit & Schönheit > Körperpflege > Massage & Entspannung > Rückenkratzer Gesundheit & Schönheit > Körperpflege > Mundpflege +Gesundheit & Schönheit > Körperpflege > Mundpflege > Aufbissschienen Gesundheit & Schönheit > Körperpflege > Mundpflege > Ersatzdüsen für Mundduschen Gesundheit & Schönheit > Körperpflege > Mundpflege > Gebissreiniger Gesundheit & Schönheit > Körperpflege > Mundpflege > Interdentalreiniger -Gesundheit & Schönheit > Körperpflege > Mundpflege > Mittel gegen Zahnschmerzen Gesundheit & Schönheit > Körperpflege > Mundpflege > Mundduschen -Gesundheit & Schönheit > Körperpflege > Mundpflege > Mundschutz Gesundheit & Schönheit > Körperpflege > Mundpflege > Mundspray Gesundheit & Schönheit > Körperpflege > Mundpflege > Mundwasser +Gesundheit & Schönheit > Körperpflege > Mundpflege > Reparatursets für Zahnprothesen +Gesundheit & Schönheit > Körperpflege > Mundpflege > Spangen-, Gebiss- & Aufbissschienendosen Gesundheit & Schönheit > Körperpflege > Mundpflege > Zahnaufhellung Gesundheit & Schönheit > Körperpflege > Mundpflege > Zahnbürsten Gesundheit & Schönheit > Körperpflege > Mundpflege > Zahnbürstenzubehör @@ -1786,27 +1406,32 @@ Gesundheit & Schönheit > Körperpflege > Ohrenpflege > Ohrenstöpsel Gesundheit & Schönheit > Körperpflege > Ohrenpflege > Ohrentrockner Gesundheit & Schönheit > Körperpflege > Ohrenpflege > Ohrentropfen Gesundheit & Schönheit > Körperpflege > Ohrenpflege > Ohrkerzen +Gesundheit & Schönheit > Körperpflege > Ohrenpflege > Ohrlöffel Gesundheit & Schönheit > Körperpflege > Ohrenpflege > Ohrspritzen Gesundheit & Schönheit > Körperpflege > Ohrenpflege > Ohrstöpselspender +Gesundheit & Schönheit > Körperpflege > Pinzetten Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Alaunstifte +Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Elektrorasierer Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Haar- & Bartschneider +Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Haaraufheller Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Haarentfernung Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Haarentfernung > Elektrolysegeräte Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Haarentfernung > Enthaarungsmittel +Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Haarentfernung > Enthaarungswachs-Wärmer Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Haarentfernung > Epilatoren Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Haarentfernung > Laserhaarentfernung -Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Haarentfernung > Waxing-Kits -Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Rasierapparate +Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Haarentfernung > Waxing-Sets & Zubehör Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Rasiercreme Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Rasierer & Rasierklingen Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Rasierpinsel +Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Rasierschalen Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Rasiersets Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Rasierwasser Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Zubehör für Haarschneidegeräte & Trimmer Gesundheit & Schönheit > Körperpflege > Rasieren & Trimmen > Zubehör für elektrische Rasierer -Gesundheit & Schönheit > Körperpflege > Rückenpflege -Gesundheit & Schönheit > Körperpflege > Rückenpflege > Stützkissen für Rücken- & Lendenbereich +Gesundheit & Schönheit > Körperpflege > Rückengesundheit +Gesundheit & Schönheit > Körperpflege > Rückengesundheit > Stützkissen für Rücken- & Lendenbereich Gesundheit & Schönheit > Körperpflege > Schlafhilfen Gesundheit & Schönheit > Körperpflege > Schlafhilfen > Hilfsmittel gegen Schnarchen und Schlaf-Apnoe Gesundheit & Schönheit > Körperpflege > Schlafhilfen > Reisekissen @@ -1814,12 +1439,14 @@ Gesundheit & Schönheit > Körperpflege > Schlafhilfen > Schlafmasken Gesundheit & Schönheit > Körperpflege > Schlafhilfen > Soundmaschinen Gesundheit & Schönheit > Körperpflege > Sonnenbänke Gesundheit & Schönheit > Körperpflege > Tanningzelt -Gesundheit & Schönheit > Körperpflege > Warzenentferner Gesundheit & Schönheit > Körperpflege > Wattebällchen Gesundheit & Schönheit > Körperpflege > Wattestäbchen Gesundheit & Schönheit > Schmuckreinigung & -pflege -Gesundheit & Schönheit > Schmuckreinigung & -pflege > Schmuckreiniger +Gesundheit & Schönheit > Schmuckreinigung & -pflege > Dampfstrahlreiniger für Schmuck +Gesundheit & Schönheit > Schmuckreinigung & -pflege > Hilfsmittel zur Schmuckreinigung +Gesundheit & Schönheit > Schmuckreinigung & -pflege > Lösungen und Poliermittel zur Schmuckreinigung Gesundheit & Schönheit > Schmuckreinigung & -pflege > Schmuckständer & -kästen +Gesundheit & Schönheit > Schmuckreinigung & -pflege > Uhrreparatursätze Heim & Garten Heim & Garten > Badzubehör Heim & Garten > Badzubehör > Badematten & Badteppiche @@ -1828,74 +1455,71 @@ Heim & Garten > Badzubehör > Badewannenkissen Heim & Garten > Badzubehör > Badezimmer-Zubehörsets Heim & Garten > Badzubehör > Badzubehörhalterungen Heim & Garten > Badzubehör > Duschvorhangringe +Heim & Garten > Badzubehör > Duschvorhangstangen Heim & Garten > Badzubehör > Duschvorhänge Heim & Garten > Badzubehör > Garderobenhaken +Heim & Garten > Badzubehör > Haltegriffe Heim & Garten > Badzubehör > Handtuchhalter Heim & Garten > Badzubehör > Händetrockner Heim & Garten > Badzubehör > Kosmetiktuchhalter Heim & Garten > Badzubehör > Medizinschränke Heim & Garten > Badzubehör > Seifen- & Lotionsspender Heim & Garten > Badzubehör > Seifenablagen -Heim & Garten > Badzubehör > Toilettenbürsten +Heim & Garten > Badzubehör > Toilettenbürsten & -halter Heim & Garten > Badzubehör > Toilettenbürstenköpfe Heim & Garten > Badzubehör > Toilettenpapierhalter Heim & Garten > Badzubehör > Zahnbürstenhalter Heim & Garten > Badzubehör > Zubehör für Händetrockner Heim & Garten > Beleuchtung -Heim & Garten > Beleuchtung > Außenbeleuchtung -Heim & Garten > Beleuchtung > Außenbeleuchtung > Gartenlaternen & -fackeln -Heim & Garten > Beleuchtung > Außenbeleuchtung > Gartenstrahler -Heim & Garten > Beleuchtung > Außenbeleuchtung > Terrassenbeleuchtung -Heim & Garten > Beleuchtung > Außenbeleuchtung > Wegbeleuchtung Heim & Garten > Beleuchtung > Bilderbeleuchtung -Heim & Garten > Beleuchtung > Flutlichter +Heim & Garten > Beleuchtung > Bodeneinbauleuchten +Heim & Garten > Beleuchtung > Flutlichter & Punktstrahler Heim & Garten > Beleuchtung > Lampen -Heim & Garten > Beleuchtung > Lampen > Standleuchten -Heim & Garten > Beleuchtung > Lampen > Tischlampen Heim & Garten > Beleuchtung > Leuchten Heim & Garten > Beleuchtung > Leuchten > Deckenleuchten Heim & Garten > Beleuchtung > Leuchten > Kronleuchter Heim & Garten > Beleuchtung > Leuchten > Unterbauleuchten Heim & Garten > Beleuchtung > Leuchten > Wandleuchten, Wandbeleuchtung -Heim & Garten > Beleuchtung > Leuchten > Wandleuchten, Wandbeleuchtung > Wandleuchten Heim & Garten > Beleuchtung > Leuchtmittel Heim & Garten > Beleuchtung > Leuchtmittel > Glühlampen Heim & Garten > Beleuchtung > Leuchtmittel > Kompaktleuchtstofflampen Heim & Garten > Beleuchtung > Leuchtmittel > LED-Leuchtmittel Heim & Garten > Beleuchtung > Leuchtmittel > Leuchtstoffröhren -Heim & Garten > Beleuchtung > Lichtschienen -Heim & Garten > Beleuchtung > Lichtschienen > Leuchten für Lichtschienensysteme -Heim & Garten > Beleuchtung > Lichtschienen > Schienen für Lichtschienensysteme -Heim & Garten > Beleuchtung > Lichtschienen > Zubehör für Lichtschienensysteme +Heim & Garten > Beleuchtung > Lichtschienensysteme +Heim & Garten > Beleuchtung > Lichtschienensysteme > Leuchten für Lichtschienensysteme +Heim & Garten > Beleuchtung > Lichtschienensysteme > Schienen für Lichtschienensysteme +Heim & Garten > Beleuchtung > Lichtschienensysteme > Zubehör für Lichtschienensysteme Heim & Garten > Beleuchtung > Lichtschläuche & Lichterketten -Heim & Garten > Beleuchtung > Nachtlichter +Heim & Garten > Beleuchtung > Nachtlichter & indirekte Beleuchtung Heim & Garten > Beleuchtung > Notbeleuchtung +Heim & Garten > Beleuchtung > Schwimmende Lichter +Heim & Garten > Beleuchtung > Tiki-Fackeln & Öllampen +Heim & Garten > Beleuchtung > Wegbeleuchtung Heim & Garten > Beleuchtungszubehör Heim & Garten > Beleuchtungszubehör > Beleuchtungszeitschaltuhren +Heim & Garten > Beleuchtungszubehör > Brennstoff für Öllampen Heim & Garten > Beleuchtungszubehör > Lampenschirme Heim & Garten > Beleuchtungszubehör > Laternenhalterungen Heim & Garten > Beleuchtungszubehör > Laternensockel Heim & Garten > Bett- und Haushaltswäsche Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche +Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Baldachine & Betthimmel Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Bettbezüge -Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Bettdecken Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Bettlaken Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Bettvolants Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Decken -Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Decken > Heizdecken -Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Decken > Überwürfe -Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Kissen- & Zierkissenbezüge +Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Kissenbezüge Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Kopfkissen -Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Kopfkissen > Seitenschläferkissen Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Matratzenschoner Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Matratzenschoner > Matratzenauflagen Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Matratzenschoner > Matratzenbezüge Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Schlafunterlagen -Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Steppdecken +Heim & Garten > Bett- und Haushaltswäsche > Bettwäsche > Steppdecken & Quilts Heim & Garten > Bett- und Haushaltswäsche > Handtücher Heim & Garten > Bett- und Haushaltswäsche > Handtücher > Badhandtücher & Waschlappen Heim & Garten > Bett- und Haushaltswäsche > Handtücher > Geschirrtücher Heim & Garten > Bett- und Haushaltswäsche > Handtücher > Strandtücher +Heim & Garten > Bett- und Haushaltswäsche > Küchentuchsets Heim & Garten > Bett- und Haushaltswäsche > Tischwäsche Heim & Garten > Bett- und Haushaltswäsche > Tischwäsche > Stoffservietten Heim & Garten > Bett- und Haushaltswäsche > Tischwäsche > Tischläufer @@ -1903,52 +1527,47 @@ Heim & Garten > Bett- und Haushaltswäsche > Tischwäsche > Tischsets Heim & Garten > Bett- und Haushaltswäsche > Tischwäsche > Tischtücher Heim & Garten > Bett- und Haushaltswäsche > Tischwäsche > Tischvorhänge Heim & Garten > Bett- und Haushaltswäsche > Tischwäsche > Zierdecken -Heim & Garten > Bett- und Haushaltswäsche > Tischwäschezubehör -Heim & Garten > Bett- und Haushaltswäsche > Tischwäschezubehör > Klemmen & Gewichte für Tischdecken Heim & Garten > Dekoration Heim & Garten > Dekoration > Bilderrahmen -Heim & Garten > Dekoration > Bilderrahmen > Digitale Bilderrahmen -Heim & Garten > Dekoration > Bilderrahmen > Posterrahmen Heim & Garten > Dekoration > Briefkasten-Zubehör -Heim & Garten > Dekoration > Briefkasten-Zubehör > Briefkasten-Befestigungsbügel Heim & Garten > Dekoration > Briefkasten-Zubehör > Briefkasten-Standanlagen Heim & Garten > Dekoration > Briefkasten-Zubehör > Briefkastenanlagen -Heim & Garten > Dekoration > Briefkasten-Zubehör > Briefkastenaufsätze Heim & Garten > Dekoration > Briefkasten-Zubehör > Briefkastenfahnen +Heim & Garten > Dekoration > Briefkasten-Zubehör > Briefkastengehäuse Heim & Garten > Dekoration > Briefkasten-Zubehör > Briefkastentüren Heim & Garten > Dekoration > Briefkästen Heim & Garten > Dekoration > Briefschlitze Heim & Garten > Dekoration > Brunnen & Teiche -Heim & Garten > Dekoration > Brunnen & Teiche > Brunnen -Heim & Garten > Dekoration > Brunnen & Teiche > Brunnen > Außenbrunnen -Heim & Garten > Dekoration > Brunnen & Teiche > Brunnen > Zimmerbrunnen +Heim & Garten > Dekoration > Brunnen & Teiche > Brunnen & Wasserfälle Heim & Garten > Dekoration > Brunnen & Teiche > Brunnen- & Teichzubehör Heim & Garten > Dekoration > Brunnen & Teiche > Teiche Heim & Garten > Dekoration > Buchstützen +Heim & Garten > Dekoration > Deko-Aufkleber Heim & Garten > Dekoration > Dekorative Flaschen Heim & Garten > Dekoration > Dekorative Gefäße -Heim & Garten > Dekoration > Dekorative Klingeln Heim & Garten > Dekoration > Dekorteller -Heim & Garten > Dekoration > Dufttarts Heim & Garten > Dekoration > Ecosphere-Kugeln Heim & Garten > Dekoration > Fensterdrapierung Heim & Garten > Dekoration > Fensterdrapierung > Buntglasscheiben Heim & Garten > Dekoration > Fensterdrapierung > Fensterfolie Heim & Garten > Dekoration > Fensterdrapierung > Fliegengitter Heim & Garten > Dekoration > Fensterdrapierung > Jalousien & Rollos -Heim & Garten > Dekoration > Fensterdrapierung > Volants +Heim & Garten > Dekoration > Fensterdrapierung > Querbehänge Heim & Garten > Dekoration > Fensterdrapierung > Vorhänge & Gardinen Heim & Garten > Dekoration > Fensterschmuckmagnete Heim & Garten > Dekoration > Festtags-Dekoartikel Heim & Garten > Dekoration > Festtags-Dekoartikel > Adventskalender Heim & Garten > Dekoration > Festtags-Dekoartikel > Haken für Weihnachtssocken Heim & Garten > Dekoration > Festtags-Dekoartikel > Krippen +Heim & Garten > Dekoration > Festtags-Dekoartikel > Modellbaudörfer & -zubehör mit saisonalem Bezug +Heim & Garten > Dekoration > Festtags-Dekoartikel > Ostereier-Färbe-Sets +Heim & Garten > Dekoration > Festtags-Dekoartikel > Traditionelle Japanische Puppen Heim & Garten > Dekoration > Festtags-Dekoartikel > Weihnachtsbaumdecken Heim & Garten > Dekoration > Festtags-Dekoartikel > Weihnachtsbaumschmuck Heim & Garten > Dekoration > Festtags-Dekoartikel > Weihnachtsbaumständer -Heim & Garten > Dekoration > Festtags-Dekoartikel > Weihnachtsschmuck-Aufbewahrung +Heim & Garten > Dekoration > Festtags-Dekoartikel > Weihnachtsschmuck-Aufhänger Heim & Garten > Dekoration > Festtags-Dekoartikel > Weihnachtsschmuck-Displays -Heim & Garten > Dekoration > Festtags-Dekoartikel > Weihnachtssocken +Heim & Garten > Dekoration > Festtags-Dekoartikel > Weihnachtsstrümpfe Heim & Garten > Dekoration > Figuren zur Dekoration Heim & Garten > Dekoration > Flaggen & Windsäcke Heim & Garten > Dekoration > Flammenlose Kerzen @@ -1959,38 +1578,40 @@ Heim & Garten > Dekoration > Futterspender > Vogelfutterspender Heim & Garten > Dekoration > Fußmatten Heim & Garten > Dekoration > Garderoben Heim & Garten > Dekoration > Garten- & Trittsteine -Heim & Garten > Dekoration > Gartenschmuck -Heim & Garten > Dekoration > Gartenschmuck > Gartenkugeln -Heim & Garten > Dekoration > Gartenskulpturen +Heim & Garten > Dekoration > Gartendeko Heim & Garten > Dekoration > Glasfensterbilder Heim & Garten > Dekoration > Globen Heim & Garten > Dekoration > Hausnummern & Buchstaben Heim & Garten > Dekoration > Hausnummernschilder -Heim & Garten > Dekoration > Kerzen -Heim & Garten > Dekoration > Kerzen > Chanukkah-Kerzen -Heim & Garten > Dekoration > Kerzen > Hochzeitskerzen -Heim & Garten > Dekoration > Kerzenständer -Heim & Garten > Dekoration > Kerzenständer > Chanukkia +Heim & Garten > Dekoration > Kartonschablonen +Heim & Garten > Dekoration > Kindermesslatten Heim & Garten > Dekoration > Kreuzblumen Heim & Garten > Dekoration > Kränze & Girlanden +Heim & Garten > Dekoration > Kunst +Heim & Garten > Dekoration > Kunst > Figuren, Skulpturen & Statuen +Heim & Garten > Dekoration > Kunst > Poster & Bildende Kunst +Heim & Garten > Dekoration > Kunst > Wandteppiche Heim & Garten > Dekoration > Körbe Heim & Garten > Dekoration > Kühlschrankmagnete Heim & Garten > Dekoration > Künstliche Pflanzen -Heim & Garten > Dekoration > Künstliche Pflanzen > Kunstfloristik -Heim & Garten > Dekoration > Künstliche Pflanzen > Künstliche Blumen -Heim & Garten > Dekoration > Künstliche Pflanzen > Künstliche Bäume -Heim & Garten > Dekoration > Künstliche Pflanzen > Künstliche Formschnittpflanzen Heim & Garten > Dekoration > Lebensmittel-Attrappen Heim & Garten > Dekoration > Niederschlags-Messgeräte Heim & Garten > Dekoration > Quadratische Sitzkissen +Heim & Garten > Dekoration > Raumduft-Zubehör +Heim & Garten > Dekoration > Raumduft-Zubehör > Kerzenlöscher +Heim & Garten > Dekoration > Raumduft-Zubehör > Kerzenständer +Heim & Garten > Dekoration > Raumduft-Zubehör > Räuchermittelhalter +Heim & Garten > Dekoration > Raumduft-Zubehör > Wachs- & Ölwärmer Heim & Garten > Dekoration > Raumdüfte +Heim & Garten > Dekoration > Raumdüfte > Duftwachs Heim & Garten > Dekoration > Raumdüfte > Duftöle +Heim & Garten > Dekoration > Raumdüfte > Kerzen Heim & Garten > Dekoration > Raumdüfte > Lufterfrischer Heim & Garten > Dekoration > Raumdüfte > Potpourri Heim & Garten > Dekoration > Raumdüfte > Räuchermittel -Heim & Garten > Dekoration > Raumdüfte > Räuchermittelhalter Heim & Garten > Dekoration > Regenketten Heim & Garten > Dekoration > Rückenstützkissen +Heim & Garten > Dekoration > Sanduhren Heim & Garten > Dekoration > Schalen Heim & Garten > Dekoration > Schaukästen Heim & Garten > Dekoration > Schneekugeln @@ -2003,35 +1624,36 @@ Heim & Garten > Dekoration > Spaßschilder Heim & Garten > Dekoration > Spiegel Heim & Garten > Dekoration > Spieluhren Heim & Garten > Dekoration > Tabletts -Heim & Garten > Dekoration > Tafeln +Heim & Garten > Dekoration > Tapeten Heim & Garten > Dekoration > Teppiche -Heim & Garten > Dekoration > Teppiche > Heizteppiche -Heim & Garten > Dekoration > Tiki-Fackeln & Öllampen Heim & Garten > Dekoration > Traumfänger Heim & Garten > Dekoration > Trockenblumen Heim & Garten > Dekoration > Truhen +Heim & Garten > Dekoration > Uhrbauteile Heim & Garten > Dekoration > Uhren Heim & Garten > Dekoration > Uhren > Standuhren Heim & Garten > Dekoration > Uhren > Tisch- und Regaluhren Heim & Garten > Dekoration > Uhren > Wanduhren -Heim & Garten > Dekoration > Uhren > Wanduhren > Kuckucksuhren Heim & Garten > Dekoration > Uhren > Wecker -Heim & Garten > Dekoration > Uhren > Wecker > Radiowecker -Heim & Garten > Dekoration > Uhren > Wecker > Reisewecker Heim & Garten > Dekoration > Vasen -Heim & Garten > Dekoration > Vogelhäuschen +Heim & Garten > Dekoration > Vasenfüller & Streudeko +Heim & Garten > Dekoration > Vogel- und Tierhäuschen +Heim & Garten > Dekoration > Vogel- und Tierhäuschen > Fledermaushäuschen +Heim & Garten > Dekoration > Vogel- und Tierhäuschen > Schmetterlingshäuschen +Heim & Garten > Dekoration > Vogel- und Tierhäuschen > Vogelhäuschen +Heim & Garten > Dekoration > Vogelhaus-Zubehör Heim & Garten > Dekoration > Vogeltränken -Heim & Garten > Dekoration > Wachs- & Ölwärmer -Heim & Garten > Dekoration > Wandapplikationen -Heim & Garten > Dekoration > Wandregale & Simse Heim & Garten > Dekoration > Wetterfahnen & Dachdekoration Heim & Garten > Dekoration > Windräder Heim & Garten > Dekoration > Windspiele +Heim & Garten > Dekoration > Zierglocken Heim & Garten > Dekoration > Zierkissen -Heim & Garten > Dekoration > Zubehör für Fahnen und Windsäcke -Heim & Garten > Dekoration > Zubehör für Fahnen und Windsäcke > Beleuchtung für Fahnen- und Windsackstangen -Heim & Garten > Dekoration > Zubehör für Fahnen und Windsäcke > Stangen für Fahnen und Windsäcke -Heim & Garten > Dekoration > Zubehör für Vogelhäuschen +Heim & Garten > Dekoration > Ziertafeln +Heim & Garten > Dekoration > Zubehör für Fahnen & Windsäcke +Heim & Garten > Dekoration > Zubehör für Fahnen & Windsäcke > Befestigungsvorrichtungen & -sets für Fahnen- & Windsackstangen +Heim & Garten > Dekoration > Zubehör für Fahnen & Windsäcke > Beleuchtung für Fahnen- & Windsackstangen +Heim & Garten > Dekoration > Zubehör für Fahnen & Windsäcke > Stangen für Fahnen & Windsäcke +Heim & Garten > Dekoration > Zubehör für Futterspender Heim & Garten > Dekoration > Zubehör zur Fensterdekoration Heim & Garten > Dekoration > Zubehör zur Fensterdekoration > Ersatzteile für Vorhänge & Jalousien Heim & Garten > Dekoration > Zubehör zur Fensterdekoration > Gardinen- & Vorhangringe @@ -2039,30 +1661,30 @@ Heim & Garten > Dekoration > Zubehör zur Fensterdekoration > Gardinen- & Vorhan Heim & Garten > Dekoration > Zubehör zur Fensterdekoration > Gardinenhalter & -quasten Heim & Garten > Feuer- & Gasschutz Heim & Garten > Feuer- & Gasschutz > Feuerlöscher -Heim & Garten > Feuer- & Gasschutz > Feuerlöschkästen Heim & Garten > Feuer- & Gasschutz > Feuermelder Heim & Garten > Feuer- & Gasschutz > Feuermelder-Steuerungen Heim & Garten > Feuer- & Gasschutz > Hitzemelder -Heim & Garten > Feuer- & Gasschutz > Kohlenmonoxiddetektoren -Heim & Garten > Feuer- & Gasschutz > Radondetektoren -Heim & Garten > Feuer- & Gasschutz > Rauchmelder +Heim & Garten > Feuer- & Gasschutz > Kästen & Halterungen für Feuerlöscher +Heim & Garten > Feuer- & Gasschutz > Rauch- & Kohlenmonoxidmelder Heim & Garten > Feuer- & Gasschutz > Sprinkleranlagen +Heim & Garten > Feuer- & Gasschutz > Wassermelder Heim & Garten > Haushaltsbedarf Heim & Garten > Haushaltsbedarf > Abfallbehälter +Heim & Garten > Haushaltsbedarf > Abfallbehälter > Müllcontainer Heim & Garten > Haushaltsbedarf > Abfallbehälter > Mülltonnen & Abfalleimer Heim & Garten > Haushaltsbedarf > Abfallbehälter > Recycling-Behälter Heim & Garten > Haushaltsbedarf > Abfallbehälter > Sondermüllbehälter Heim & Garten > Haushaltsbedarf > Abfallbehälterzubehör -Heim & Garten > Haushaltsbedarf > Abfallbehälterzubehör > Abfallbehälter-Auszugsrahmen +Heim & Garten > Haushaltsbedarf > Abfallbehälterzubehör > Abfallbehälter-Verkleidungen Heim & Garten > Haushaltsbedarf > Abfallbehälterzubehör > Abfallbehälterdeckel Heim & Garten > Haushaltsbedarf > Abfallbehälterzubehör > Abfallbehälterrollen Heim & Garten > Haushaltsbedarf > Abfallbehälterzubehör > Abfallbehälterwagen +Heim & Garten > Haushaltsbedarf > Abfallbehälterzubehör > Beschriftungen & Etiketten für Abfallbehälter Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme -Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Aufbewahrung & Ordnungssysteme für Geschenkpapier Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Aufbewahrungsbehälter Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Aufbewahrungsbeutel Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Aufbewahrungshaken & Regale -Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Aufbewahrungshaken & Regale > Kleiderständer +Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Aufbewahrungshaken & Regale > Bügelbrettaufhänger und -ablagen Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Aufbewahrungshaken & Regale > Mehrzweckhaken Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Aufbewahrungshaken & Regale > Schirmständer Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Fotoaufbewahrung @@ -2070,24 +1692,24 @@ Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Fotoaufbewahr Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Fotoaufbewahrung > Fotoboxen Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Haushalts-Ordnungshelfer Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Kleider- & Wandschrankaufbewahrung +Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Kleider- & Wandschrankaufbewahrung > Herrendiener Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Kleider- & Wandschrankaufbewahrung > Hutschachteln Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Kleider- & Wandschrankaufbewahrung > Kleiderbügel +Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Kleider- & Wandschrankaufbewahrung > Kleiderständer +Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Kleider- & Wandschrankaufbewahrung > Ladestationen Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Kleider- & Wandschrankaufbewahrung > Schuhregale & -schränke -Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Kleinaufbewahrung / sonstige Aufbewahrung -Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Kleinaufbewahrung / sonstige Aufbewahrung > Herrendiener -Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Kleinaufbewahrung / sonstige Aufbewahrung > Kommoden-Organizer -Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Kleinaufbewahrung / sonstige Aufbewahrung > Ladestationen -Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Pillendosen +Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Schubladeneinsätze Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Schubladensysteme Heim & Garten > Haushaltsbedarf > Aufbewahrung & Ordnungssysteme > Tischbesteckkommoden Heim & Garten > Haushaltsbedarf > Auskleidung für Schubladen & Regale +Heim & Garten > Haushaltsbedarf > Fußbodenschutzfolien & -läufer +Heim & Garten > Haushaltsbedarf > Garagenmatten Heim & Garten > Haushaltsbedarf > Haushaltsthermometer Heim & Garten > Haushaltsbedarf > Hygienepapiere Heim & Garten > Haushaltsbedarf > Hygienepapiere > Küchenrollen & Papierhandtücher Heim & Garten > Haushaltsbedarf > Hygienepapiere > Papierservietten Heim & Garten > Haushaltsbedarf > Hygienepapiere > Papiertaschentücher Heim & Garten > Haushaltsbedarf > Hygienepapiere > Toilettenpapier -Heim & Garten > Haushaltsbedarf > Luftentfeuchter Heim & Garten > Haushaltsbedarf > Möbelgleiter Heim & Garten > Haushaltsbedarf > Müllbeutel Heim & Garten > Haushaltsbedarf > Müllpressezubehör @@ -2095,22 +1717,23 @@ Heim & Garten > Haushaltsbedarf > Reinigungsutensilien Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Abzieher Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Besen Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Besen- & Bodenwischerstiele +Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Besenköpfe +Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Bodenwischeraufsätze Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Eimer Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Ersatz-Wischmops Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Gewebe- & Polsterschutz Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Allzweckreiniger -Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Backofenreiniger +Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Backofen- & Grillreiniger +Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Bodenreiniger Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Edelstahlreiniger & -polituren +Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Entkalkungsmittel Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Geschirrspülmittel & -seifen Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Glas- & Oberflächenreiniger Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Haushaltsdesinfektion Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Klarspüler -Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Lederpflege- & Färbemittel Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Möbelreiniger & -Politur -Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Reiniger für Hartholzböden Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Spülmaschinenreiniger -Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Steinreiniger Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Teppichreiniger Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Textil- & Polsterreiniger Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Tiergeruchs- & Fleckenentferner @@ -2119,8 +1742,8 @@ Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Wan Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Haushaltsreiniger > Waschmaschinenreiniger Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Kehrschaufeln Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Reinigungshandschuhe +Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Reinigungstücher Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Scheuerbürsten -Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Scheuerbürsten > Bürsten für Babyflaschen Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Schwämme & Topfreiniger Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Staubwedel & -Tücher Heim & Garten > Haushaltsbedarf > Reinigungsutensilien > Staubwedel-Ersatzköpfe @@ -2145,18 +1768,22 @@ Heim & Garten > Haushaltsbedarf > Schädlingsbekämpfung > Insektenschutz Heim & Garten > Haushaltsbedarf > Schädlingsbekämpfung > Insektenschutz > Mittel gegen Haushaltsinsekten Heim & Garten > Haushaltsbedarf > Schädlingsbekämpfung > Insektenschutz > Tierabwehr Heim & Garten > Haushaltsbedarf > Schädlingsbekämpfung > Schädlingsbekämpfungsmittel +Heim & Garten > Haushaltsbedarf > Stufenteppiche Heim & Garten > Haushaltsbedarf > Teppichunterlagen +Heim & Garten > Haushaltsbedarf > Trocknungsmittel Heim & Garten > Haushaltsbedarf > Wäschepflege Heim & Garten > Haushaltsbedarf > Wäschepflege > Bleichmittel Heim & Garten > Haushaltsbedarf > Wäschepflege > Bügelbretter Heim & Garten > Haushaltsbedarf > Wäschepflege > Bügelbrettunterlagen & -bezüge +Heim & Garten > Haushaltsbedarf > Wäschepflege > Bügeleisenhalter +Heim & Garten > Haushaltsbedarf > Wäschepflege > Ersatzteile für Bügeleisen Heim & Garten > Haushaltsbedarf > Wäschepflege > Fleckenentferner für Stoffe Heim & Garten > Haushaltsbedarf > Wäschepflege > Fusselrasierer Heim & Garten > Haushaltsbedarf > Wäschepflege > Fusselrollen Heim & Garten > Haushaltsbedarf > Wäschepflege > Mittel gegen Falten & statische Aufladung Heim & Garten > Haushaltsbedarf > Wäschepflege > Schweißabsorbierungspads Heim & Garten > Haushaltsbedarf > Wäschepflege > Textilerfrischer -Heim & Garten > Haushaltsbedarf > Wäschepflege > Trockenreinigungsbedarf +Heim & Garten > Haushaltsbedarf > Wäschepflege > Trockenreinigungssets Heim & Garten > Haushaltsbedarf > Wäschepflege > Waschbälle Heim & Garten > Haushaltsbedarf > Wäschepflege > Waschpulver Heim & Garten > Haushaltsbedarf > Wäschepflege > Weichspüler & Trocknertücher @@ -2166,39 +1793,36 @@ Heim & Garten > Haushaltsbedarf > Wäschepflege > Wäscheständer Heim & Garten > Haushaltsbedarf > Wäschepflege > Wäschestärke Heim & Garten > Haushaltsbedarf > Wäschepflege > Wäschesäcke & Waschkörbe Heim & Garten > Haushaltsgeräte +Heim & Garten > Haushaltsgeräte > Boden- & Teppichtrockner Heim & Garten > Haushaltsgeräte > Bohner- & Poliermaschinen Heim & Garten > Haushaltsgeräte > Dampf-Tapetenlöser Heim & Garten > Haushaltsgeräte > Futontrockner -Heim & Garten > Haushaltsgeräte > Garagenparksensoren +Heim & Garten > Haushaltsgeräte > Garagentorantrieb Heim & Garten > Haushaltsgeräte > Garagentoröffner -Heim & Garten > Haushaltsgeräte > Garagenöffner -Heim & Garten > Haushaltsgeräte > Heim- & Gebäudeautomatisierung Heim & Garten > Haushaltsgeräte > Heißwasserboiler -Heim & Garten > Haushaltsgeräte > Heißwasserboiler > Tauchsieder Heim & Garten > Haushaltsgeräte > Klimatisierung Heim & Garten > Haushaltsgeräte > Klimatisierung > Fog-Anlagen für den Außenbereich Heim & Garten > Haushaltsgeräte > Klimatisierung > Kanalheizer Heim & Garten > Haushaltsgeräte > Klimatisierung > Klimaanlagen -Heim & Garten > Haushaltsgeräte > Klimatisierung > Kotatsu Heim & Garten > Haushaltsgeräte > Klimatisierung > Luftbefeuchter +Heim & Garten > Haushaltsgeräte > Klimatisierung > Luftentfeuchter Heim & Garten > Haushaltsgeräte > Klimatisierung > Luftreiniger Heim & Garten > Haushaltsgeräte > Klimatisierung > Radiatoren Heim & Garten > Haushaltsgeräte > Klimatisierung > Raumheizgeräte -Heim & Garten > Haushaltsgeräte > Klimatisierung > Raumtrocknungsgeräte Heim & Garten > Haushaltsgeräte > Klimatisierung > Terrassenstrahler Heim & Garten > Haushaltsgeräte > Klimatisierung > Ventilatoren Heim & Garten > Haushaltsgeräte > Klimatisierung > Ventilatoren > Deckenventilatoren Heim & Garten > Haushaltsgeräte > Klimatisierung > Ventilatoren > Handventilatoren Heim & Garten > Haushaltsgeräte > Klimatisierung > Ventilatoren > Lüftungsventilatoren Heim & Garten > Haushaltsgeräte > Klimatisierung > Ventilatoren > Tisch- & Standventilatoren +Heim & Garten > Haushaltsgeräte > Klimatisierung > Ventilatoren > Wandventilatoren Heim & Garten > Haushaltsgeräte > Klimatisierung > Verdunstungskühler Heim & Garten > Haushaltsgeräte > Klimatisierung > Öfen & Kessel -Heim & Garten > Haushaltsgeräte > Nähmaschinen -Heim & Garten > Haushaltsgeräte > Nähmaschinen > Overlock-Nähmaschinen Heim & Garten > Haushaltsgeräte > Staubsauger Heim & Garten > Haushaltsgeräte > Teppich- & Dampfreinigungsgeräte +Heim & Garten > Haushaltsgeräte > Ultraschallreiniger Heim & Garten > Haushaltsgeräte > Waschen & Trocknen -Heim & Garten > Haushaltsgeräte > Waschen & Trocknen > Bügeleisen +Heim & Garten > Haushaltsgeräte > Waschen & Trocknen > Bügeleisen & -systeme Heim & Garten > Haushaltsgeräte > Waschen & Trocknen > Dampfglätter für Bekleidung Heim & Garten > Haushaltsgeräte > Waschen & Trocknen > Dampfpressen Heim & Garten > Haushaltsgeräte > Waschen & Trocknen > Waschmaschinen @@ -2207,13 +1831,13 @@ Heim & Garten > Haushaltsgeräte > Waschen & Trocknen > Wäschetrockner Heim & Garten > Haushaltsgeräte-Zubehör Heim & Garten > Haushaltsgeräte-Zubehör > Boiler-Zubehör Heim & Garten > Haushaltsgeräte-Zubehör > Boiler-Zubehör > Abgasrohre +Heim & Garten > Haushaltsgeräte-Zubehör > Boiler-Zubehör > Ausgleichsgefäß für Warmwasserbereiter Heim & Garten > Haushaltsgeräte-Zubehör > Boiler-Zubehör > Boiler Heim & Garten > Haushaltsgeräte-Zubehör > Boiler-Zubehör > Boilerwannen Heim & Garten > Haushaltsgeräte-Zubehör > Boiler-Zubehör > Heizelemente Heim & Garten > Haushaltsgeräte-Zubehör > Boiler-Zubehör > Heizungslüftung Heim & Garten > Haushaltsgeräte-Zubehör > Boiler-Zubehör > Opferanoden Heim & Garten > Haushaltsgeräte-Zubehör > Entfeuchterzubehör -Heim & Garten > Haushaltsgeräte-Zubehör > Entfeuchterzubehör > Entfeuchterfilter Heim & Garten > Haushaltsgeräte-Zubehör > Heizkessel- & Boilerzubehör Heim & Garten > Haushaltsgeräte-Zubehör > Heizkörperzubehör Heim & Garten > Haushaltsgeräte-Zubehör > Heizkörperzubehör > Heizkörperreflexionsfolie @@ -2226,63 +1850,51 @@ Heim & Garten > Haushaltsgeräte-Zubehör > Luftbefeuchter-Zubehör Heim & Garten > Haushaltsgeräte-Zubehör > Luftbefeuchter-Zubehör > Luftbefeuchterfilter Heim & Garten > Haushaltsgeräte-Zubehör > Luftreinigerzubehör Heim & Garten > Haushaltsgeräte-Zubehör > Luftreinigerzubehör > Luftreinigerfilter -Heim & Garten > Haushaltsgeräte-Zubehör > Nähmaschinenzubehör -Heim & Garten > Haushaltsgeräte-Zubehör > Nähmaschinenzubehör > Nähmaschinenfüße -Heim & Garten > Haushaltsgeräte-Zubehör > Nähmaschinenzubehör > Nähmaschinengehäuse & -abdeckungen -Heim & Garten > Haushaltsgeräte-Zubehör > Nähmaschinenzubehör > Nähmaschinennadeln -Heim & Garten > Haushaltsgeräte-Zubehör > Nähmaschinenzubehör > Spulen & Spulenhalter Heim & Garten > Haushaltsgeräte-Zubehör > Staubsaugerzubehör -Heim & Garten > Haushaltsgeräte-Zubehör > Staubsaugerzubehör > Staubsauger-Antriebsriemen -Heim & Garten > Haushaltsgeräte-Zubehör > Staubsaugerzubehör > Staubsaugeraufsätze -Heim & Garten > Haushaltsgeräte-Zubehör > Staubsaugerzubehör > Staubsaugerbeutel -Heim & Garten > Haushaltsgeräte-Zubehör > Staubsaugerzubehör > Staubsaugerfilter -Heim & Garten > Haushaltsgeräte-Zubehör > Staubsaugerzubehör > Staubsaugerschläuche -Heim & Garten > Haushaltsgeräte-Zubehör > Teppich- und Dampfreinigerzubehör +Heim & Garten > Haushaltsgeräte-Zubehör > Teppich- & Dampfreinigerzubehör Heim & Garten > Haushaltsgeräte-Zubehör > Ventilatorenzubehör Heim & Garten > Haushaltsgeräte-Zubehör > Wasch-Zubehör Heim & Garten > Haushaltsgeräte-Zubehör > Wasch-Zubehör > Bügeleisenzubehör -Heim & Garten > Haushaltsgeräte-Zubehör > Wasch-Zubehör > Waschmaschinenzubehör -Heim & Garten > Haushaltsgeräte-Zubehör > Wasch-Zubehör > Waschmaschinenzubehör > Sockel für Waschmaschinen & Trockner -Heim & Garten > Haushaltsgeräte-Zubehör > Wasch-Zubehör > Waschmaschinenzubehör > Waschmaschinenschläuche -Heim & Garten > Haushaltsgeräte-Zubehör > Wasch-Zubehör > Waschmaschinenzubehör > Waschmaschinenventile Heim & Garten > Haushaltsgeräte-Zubehör > Wasch-Zubehör > Zubehör für Dampfmaschinen Heim & Garten > Haushaltsgeräte-Zubehör > Wasch-Zubehör > Zubehör für Dampfpressen -Heim & Garten > Haushaltsgeräte-Zubehör > Wasch-Zubehör > Zubehör für Wäschetrockner +Heim & Garten > Haushaltsgeräte-Zubehör > Wasch-Zubehör > Zubehör für Waschmaschinen und Wäschetrockner Heim & Garten > Haussicherheit Heim & Garten > Haussicherheit > Bewegungsmelder Heim & Garten > Haussicherheit > Hausalarmsysteme +Heim & Garten > Haussicherheit > Safe-Zubehör Heim & Garten > Haussicherheit > Safes & Tresore Heim & Garten > Haussicherheit > Sensoren für Sicherheitssysteme Heim & Garten > Haussicherheit > Sicherheitsleuchten -Heim & Garten > Haussicherheit > Tracking-Geräte +Heim & Garten > Haussicherheit > Sicherheitsspiegel Heim & Garten > Haussicherheit > Überwachungskamera-Attrappen Heim & Garten > Haussicherheit > Überwachungsmonitore & -rekorder Heim & Garten > Holzöfen Heim & Garten > Kamine -Heim & Garten > Kamine > Außenkamine -Heim & Garten > Kamine > Innenkamine Heim & Garten > Küche & Esszimmer Heim & Garten > Küche & Esszimmer > Bar-Utensilien Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Absinth-Fontänen Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Bierzapfhähne +Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Cocktail- & Barzubehörsets +Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Cocktailshaker & Zubehör +Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Cocktailshaker & Zubehör > Bar-Eispickel +Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Cocktailshaker & Zubehör > Barsiebe +Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Cocktailshaker & Zubehör > Cocktailshaker +Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Cocktailshaker & Zubehör > Cocktailstößel +Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Cocktailshaker & Zubehör > Flaschenöffner Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Dekanter -Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Eiskübel Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Flaschenstöpsel & -verschlüsse +Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Flaschenverschlüsse Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Folienschneider -Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Getränkeshaker & Zubehör -Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Getränkeshaker & Zubehör > Bar-Eispickel -Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Getränkeshaker & Zubehör > Cocktailshaker -Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Getränkeshaker & Zubehör > Flaschenöffner -Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Getränkeshaker & Zubehör > Getränkezubehörsets +Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Getränkewannen & -kühler Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Glasuntersetzer & Bierdeckel Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Korkenzieher +Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Kühlsteine & -Sticks für Getränke Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Untersetzerhalter Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Weindekantierer +Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Weinflaschenhalter Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Weinglasanhänger -Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Weinkübel & -kühler -Heim & Garten > Küche & Esszimmer > Bar-Utensilien > Whisky-Steine Heim & Garten > Küche & Esszimmer > Essens- & Getränkebehälter -Heim & Garten > Küche & Esszimmer > Essens- & Getränkebehälter > Brotdosen & Tragetaschen +Heim & Garten > Küche & Esszimmer > Essens- & Getränkebehälter > Brotdosen & -taschen Heim & Garten > Küche & Esszimmer > Essens- & Getränkebehälter > Feldflaschen Heim & Garten > Küche & Esszimmer > Essens- & Getränkebehälter > Flachmänner Heim & Garten > Küche & Esszimmer > Essens- & Getränkebehälter > Getränkedeckel @@ -2298,6 +1910,7 @@ Heim & Garten > Küche & Esszimmer > Essens- & Getränkebehälter > Wasserflasch Heim & Garten > Küche & Esszimmer > Essens- & Getränkebehälter > Weinflaschentaschen Heim & Garten > Küche & Esszimmer > Essensaufbewahrung Heim & Garten > Küche & Esszimmer > Essensaufbewahrung - Zubehör +Heim & Garten > Küche & Esszimmer > Essensaufbewahrung - Zubehör > Etiketten für Lebensmittel und Getränke Heim & Garten > Küche & Esszimmer > Essensaufbewahrung - Zubehör > Sauerstoffabsorber Heim & Garten > Küche & Esszimmer > Essensaufbewahrung - Zubehör > Spender für Frischhaltefolie Heim & Garten > Küche & Esszimmer > Essensaufbewahrung - Zubehör > Verschlussstreifen & Verschlussclips @@ -2305,7 +1918,7 @@ Heim & Garten > Küche & Esszimmer > Essensaufbewahrung > Brotkästen & -taschen Heim & Garten > Küche & Esszimmer > Essensaufbewahrung > Frischhaltebeutel Heim & Garten > Küche & Esszimmer > Essensaufbewahrung > Honiggläser Heim & Garten > Küche & Esszimmer > Essensaufbewahrung > Kecksdosen & -behälter -Heim & Garten > Küche & Esszimmer > Essensaufbewahrung > Lebensmittelabdeckhauben +Heim & Garten > Küche & Esszimmer > Essensaufbewahrung > Lebensmittel-Abdeckhauben Heim & Garten > Küche & Esszimmer > Essensaufbewahrung > Lebensmittelbehälter Heim & Garten > Küche & Esszimmer > Essensaufbewahrung > Lebensmittelverpackungsmaterial Heim & Garten > Küche & Esszimmer > Essensaufbewahrung > Lebensmittelverpackungsmaterial > Alufolien @@ -2323,15 +1936,13 @@ Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backformen > Br Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backformen > Grillbleche mit Fettpfannen Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backformen > Kuchen- & Quichebackformen Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backformen > Kuchenbackformen -Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backformen > Muffin- & Cupcakebackformen +Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backformen > Muffin- & Gebäckformen Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backformen > Pizzableche Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backformen > Pizzasteine -Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backformen > Spezialbackformen Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backzubehör Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backzubehör > Backgewichte Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backzubehör > Backmatten & -formen Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backzubehör > Brätereinsätze -Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Backzubehör > Papierbackförmchen Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr & Backformen (Kombisets) Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Auflaufformen & Kasserollen @@ -2344,12 +1955,11 @@ Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Sautépfannen Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Schmortöpfe Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Schnellkochtöpfe -Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Schnellkochtöpfe > Elektrische Schnellkochtöpfe Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Stiel- & Bratpfannen Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Stieltöpfe Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Suppentöpfe -Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Tajines -Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Wasserbadtopf +Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Tajines & Tonkochtöpfe +Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Wasserbadtöpfe Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Wasserkessel Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirr > Woks Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirrzubehör @@ -2360,92 +1970,78 @@ Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirrzub Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirrzubehör > Wokzubehör Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirrzubehör > Wokzubehör > Wokbürsten Heim & Garten > Küche & Esszimmer > Kochgeschirr & Backformen > Kochgeschirrzubehör > Wokzubehör > Wokringe +Heim & Garten > Küche & Esszimmer > Küchenblöcke & Pantryküchen Heim & Garten > Küche & Esszimmer > Küchengeräte Heim & Garten > Küche & Esszimmer > Küchengeräte > Abfallzerkleinerer Heim & Garten > Küche & Esszimmer > Küchengeräte > Abzugshauben -Heim & Garten > Küche & Esszimmer > Küchengeräte > Allesschneider -Heim & Garten > Küche & Esszimmer > Küchengeräte > Allesschneider > Aufschnittmaschine +Heim & Garten > Küche & Esszimmer > Küchengeräte > Aufschnittmaschine Heim & Garten > Küche & Esszimmer > Küchengeräte > Außengrills -Heim & Garten > Küche & Esszimmer > Küchengeräte > Außengrills > Gasgrills -Heim & Garten > Küche & Esszimmer > Küchengeräte > Außengrills > Holzkohlegrills Heim & Garten > Küche & Esszimmer > Küchengeräte > Backöfen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Backöfen > Durchlauföfen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Backöfen > Etagenöfen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Backöfen > Wandöfen Heim & Garten > Küche & Esszimmer > Küchengeräte > Brotbackautomaten Heim & Garten > Küche & Esszimmer > Küchengeräte > Campingkocher -Heim & Garten > Küche & Esszimmer > Küchengeräte > Crêpe-Geräte Heim & Garten > Küche & Esszimmer > Küchengeräte > Dörrautomaten -Heim & Garten > Küche & Esszimmer > Küchengeräte > Eis-Shaver +Heim & Garten > Küche & Esszimmer > Küchengeräte > Eis-Crusher & Eis-Shaver Heim & Garten > Küche & Esszimmer > Küchengeräte > Eismaschinen Heim & Garten > Küche & Esszimmer > Küchengeräte > Elektrische Pfannen & Woks Heim & Garten > Küche & Esszimmer > Küchengeräte > Elektrogrills & -bratplatten -Heim & Garten > Küche & Esszimmer > Küchengeräte > Elektrogrills & -bratplatten > Elektrische Grillpfannen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Elektrogrills & -bratplatten > Elektrogrills Heim & Garten > Küche & Esszimmer > Küchengeräte > Entsafter -Heim & Garten > Küche & Esszimmer > Küchengeräte > Entsafter > Handentsafter -Heim & Garten > Küche & Esszimmer > Küchengeräte > Fonduesets +Heim & Garten > Küche & Esszimmer > Küchengeräte > Fonduetöpfe & -Sets Heim & Garten > Küche & Esszimmer > Küchengeräte > Fritteusen Heim & Garten > Küche & Esszimmer > Küchengeräte > Gasgrillplatten Heim & Garten > Küche & Esszimmer > Küchengeräte > Gefrierschränke & -truhen Heim & Garten > Küche & Esszimmer > Küchengeräte > Geschirrspüler Heim & Garten > Küche & Esszimmer > Küchengeräte > Getränkewärmer +Heim & Garten > Küche & Esszimmer > Küchengeräte > Grillöfen & Mini-Backöfen Heim & Garten > Küche & Esszimmer > Küchengeräte > Heißgetränkebereiter Heim & Garten > Küche & Esszimmer > Küchengeräte > Joghurtmaschinen Heim & Garten > Küche & Esszimmer > Küchengeräte > Kaffee- & Espressomaschinen Heim & Garten > Küche & Esszimmer > Küchengeräte > Kaffee- & Espressomaschinen > Espressokannen Heim & Garten > Küche & Esszimmer > Küchengeräte > Kaffee- & Espressomaschinen > Espressomaschinen Heim & Garten > Küche & Esszimmer > Küchengeräte > Kaffee- & Espressomaschinen > Filterkaffeemaschinen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Kaffee- & Espressomaschinen > French Presses Heim & Garten > Küche & Esszimmer > Küchengeräte > Kaffee- & Espressomaschinen > Perkolatoren & Kaffeebrüher +Heim & Garten > Küche & Esszimmer > Küchengeräte > Kaffee- & Espressomaschinen > Pressstempelkannen Heim & Garten > Küche & Esszimmer > Küchengeräte > Kaffee- & Espressomaschinen > Vakuumbereiter Heim & Garten > Küche & Esszimmer > Küchengeräte > Kochfelder Heim & Garten > Küche & Esszimmer > Küchengeräte > Kochplatten Heim & Garten > Küche & Esszimmer > Küchengeräte > Kochtöpfe & Dampfkochtöpfe Heim & Garten > Küche & Esszimmer > Küchengeräte > Kochtöpfe & Dampfkochtöpfe > Dampfgarer Heim & Garten > Küche & Esszimmer > Küchengeräte > Kochtöpfe & Dampfkochtöpfe > Eierkocher -Heim & Garten > Küche & Esszimmer > Küchengeräte > Kochtöpfe & Dampfkochtöpfe > Langsamkocher Heim & Garten > Küche & Esszimmer > Küchengeräte > Kochtöpfe & Dampfkochtöpfe > Reiskocher +Heim & Garten > Küche & Esszimmer > Küchengeräte > Kochtöpfe & Dampfkochtöpfe > Schongarer Heim & Garten > Küche & Esszimmer > Küchengeräte > Kochtöpfe & Dampfkochtöpfe > Thermokocher Heim & Garten > Küche & Esszimmer > Küchengeräte > Kochtöpfe & Dampfkochtöpfe > Öfen zum Vakuumgaren Heim & Garten > Küche & Esszimmer > Küchengeräte > Küchenherde Heim & Garten > Küche & Esszimmer > Küchengeräte > Kühlschränke Heim & Garten > Küche & Esszimmer > Küchengeräte > Lebensmittel-Räucheröfen Heim & Garten > Küche & Esszimmer > Küchengeräte > Lebensmittelmühlen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Lebensmittelmühlen > Fleischwölfe -Heim & Garten > Küche & Esszimmer > Küchengeräte > Lebensmittelmühlen > Getreidemühlen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Lebensmittelmühlen > Gewürzmühlen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Lebensmittelmühlen > Kaffeemühlen Heim & Garten > Küche & Esszimmer > Küchengeräte > Messerschärfer Heim & Garten > Küche & Esszimmer > Küchengeräte > Mikrowellenherde Heim & Garten > Küche & Esszimmer > Küchengeräte > Milchaufschäumer +Heim & Garten > Küche & Esszimmer > Küchengeräte > Mixer & Pürierstäbe Heim & Garten > Küche & Esszimmer > Küchengeräte > Mochi-Maschinen Heim & Garten > Küche & Esszimmer > Küchengeräte > Müllpressen Heim & Garten > Küche & Esszimmer > Küchengeräte > Nudelmaschinen Heim & Garten > Küche & Esszimmer > Küchengeräte > Popcornmaschinen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Roaster Ovens & Mini-Backöfen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Rühr- & Küchenmaschinen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Rührgeräte -Heim & Garten > Küche & Esszimmer > Küchengeräte > Shlushmaschinen +Heim & Garten > Küche & Esszimmer > Küchengeräte > Slush-Eis-Maschinen Heim & Garten > Küche & Esszimmer > Küchengeräte > Sojamilchbereiter Heim & Garten > Küche & Esszimmer > Küchengeräte > Speiseeismaschinen Heim & Garten > Küche & Esszimmer > Küchengeräte > Speisewärmer +Heim & Garten > Küche & Esszimmer > Küchengeräte > Speisewärmer > Buffetwärmer +Heim & Garten > Küche & Esszimmer > Küchengeräte > Speisewärmer > Reiswärmer Heim & Garten > Küche & Esszimmer > Küchengeräte > Speisewärmer > Speisewärmelampen -Heim & Garten > Küche & Esszimmer > Küchengeräte > Speisewärmer > Warmhaltebehälter Heim & Garten > Küche & Esszimmer > Küchengeräte > Speisewärmer > Warmhaltetische -Heim & Garten > Küche & Esszimmer > Küchengeräte > Standmixer & Pürierstäbe Heim & Garten > Küche & Esszimmer > Küchengeräte > Teekocher Heim & Garten > Küche & Esszimmer > Küchengeräte > Temperiermaschinen für Schokolade Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills > Brezelbackautomat Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills > Donutmaker +Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills > Mini- & Toastöfen Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills > Muffin- & Cupcake-Maker Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills > Pizzaöfen Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills > Pizzelle-Eisen Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills > Quesadilla- & Tortilla-Maker Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills > Sandwichtoaster Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills > Toaster -Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills > Toastöfen Heim & Garten > Küche & Esszimmer > Küchengeräte > Toaster & Grills > Waffeleisen Heim & Garten > Küche & Esszimmer > Küchengeräte > Trinkwasserspender Heim & Garten > Küche & Esszimmer > Küchengeräte > Vakuumierer @@ -2461,7 +2057,6 @@ Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Allespicker & Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Ausstechförmchen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Backpinsel Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Backschaufeln -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Bonbon- & Pralinenformen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Bratenspritzen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Bratschläuche Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Dampfgarbeutel @@ -2480,25 +2075,25 @@ Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Fleischklopfe Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Fleischpistolen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Geschirrständer & Abtropfflächen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Getränkespender +Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Gewürzmühlen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Kartoffelpressen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Keksmaschine Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Kellen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Knoblauchpressen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Kochthermometer -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Kuchenverzierungsbedarf +Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Kuchen-Dekorationsbedarf Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Kurzzeitmesser & Eieruhren Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenformen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenmesser Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Aufbewahrungssysteme für Getränkepads Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Besteckkästen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Gewürzregale & -ständer Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Halter für Küchenutensilien Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Konservenregal -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Küchenkarusselle Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Küchenrollenhalter & -spender Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Küchenschranksysteme Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Messerblöcke & -halter +Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Organizer für Küchenutensilien Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Serviettenhalter & -spender Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Topfregale Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenorganizer > Trinkgefäßhalter @@ -2511,14 +2106,11 @@ Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenschabe Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenschaber > Teigspatel Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenscheren Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenschneidegeräte -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenschneidegeräte > Brotschneidemaschinen -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenschneidegeräte > Eierschneider -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenschneidegeräte > Gemüsehobel -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenschneidegeräte > Käsehobel -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenthermometer +Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenthermometer-Zubehör +Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchentrichter Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenutensilien-Sets -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenwaagen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Küchenzangen +Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Lebensmittelschablonen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Löffelablagen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Mehlsiebe Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Messbecher & Dosierlöffel @@ -2532,6 +2124,7 @@ Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Portionierer Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Portionierer > Eiscremeportionierer Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Portionierer > Eisportionierer Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Portionierer > Kugelausstecher +Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Portionierer > Popcorn- & Pommesschaufeln Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Reiben Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Rührschüsseln Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Salatschleudern @@ -2539,8 +2132,7 @@ Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Saucenspender Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Schaumlöffel Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Schneebesen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Schneidebretter -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Schneidemaschinen für Lebensmittel -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Schäler +Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Schäler & Entkerner Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Schürzen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Spezialbesteck & Nussknacker Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Spezialbesteck & Nussknacker > Hummer- & Krebszangen @@ -2549,7 +2141,6 @@ Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Spezialbestec Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Spülbeckenkörbe Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Spülbeckenmatten Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Stampfer -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Stieleisformen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Sushi-Matten Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Teesiebe Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Teigmischer @@ -2561,9 +2152,8 @@ Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Teigräder Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Teigtücher Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Topfhandschuhe & Topflappen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Tortenheber -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Würfelschneider & Zerkleinerungsgeräte Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Ziseliermesser -Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Zitruspressen +Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Zubehör für Gewürzmühlen Heim & Garten > Küche & Esszimmer > Küchenhelfer & -utensilien > Zuckerspender Heim & Garten > Küche & Esszimmer > Tafelgeschirr Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Geschirr @@ -2572,6 +2162,7 @@ Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Geschirr > Schüsseln Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Geschirr > Teller Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Kaffee- & Teekannen Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Kaffee- & Teeservices +Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Klemmen & Gewichte für Tischdecken Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Salz- & Pfefferstreuer Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Servierbesteck Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Servierbesteck > Bowlenschüsseluntersatz @@ -2593,145 +2184,115 @@ Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Tischbestecke Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Tischbestecke > Besteck-Sets Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Tischbestecke > Essstäbchen Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Tischbestecke > Essstäbchenzubehör -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Tischbestecke > Essstäbchenzubehör > Essstäbchenaufsätze -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Tischbestecke > Essstäbchenzubehör > Essstäbchenbank Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Tischbestecke > Gabeln Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Tischbestecke > Löffel Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Tischbestecke > Tafelmesser Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Topfuntersetzer Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Biergläser -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Biergläser > Krüge & Bierhumpen -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Biergläser > Pilsgläser -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Biergläser > Pint-Gläser Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Kaffee- & Teebecher Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Kaffee- und Teetassen -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Kaffee- und Teetassen > Kaffeetassen -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Kaffee- und Teetassen > Teetassen Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Kaffee- und Teeuntertassen Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Schnapsgläser Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Stielgläser -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Stielgläser > Kognakschwenker -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Stielgläser > Margarita-Gläser -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Stielgläser > Martini-Gläser -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Stielgläser > Sektgläser -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Stielgläser > Weingläser Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Trinkbehältersets Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Trinkgläser -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Trinkgläser > Highball-Gläser -Heim & Garten > Küche & Esszimmer > Tafelgeschirr > Trinkgefäße > Trinkgläser > Tumbler Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Außengrillzubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Außengrillzubehör > Anzündkamine Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Außengrillzubehör > Außengrillabdeckungen Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Außengrillzubehör > Außengrillroste & -auflagen Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Außengrillzubehör > Außengrillspieße & -körbe -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Außengrillzubehör > Grillbrenner +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Außengrillzubehör > Ersatzteile für Außengrills Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Außengrillzubehör > Grillbretter Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Außengrillzubehör > Grillwagen Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Außengrillzubehör > Holzkohle Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Außengrillzubehör > Räucherchips & -Pellets -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Backofenzubehör -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Backofenzubehör > Backofeninnenverkleidungen -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Backofenzubehör > Backofenschutz -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Backofenzubehör > Ofenroste +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Dunstabzugshauben-Zubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Dörrautomatenzubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Dörrautomatenzubehör > Dörrautomatenbleche Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Dörrautomatenzubehör > Dörrautomatentabletts -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Eis-Shaver-Zubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Entsafterzubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Fonduesetzubehör +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Fonduesetzubehör > Chafing Dish- & Fonduebrennpasten Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Fonduesetzubehör > Fonduegabeln Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Fonduesetzubehör > Fonduetopfständer Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Fritteusenzubehör -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Fritteusenzubehör > Fritteusenkörbe -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Gasherdzubehör -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Gasherdzubehör > Gasherdaufsätze -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Herdzubehör -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Herdzubehör > Herdabdeckungen -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Herdzubehör > Herdauffangwannen Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Küchenabfallzerkleinerer-Zubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Kühlschrankzubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Mikrowellenzubehör -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Mikrowellenzubehör > Mikrowellen Trim-Kits -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Mikrowellenzubehör > Mikrowellen-Schublade -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Mixerzubehör -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Mixerzubehör > Mixbecher -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Mixerzubehör > Mixerdeckel Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Nudelmaschinenzubehör -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Rührgerätezubehör -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Rührgerätezubehör > Knethaken für Rührgeräte -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Rührgerätezubehör > Rührbesen für Rührgeräte -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Rührgerätezubehör > Rührgeräteaufsätze -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Rührgerätezubehör > Rührgeräteaufsätze > Entsafteraufsätze -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Rührgerätezubehör > Rührgeräteaufsätze > Fleischwolfaufsätze -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Rührgerätezubehör > Rührgeräteaufsätze > Nudelmaschinenaufsätze -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Rührgerätezubehör > Schüsseln für Rührgeräte Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Speiseeismaschinenzubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Speiseeismaschinenzubehör > Gefrierschüsseln für Speiseeismaschinen Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Spülmaschinenzubehör -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Spülmaschinenzubehör > Spülmaschineneinsätze für Gläser Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Toasterzubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Vakuumiererzubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Vakuumiererzubehör > Vakuumierer-Beutel Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Wasserspenderzubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Wasserspenderzubehör > Flaschen für Wasserspender -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zerkleinererzubehör +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Weinkühlschrank-Zubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Brotbackautomaten Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Campingöfen +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Eis-Crusher & Eis-Shaver Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Eismaschinen -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Gefriertruhen +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Gefrierschränke +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Herdplatten, Backöfen und Küchenherde Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Joghurtmaschinen Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen > Aufschäumer -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen > Espressokocherteile +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen > Ersatzteile für Espresso- & Kaffeemaschinen Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen > Kaffeefilter Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen > Kaffeefilterkörbe Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen > Kaffeekannen Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen > Kaffeekannenwarmhalter +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen > Kaffeemühlen +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen > Kaffeemühlenzubehör Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen > Siebträger Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Kaffee- & Espressomaschinen > Wasserfilter für Kaffeemaschinen Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Lebensmittelmühlen -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Lebensmittelmühlen > Fleischwolfzubehör -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Lebensmittelmühlen > Kaffeemühlenzubehör -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Lebensmittelmühlen > Zubehör für Gewürzmühlen +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Mixer & Pürierstäbe Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Popcornmaschinen +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Trinkwassersprudler +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Waffeleisen Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Warmhaltetheken Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Warmhaltetheken > Behälter für Warmhaltetheken Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Warmhaltetheken > Deckel für Warmhaltebehälter +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Wasserkocher Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für Zuckerwattemaschinen -Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für elektrische Töpfe & Woks +Heim & Garten > Küche & Esszimmer > Zubehör für Küchengeräte > Zubehör für elektrische Pfannen & Woks Heim & Garten > Notfallvorsorge Heim & Garten > Notfallvorsorge > Erdbebenmelder Heim & Garten > Notfallvorsorge > Möbelanker +Heim & Garten > Notfallvorsorge > Notfalldecken Heim & Garten > Notfallvorsorge > Notfallwerkzeuge Heim & Garten > Notfallvorsorge > Notvorräte Heim & Garten > Pflanzen Heim & Garten > Pflanzen > Blumen Heim & Garten > Pflanzen > Bäume Heim & Garten > Pflanzen > Grünpflanzen, Büsche & Sträucher -Heim & Garten > Pflanzen > Grünpflanzen, Büsche & Sträucher > Gartenpflanzen -Heim & Garten > Pflanzen > Grünpflanzen, Büsche & Sträucher > Zimmerpflanzen +Heim & Garten > Pflanzen > Pflanzen- & Kräuteranbausets Heim & Garten > Pflanzen > Saatgut Heim & Garten > Pflanzen > Wasserpflanzen Heim & Garten > Pool & Spa Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Badeinseln & Schwimmliegen -Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Badeinseln & Schwimmliegen > Baby-Luftmatratzen & Baby-Schwimmringe Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Badespielzeug +Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Chlorgeneratoren für Pools und Whirlpools Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Mechanische & Chemische Poolreiniger Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Pool- & Whirlpool-Filter -Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Poolabdeckungen +Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Poolabdeckungen & -unterlagen Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Poolbürsten -Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Poolleitern +Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Poolfolien +Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Poolleitern, -treppen & -rampen Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Poolreinigungsgeräte Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Poolskimmer -Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Poolsysteme Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Schläuche für Poolreiniger -Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Schwimmflügel +Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Sonnendecks Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Sprungbretter Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Swimmingpoolheizungen +Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Wartungssets für Pools & Whirlpools Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Wasserrutschen +Heim & Garten > Pool & Spa > Pool- & Whirlpool-Zubehör > Zubehör für Poolabdeckungen Heim & Garten > Pool & Spa > Pools Heim & Garten > Pool & Spa > Saunen Heim & Garten > Pool & Spa > Spa-Becken @@ -2741,12 +2302,12 @@ Heim & Garten > Rasen & Garten > Bewässerungssysteme > Gartenschlauchsprühköp Heim & Garten > Rasen & Garten > Bewässerungssysteme > Gartenschlauchzubehör & -ventile Heim & Garten > Rasen & Garten > Bewässerungssysteme > Gartenschläuche Heim & Garten > Rasen & Garten > Bewässerungssysteme > Gießkannen -Heim & Garten > Rasen & Garten > Bewässerungssysteme > Sprinkler & Köpfe -Heim & Garten > Rasen & Garten > Bewässerungssysteme > Sprinkler & Köpfe > Sprinkler -Heim & Garten > Rasen & Garten > Bewässerungssysteme > Sprinkler & Köpfe > Sprinklerköpfe +Heim & Garten > Rasen & Garten > Bewässerungssysteme > Sprinkler & -köpfe Heim & Garten > Rasen & Garten > Bewässerungssysteme > Sprinklersysteme Heim & Garten > Rasen & Garten > Bewässerungssysteme > Sprinklersysteme > Sprinklerventile Heim & Garten > Rasen & Garten > Bewässerungssysteme > Sprinklersysteme > Steuerelemente für Sprinkleranlagen +Heim & Garten > Rasen & Garten > Bewässerungssysteme > Wasserspender für Pflanzen +Heim & Garten > Rasen & Garten > Bewässerungssysteme > Zubehör für Gießkannen Heim & Garten > Rasen & Garten > Garten & Balkon Heim & Garten > Rasen & Garten > Garten & Balkon > Gartenhäuschen & Gartenbauten Heim & Garten > Rasen & Garten > Garten & Balkon > Gartenhäuschen & Gartenbauten > Bögen, Gitter, Laubengänge & Pergolen @@ -2758,43 +2319,42 @@ Heim & Garten > Rasen & Garten > Garten & Balkon > Gartenhäuschen & Gartenbaute Heim & Garten > Rasen & Garten > Garten & Balkon > Gartenhäuschen & Gartenbauten > Pavillon-Zubehör > Seitenwand-Sets für Pavillons Heim & Garten > Rasen & Garten > Garten & Balkon > Gartenhäuschen & Gartenbauten > Pavillon-Zubehör > Stangen für Sonnendächer Heim & Garten > Rasen & Garten > Garten & Balkon > Gartenhäuschen & Gartenbauten > Pavillons & Gartenlauben -Heim & Garten > Rasen & Garten > Garten & Balkon > Gartenhäuschen & Gartenbauten > Schuppen & Lagerung +Heim & Garten > Rasen & Garten > Garten & Balkon > Gartenhäuschen & Gartenbauten > Schuppen, Garagen & Carports Heim & Garten > Rasen & Garten > Garten & Balkon > Hollywoodschaukeln Heim & Garten > Rasen & Garten > Garten & Balkon > Hängematten Heim & Garten > Rasen & Garten > Garten & Balkon > Hängemattenzubehör +Heim & Garten > Rasen & Garten > Garten & Balkon > Markisen +Heim & Garten > Rasen & Garten > Garten & Balkon > Markisen-Zubehör Heim & Garten > Rasen & Garten > Garten & Balkon > Outdoor-Decken Heim & Garten > Rasen & Garten > Garten & Balkon > Outdoor-Decken > Picknickdecken Heim & Garten > Rasen & Garten > Garten & Balkon > Outdoor-Decken > Poncholiner Heim & Garten > Rasen & Garten > Garten & Balkon > Outdoor-Decken > Strandmatten -Heim & Garten > Rasen & Garten > Garten & Balkon > Sonnenschirm- und Markisenzubehör -Heim & Garten > Rasen & Garten > Garten & Balkon > Sonnenschirm- und Markisenzubehör > Beleuchtung für Sonnenschirme -Heim & Garten > Rasen & Garten > Garten & Balkon > Sonnenschirm- und Markisenzubehör > Sonnenschirmschutzhüllen -Heim & Garten > Rasen & Garten > Garten & Balkon > Sonnenschirm- und Markisenzubehör > Sonnenschirmstoff -Heim & Garten > Rasen & Garten > Garten & Balkon > Sonnenschirm- und Markisenzubehör > Sonnenschirmständer Heim & Garten > Rasen & Garten > Garten & Balkon > Sonnenschirme +Heim & Garten > Rasen & Garten > Garten & Balkon > Zubehör für Hollywoodschaukeln +Heim & Garten > Rasen & Garten > Garten & Balkon > Zubehör für Sonnenschirme +Heim & Garten > Rasen & Garten > Garten & Balkon > Zubehör für Sonnenschirme > Sonnenschirmabdeckungen +Heim & Garten > Rasen & Garten > Garten & Balkon > Zubehör für Sonnenschirme > Sonnenschirmbeleuchtung +Heim & Garten > Rasen & Garten > Garten & Balkon > Zubehör für Sonnenschirme > Sonnenschirmschutzhüllen +Heim & Garten > Rasen & Garten > Garten & Balkon > Zubehör für Sonnenschirme > Sonnenschirmstoff +Heim & Garten > Rasen & Garten > Garten & Balkon > Zubehör für Sonnenschirme > Sonnenschirmständer Heim & Garten > Rasen & Garten > Gartenbau +Heim & Garten > Rasen & Garten > Gartenbau > Blumenkäfige & Zubehör Heim & Garten > Rasen & Garten > Gartenbau > Blumentöpfe & Pflanzgefäße Heim & Garten > Rasen & Garten > Gartenbau > Dünger +Heim & Garten > Rasen & Garten > Gartenbau > Gartentopfuntersetzer und Trays Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Astsägen Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Baumscheren -Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Gartenbauutensilien -Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Gartenbauutensilien > Gartenhacken -Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Gartensicheln +Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Gartensicheln & Macheten Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Geräte zum Pflanzen von Knollen -Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Hacken & Spitzhacken -Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Haumesser -Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Macheten Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Mistgabeln Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Pflanzkellen +Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Pflanzwerkzeuge Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Rasen- & Gartenspritzgeräte -Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Rasenkantentrimmer -Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Rasenlüfter +Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Rasenwalzen Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Rechen Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Schaufeln & Spaten Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Schubkarren -Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Schubkarrenteile -Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Strahlstöcke Heim & Garten > Rasen & Garten > Gartenbau > Gartenwerkzeuge > Streugeräte Heim & Garten > Rasen & Garten > Gartenbau > Gewächshäuser Heim & Garten > Rasen & Garten > Gartenbau > Herbizide @@ -2804,28 +2364,34 @@ Heim & Garten > Rasen & Garten > Gartenbau > Kompostieren > Komposter Heim & Garten > Rasen & Garten > Gartenbau > Kompostieren > Kompostlüfter Heim & Garten > Rasen & Garten > Gartenbau > Krankheitsbekämpfung Heim & Garten > Rasen & Garten > Gartenbau > Mulch +Heim & Garten > Rasen & Garten > Gartenbau > Pflanzeinsätze für Blumentöpfe und -kübel Heim & Garten > Rasen & Garten > Gartenbau > Pflanzenständer -Heim & Garten > Rasen & Garten > Gartenbau > Pflanzenstützen -Heim & Garten > Rasen & Garten > Gartenbau > Pflanzenstützstäbe & -gitter Heim & Garten > Rasen & Garten > Gartenbau > Regenfässer Heim & Garten > Rasen & Garten > Gartenbau > Sand & Gartenerde Heim & Garten > Rasen & Garten > Gartenbau > Unkrautvlies Heim & Garten > Rasen & Garten > Gartenbau > Unkrautvlies-Zubehör Heim & Garten > Rasen & Garten > Gartenbau > Unkrautvlies-Zubehör > Erdanker & -nägel -Heim & Garten > Rasen & Garten > Gartenbau > Unkrautvlies-Zubehör > Unkrautvlies - Gewebeband +Heim & Garten > Rasen & Garten > Gartenbau > Unkrautvlies-Zubehör > Garten - Gewebeband Heim & Garten > Rasen & Garten > Gartenbau > Zubehör für Gartenarbeit Heim & Garten > Rasen & Garten > Gartenbau > Zubehör für Gartenarbeit > Gartentaschen -Heim & Garten > Rasen & Garten > Gartenbau > Zubehör für Gartenarbeit > Kniekissen +Heim & Garten > Rasen & Garten > Gartenbau > Zubehör für Gartenarbeit > Rollsitze, Sitzhilfen & Kniekissen für Gartenarbeit Heim & Garten > Rasen & Garten > Gartenbau > Zubehör für Gartenarbeit > Umtopftische +Heim & Garten > Rasen & Garten > Gartenbau > Zubehör für Gartengeräte +Heim & Garten > Rasen & Garten > Gartenbau > Zubehör für Gartengeräte > Aufsätze für Gartengeräte +Heim & Garten > Rasen & Garten > Gartenbau > Zubehör für Gartengeräte > Griffe für Gartengeräte +Heim & Garten > Rasen & Garten > Gartenbau > Zubehör für Gartengeräte > Schubkarrenteile Heim & Garten > Rasen & Garten > Gartenmaschinen +Heim & Garten > Rasen & Garten > Gartenmaschinen > Basiseinheiten für elektrische Gartengeräte Heim & Garten > Rasen & Garten > Gartenmaschinen > Bodenbearbeitungsmaschinen Heim & Garten > Rasen & Garten > Gartenmaschinen > Elektrische Heckenscheren -Heim & Garten > Rasen & Garten > Gartenmaschinen > Elektrische Kombi-Ausstattung -Heim & Garten > Rasen & Garten > Gartenmaschinen > Elektrische Kombi-Gartenmaschinen +Heim & Garten > Rasen & Garten > Gartenmaschinen > Elektrische Kombi-Gartenmaschinen-Sets Heim & Garten > Rasen & Garten > Gartenmaschinen > Hochdruckreiniger +Heim & Garten > Rasen & Garten > Gartenmaschinen > Kehrmaschine Heim & Garten > Rasen & Garten > Gartenmaschinen > Kettensägen Heim & Garten > Rasen & Garten > Gartenmaschinen > Laubbläser Heim & Garten > Rasen & Garten > Gartenmaschinen > Laubsauger +Heim & Garten > Rasen & Garten > Gartenmaschinen > Rasenkantentrimmer +Heim & Garten > Rasen & Garten > Gartenmaschinen > Rasenlüfter und Vertikutierer Heim & Garten > Rasen & Garten > Gartenmaschinen > Rasenmäher Heim & Garten > Rasen & Garten > Gartenmaschinen > Rasenmäher > Aufsitzrasenmäher Heim & Garten > Rasen & Garten > Gartenmaschinen > Rasenmäher > Handrasenmäher @@ -2835,382 +2401,358 @@ Heim & Garten > Rasen & Garten > Gartenmaschinen > Rasentrimmer & Freischneider Heim & Garten > Rasen & Garten > Gartenmaschinen > Schneefräsen Heim & Garten > Rasen & Garten > Gartenmaschinen > Traktoren Heim & Garten > Rasen & Garten > Schnee- und Eisbeseitigung -Heim & Garten > Rasen & Garten > Schnee- und Eisbeseitigung > Eiskratzer +Heim & Garten > Rasen & Garten > Schnee- und Eisbeseitigung > Eiskratzer & Schneebesen Heim & Garten > Rasen & Garten > Schnee- und Eisbeseitigung > Schneeschaufeln Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Aufsätze für elektrische Kombi-Gartenmaschinen +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Aufsätze für elektrische Kombi-Gartenmaschinen > Fräsen- und Grubber-Aufsätze +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Aufsätze für elektrische Kombi-Gartenmaschinen > Heckenscheren-Aufsätze +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Aufsätze für elektrische Kombi-Gartenmaschinen > Laubbläser-Aufsätze +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Aufsätze für elektrische Kombi-Gartenmaschinen > Rasentrimmer-Aufsätze +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Aufsätze für elektrische Kombi-Gartenmaschinen > Stangensägen-Aufsätze +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Aufsätze für elektrische Kombi-Gartenmaschinen > Unkrauttrimmer-Aufsätze Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Batterien für elektrische Kombi-Gartenmaschinen -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Batterien für elektrische Kombi-Gartenmaschinen > Batterien für Heckenschneider -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Batterien für elektrische Kombi-Gartenmaschinen > Batterien für Unkrauttrimmer -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Batterien für elektrische Kombi-Gartenmaschinen > Rasenmäherbatterien Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Heckenscheren-Zubehör -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Heckenscheren-Zubehör > Heckenscheren-Aufsätze Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Hochdruckreiniger-Zubehör Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Kettensägenzubehör Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Kettensägenzubehör > Kettensägenketten Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Kettensägenzubehör > Kettensägenschwerter Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Laubbläser-Zubehör -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Laubbläser-Zubehör > Laubbläser-Erweiterungen +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Laubbläser-Zubehör > Laubbläser-Rohre +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Grasauffangbehälter +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Mulchkits für Rasenmäher +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Mulchstopfen für Rasenmäher +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Rasenkehrmaschinen +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Rasenmäher-Antriebsrollen +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Rasenmäher-Keilriemen +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Rasenmäher-Reifen +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Rasenmäher-Reifenschläuche +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Rasenmäher-Riemenscheiben +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Rasenmäher-Räder +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Rasenmäherabdeckungen +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Rasenmähermesser +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasenmäher-Zubehör > Wiesenmäher-Zusatzteile Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasentrimmer-Zubehör -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Rasentrimmer-Zubehör > Rasentrimmer-Aufsätze Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Schneefräsenzubehör Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Traktorteile & Zubehör +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Traktorteile & Zubehör > Traktorreifen +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Traktorteile & Zubehör > Traktorräder Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Unkrauttrimmer-Zubehör -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Unkrauttrimmer-Zubehör > Abdeckungen für Unkrauttrimmer-Kabelrollen -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Unkrauttrimmer-Zubehör > Messer & Kabelrollen für Unkrauttrimmer -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Unkrauttrimmer-Zubehör > Unkrauttrimmer-Aufsätze -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Zubehör für Rasenmäher -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Zubehör für Rasenmäher > Grasauffangbehälter -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Zubehör für Rasenmäher > Rasenkehrmaschinen -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Zubehör für Rasenmäher > Rasenmäher-Keilriemen -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Zubehör für Rasenmäher > Rasenmäher-Reifen -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Zubehör für Rasenmäher > Rasenmäher-Riemenscheiben -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Zubehör für Rasenmäher > Rasenmäher-Räder -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Zubehör für Rasenmäher > Rasenmäher-Zusatzteile -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Zubehör für Rasenmäher > Rasenmäherabdeckungen -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Zubehör für Rasenmäher > Rasenmähermesser -Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Zubehör für Rasenmäher > Wiesenmäher-Zusatzteile +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Unkrauttrimmer-Zubehör > Abdeckungen für Rasentrimmer-Kabelrollen +Heim & Garten > Rasen & Garten > Zubehör für Gartenmaschinen > Unkrauttrimmer-Zubehör > Messer & Kabelrollen für Rasentrimmer Heim & Garten > Rauchzubehör Heim & Garten > Rauchzubehör > Aschenbecher -Heim & Garten > Rauchzubehör > Feuerzeuge Heim & Garten > Rauchzubehör > Humidore Heim & Garten > Rauchzubehör > Humidorzubehör -Heim & Garten > Rauchzubehör > Streichhölzer +Heim & Garten > Rauchzubehör > Zigarettenhalter Heim & Garten > Rauchzubehör > Zigarettenkisten Heim & Garten > Rauchzubehör > Zigarrenkisten -Heim & Garten > Rauchzubehör > Zigarrenschneider +Heim & Garten > Rauchzubehör > Zigarrenschneider & -locher Heim & Garten > Schutzhüllen für Sonnenschirme Heim & Garten > Sonnen- & Regenschirme Heim & Garten > Zubehör für Kamine & Holzöfen Heim & Garten > Zubehör für Kamine & Holzöfen > Blasebälge +Heim & Garten > Zubehör für Kamine & Holzöfen > Feuerböcke Heim & Garten > Zubehör für Kamine & Holzöfen > Feuerholz & Brennstoffe Heim & Garten > Zubehör für Kamine & Holzöfen > Feuerholz-Aufbewahrung Heim & Garten > Zubehör für Kamine & Holzöfen > Holzofenventilatoren & -gebläse Heim & Garten > Zubehör für Kamine & Holzöfen > Kamin- & Holzofenroste Heim & Garten > Zubehör für Kamine & Holzöfen > Kaminbodenplatten -Heim & Garten > Zubehör für Kamine & Holzöfen > Kaminböcke Heim & Garten > Zubehör für Kamine & Holzöfen > Kamingitter Heim & Garten > Zubehör für Kamine & Holzöfen > Kaminreflektoren Heim & Garten > Zubehör für Kamine & Holzöfen > Kaminwerkzeug Heim & Garten > Zubehör für Kamine & Holzöfen > Zubehör für Feuerholz-Aufbewahrung Heimwerkerbedarf +Heimwerkerbedarf > Baumaterial +Heimwerkerbedarf > Baumaterial > Chemikalien +Heimwerkerbedarf > Baumaterial > Chemikalien > Abflussreiniger +Heimwerkerbedarf > Baumaterial > Chemikalien > Ammonium +Heimwerkerbedarf > Baumaterial > Chemikalien > Anzünderflüssigkeit +Heimwerkerbedarf > Baumaterial > Chemikalien > Beton- & Mauerreiniger +Heimwerkerbedarf > Baumaterial > Chemikalien > Enteiser +Heimwerkerbedarf > Baumaterial > Chemikalien > Holzterrassen- & -zaunreiniger +Heimwerkerbedarf > Baumaterial > Chemikalien > Kaminreiniger +Heimwerkerbedarf > Baumaterial > Chemikalien > Kältespray für Elektronik +Heimwerkerbedarf > Baumaterial > Chemikalien > Reiniger für Abwassertanks & Senkgruben +Heimwerkerbedarf > Baumaterial > Chemikalien > Säureneutralisierer +Heimwerkerbedarf > Baumaterial > Klebebänder +Heimwerkerbedarf > Baumaterial > Kraftklebstoffe & -leime +Heimwerkerbedarf > Baumaterial > Lösungsmittel, Abbeizmittel & Verdünner +Heimwerkerbedarf > Baumaterial > Lötmetall & Flussmittel +Heimwerkerbedarf > Baumaterial > Malermaterial +Heimwerkerbedarf > Baumaterial > Malermaterial > Farbe +Heimwerkerbedarf > Baumaterial > Malermaterial > Farbenbindemittel +Heimwerkerbedarf > Baumaterial > Malermaterial > Farbstoffe +Heimwerkerbedarf > Baumaterial > Malermaterial > Grundierungen +Heimwerkerbedarf > Baumaterial > Malermaterial > Lacke & Beschichtungen +Heimwerkerbedarf > Baumaterial > Maurermaterial +Heimwerkerbedarf > Baumaterial > Maurermaterial > Mauersteine & Betonblöcke +Heimwerkerbedarf > Baumaterial > Maurermaterial > Mörtel +Heimwerkerbedarf > Baumaterial > Maurermaterial > Zement, Mörtel & Beton +Heimwerkerbedarf > Baumaterial > Primer für Rohrverbindungen +Heimwerkerbedarf > Baumaterial > Schmiermittel +Heimwerkerbedarf > Baumaterial > Schutzbeschichtungen & Dichtmittel +Heimwerkerbedarf > Baumaterial > Wandausbesserungsmaterial & Gips Heimwerkerbedarf > Baumaterialien Heimwerkerbedarf > Baumaterialien > Arbeitsplatten -Heimwerkerbedarf > Baumaterialien > Arbeitsplatten > Granitarbeitsplatten -Heimwerkerbedarf > Baumaterialien > Arbeitsplatten > Hartholzarbeitsplatten -Heimwerkerbedarf > Baumaterialien > Arbeitsplatten > Keramik- & Porzellanarbeitsplatten -Heimwerkerbedarf > Baumaterialien > Arbeitsplatten > Kunststoffbeschichtete Arbeitsplatten -Heimwerkerbedarf > Baumaterialien > Arbeitsplatten > Marmorarbeitsplatten -Heimwerkerbedarf > Baumaterialien > Arbeitsplatten > Schichtstoffarbeitsplatten Heimwerkerbedarf > Baumaterialien > Bauholz +Heimwerkerbedarf > Baumaterialien > Bedachung +Heimwerkerbedarf > Baumaterialien > Bedachung > Dachrinnen +Heimwerkerbedarf > Baumaterialien > Bedachung > Dachschindeln & -pfannen +Heimwerkerbedarf > Baumaterialien > Bedachung > Kehlbleche +Heimwerkerbedarf > Baumaterialien > Bedachung > Zubehör für Dachrinnen +Heimwerkerbedarf > Baumaterialien > Betonstahl & -gitter Heimwerkerbedarf > Baumaterialien > Fenster +Heimwerkerbedarf > Baumaterialien > Fensterbauteile +Heimwerkerbedarf > Baumaterialien > Fensterbauteile > Fensterhebel +Heimwerkerbedarf > Baumaterialien > Fensterbauteile > Fensterrahmen Heimwerkerbedarf > Baumaterialien > Fensterläden -Heimwerkerbedarf > Baumaterialien > Fliesen -Heimwerkerbedarf > Baumaterialien > Fliesen > Fliesenböden -Heimwerkerbedarf > Baumaterialien > Fliesen > Geflieste Arbeitsplatten -Heimwerkerbedarf > Baumaterialien > Fußböden -Heimwerkerbedarf > Baumaterialien > Fußböden > Granitböden -Heimwerkerbedarf > Baumaterialien > Fußböden > Hartholzböden -Heimwerkerbedarf > Baumaterialien > Fußböden > Kautschukbeläge -Heimwerkerbedarf > Baumaterialien > Fußböden > Keramik- & Porzellanböden -Heimwerkerbedarf > Baumaterialien > Fußböden > Künstlicher Torf -Heimwerkerbedarf > Baumaterialien > Fußböden > Laminatböden -Heimwerkerbedarf > Baumaterialien > Fußböden > Marmorböden -Heimwerkerbedarf > Baumaterialien > Fußböden > Vinylböden +Heimwerkerbedarf > Baumaterialien > Fußböden & Teppichböden +Heimwerkerbedarf > Baumaterialien > Gipskartonplatten Heimwerkerbedarf > Baumaterialien > Glas +Heimwerkerbedarf > Baumaterialien > Handläufe und Geländersysteme +Heimwerkerbedarf > Baumaterialien > Hausverkleidung +Heimwerkerbedarf > Baumaterialien > Isolierung Heimwerkerbedarf > Baumaterialien > Leisten Heimwerkerbedarf > Baumaterialien > Luken -Heimwerkerbedarf > Baumaterialien > Mauersteine, Steine & Beton Heimwerkerbedarf > Baumaterialien > Schalldämpfende Materialien -Heimwerkerbedarf > Baumaterialien > Teppichböden Heimwerkerbedarf > Baumaterialien > Treppen +Heimwerkerbedarf > Baumaterialien > Türbeschläge & -klingeln +Heimwerkerbedarf > Baumaterialien > Türbeschläge & -klingeln > Schlossfallen +Heimwerkerbedarf > Baumaterialien > Türbeschläge & -klingeln > Schlüssellochverzierungen für Türen +Heimwerkerbedarf > Baumaterialien > Türbeschläge & -klingeln > Türdruckplatten +Heimwerkerbedarf > Baumaterialien > Türbeschläge & -klingeln > Türklingeln +Heimwerkerbedarf > Baumaterialien > Türbeschläge & -klingeln > Türklopfer +Heimwerkerbedarf > Baumaterialien > Türbeschläge & -klingeln > Türknäufe & -klinken +Heimwerkerbedarf > Baumaterialien > Türbeschläge & -klingeln > Türrahmen +Heimwerkerbedarf > Baumaterialien > Türbeschläge & -klingeln > Türschließer +Heimwerkerbedarf > Baumaterialien > Türbeschläge & -klingeln > Türstopper Heimwerkerbedarf > Baumaterialien > Türen Heimwerkerbedarf > Baumaterialien > Türen > Garagentore Heimwerkerbedarf > Baumaterialien > Türen > Haustüren -Heimwerkerbedarf > Baumaterialien > Türen > Haustüren > Fliegengittertüren -Heimwerkerbedarf > Baumaterialien > Türen > Sonstige Türen -Heimwerkerbedarf > Baumaterialien > Türen > Sonstige Türen > Haustiertüren +Heimwerkerbedarf > Baumaterialien > Wand- & Deckenfliesen Heimwerkerbedarf > Baumaterialien > Wandverkleidung +Heimwerkerbedarf > Baumaterialien > Zugluftstopper, Fenster- & Türdichtungen Heimwerkerbedarf > Bauzubehör Heimwerkerbedarf > Bauzubehör > Abdeckfolien +Heimwerkerbedarf > Bauzubehör > Befestigungselemente +Heimwerkerbedarf > Bauzubehör > Befestigungselemente > Dübel +Heimwerkerbedarf > Bauzubehör > Befestigungselemente > Gewindestangen +Heimwerkerbedarf > Bauzubehör > Befestigungselemente > Nieten +Heimwerkerbedarf > Bauzubehör > Befestigungselemente > Nägel +Heimwerkerbedarf > Bauzubehör > Befestigungselemente > Schrauben +Heimwerkerbedarf > Bauzubehör > Befestigungselemente > Schrauben & Muttern +Heimwerkerbedarf > Bauzubehör > Befestigungselemente > Schraubverbinder +Heimwerkerbedarf > Bauzubehör > Befestigungselemente > Unterlegscheiben Heimwerkerbedarf > Bauzubehör > Betonformen -Heimwerkerbedarf > Bauzubehör > Bindegarne -Heimwerkerbedarf > Bauzubehör > Draht -Heimwerkerbedarf > Bauzubehör > Dübel +Heimwerkerbedarf > Bauzubehör > Bodenspieße +Heimwerkerbedarf > Bauzubehör > Federn Heimwerkerbedarf > Bauzubehör > Filter & Siebe -Heimwerkerbedarf > Bauzubehör > Flaschenzüge Heimwerkerbedarf > Bauzubehör > Gasschläuche +Heimwerkerbedarf > Bauzubehör > Haken, Schnallen & Verschlüsse +Heimwerkerbedarf > Bauzubehör > Haken, Schnallen & Verschlüsse > Gear Tie-Universalkabelbinder +Heimwerkerbedarf > Bauzubehör > Haken, Schnallen & Verschlüsse > Kettenverbinder & -glieder +Heimwerkerbedarf > Bauzubehör > Haken, Schnallen & Verschlüsse > Lasthaken, Trageklemmen & Schäkel +Heimwerkerbedarf > Bauzubehör > Haken, Schnallen & Verschlüsse > Schnallen- & Steckschnallen Heimwerkerbedarf > Bauzubehör > Halterungen -Heimwerkerbedarf > Bauzubehör > Holzdübel & -stäbe -Heimwerkerbedarf > Bauzubehör > Holzplättchen -Heimwerkerbedarf > Bauzubehör > Ketten -Heimwerkerbedarf > Bauzubehör > Klebebänder -Heimwerkerbedarf > Bauzubehör > Klebebänder > Abdeckband -Heimwerkerbedarf > Bauzubehör > Klebebänder > Folienklebeband -Heimwerkerbedarf > Bauzubehör > Klebebänder > Gafferband -Heimwerkerbedarf > Bauzubehör > Klebebänder > Gewebeband -Heimwerkerbedarf > Bauzubehör > Klebebänder > Heizungsband -Heimwerkerbedarf > Bauzubehör > Klebebänder > Isolierband -Heimwerkerbedarf > Bauzubehör > Klebebänder > Klebe- & Gitterband für Gipskartonplatten -Heimwerkerbedarf > Bauzubehör > Klebebänder > Kupferband -Heimwerkerbedarf > Bauzubehör > Klebebänder > Teflonband -Heimwerkerbedarf > Bauzubehör > Luftdurchlässe & Abzugsrohre +Heimwerkerbedarf > Bauzubehör > Ketten, Drähte & Seile +Heimwerkerbedarf > Bauzubehör > Ketten, Drähte & Seile > Bindegarne +Heimwerkerbedarf > Bauzubehör > Ketten, Drähte & Seile > Draht +Heimwerkerbedarf > Bauzubehör > Ketten, Drähte & Seile > Ketten +Heimwerkerbedarf > Bauzubehör > Ketten, Drähte & Seile > Seile +Heimwerkerbedarf > Bauzubehör > Ketten, Drähte & Seile > Spanngummis +Heimwerkerbedarf > Bauzubehör > Ketten, Drähte & Seile > Spanngurte +Heimwerkerbedarf > Bauzubehör > Ketten, Drähte & Seile > Zugschalterketten und -kordeln +Heimwerkerbedarf > Bauzubehör > Laufrollen Heimwerkerbedarf > Bauzubehör > Luftschläuche -Heimwerkerbedarf > Bauzubehör > Lötmetall & Flussmittel -Heimwerkerbedarf > Bauzubehör > Nieten -Heimwerkerbedarf > Bauzubehör > Nägel +Heimwerkerbedarf > Bauzubehör > Metallgussformen Heimwerkerbedarf > Bauzubehör > Planen -Heimwerkerbedarf > Bauzubehör > Riegel +Heimwerkerbedarf > Bauzubehör > Richtstützen Heimwerkerbedarf > Bauzubehör > Scharniere -Heimwerkerbedarf > Bauzubehör > Schmiermittel Heimwerkerbedarf > Bauzubehör > Schmierschläuche Heimwerkerbedarf > Bauzubehör > Schrankbauteile +Heimwerkerbedarf > Bauzubehör > Schrankbauteile > Schlüssellochverzierungen für Schränke Heimwerkerbedarf > Bauzubehör > Schrankbauteile > Schrankbeschläge Heimwerkerbedarf > Bauzubehör > Schrankbauteile > Schrankknäufe & -griffe Heimwerkerbedarf > Bauzubehör > Schrankbauteile > Schrankscharniere -Heimwerkerbedarf > Bauzubehör > Schrankbauteile > Schrankschlösser Heimwerkerbedarf > Bauzubehör > Schrankbauteile > Schranktüren -Heimwerkerbedarf > Bauzubehör > Schrauben -Heimwerkerbedarf > Bauzubehör > Schrauben & Muttern -Heimwerkerbedarf > Bauzubehör > Schrauben > Gipskartonschrauben -Heimwerkerbedarf > Bauzubehör > Schrauben > Holzschrauben -Heimwerkerbedarf > Bauzubehör > Schrauben > Metallschrauben -Heimwerkerbedarf > Bauzubehör > Schraubverbinder -Heimwerkerbedarf > Bauzubehör > Seile -Heimwerkerbedarf > Bauzubehör > Spanngummis -Heimwerkerbedarf > Bauzubehör > Spanngurte +Heimwerkerbedarf > Bauzubehör > Schubladenschienen Heimwerkerbedarf > Bauzubehör > Spulen -Heimwerkerbedarf > Bauzubehör > Steuerungen -Heimwerkerbedarf > Bauzubehör > Steuerungen > Heizungs- & Klimaanlagen Steuerungen -Heimwerkerbedarf > Bauzubehör > Steuerungen > Schalt- & Bedienungstafeln -Heimwerkerbedarf > Bauzubehör > Steuerungen > Thermostate -Heimwerkerbedarf > Bauzubehör > Türbeschläge & -klingeln -Heimwerkerbedarf > Bauzubehör > Türbeschläge & -klingeln > Schlossfallen -Heimwerkerbedarf > Bauzubehör > Türbeschläge & -klingeln > Türklingeln -Heimwerkerbedarf > Bauzubehör > Türbeschläge & -klingeln > Türklopfer -Heimwerkerbedarf > Bauzubehör > Türbeschläge & -klingeln > Türknäufe & -klinken -Heimwerkerbedarf > Bauzubehör > Türbeschläge & -klingeln > Türschließer -Heimwerkerbedarf > Bauzubehör > Türbeschläge & -klingeln > Türstopper -Heimwerkerbedarf > Bauzubehör > Unterlegscheiben +Heimwerkerbedarf > Bauzubehör > Standfüße für Pfosten +Heimwerkerbedarf > Bauzubehör > Transport- und Schallschutzabdeckungen Heimwerkerbedarf > Bauzubehör > Warnklebeband Heimwerkerbedarf > Bauzubehör > Werkzeugaufbewahrung & Ordnungssysteme Heimwerkerbedarf > Bauzubehör > Werkzeugaufbewahrung & Ordnungssysteme > Dienst- & Werkzeuggürtel +Heimwerkerbedarf > Bauzubehör > Werkzeugaufbewahrung & Ordnungssysteme > Einsätze zur Werkzeugaufbewahrung Heimwerkerbedarf > Bauzubehör > Werkzeugaufbewahrung & Ordnungssysteme > Gartenschlauchhalterungen Heimwerkerbedarf > Bauzubehör > Werkzeugaufbewahrung & Ordnungssysteme > Werkbänke Heimwerkerbedarf > Bauzubehör > Werkzeugaufbewahrung & Ordnungssysteme > Werkzeugetuis Heimwerkerbedarf > Bauzubehör > Werkzeugaufbewahrung & Ordnungssysteme > Werkzeugkästen Heimwerkerbedarf > Bauzubehör > Werkzeugaufbewahrung & Ordnungssysteme > Werkzeugschränke Heimwerkerbedarf > Bauzubehör > Werkzeugaufbewahrung & Ordnungssysteme > Werkzeugtaschen -Heimwerkerbedarf > Bauzubehör > Zugschalterketten und -kordeln -Heimwerkerbedarf > Bedachung -Heimwerkerbedarf > Bedachung > Dachrinnen -Heimwerkerbedarf > Bedachung > Kehlbleche -Heimwerkerbedarf > Bedachung > Zubehör für Dachrinnen -Heimwerkerbedarf > Chemikalien -Heimwerkerbedarf > Chemikalien > Abflussreiniger -Heimwerkerbedarf > Chemikalien > Ammonium -Heimwerkerbedarf > Chemikalien > Beton- & Mauerreiniger -Heimwerkerbedarf > Chemikalien > Enteiser -Heimwerkerbedarf > Chemikalien > Holzterrassen- & -zaunreiniger -Heimwerkerbedarf > Chemikalien > Kaminreiniger -Heimwerkerbedarf > Chemikalien > Reiniger für Abwassertanks & Senkgruben -Heimwerkerbedarf > Chemikalien > Säureneutralisierer -Heimwerkerbedarf > Elektrikbedarf -Heimwerkerbedarf > Elektrikbedarf > Anschlussdosen & Halterungen -Heimwerkerbedarf > Elektrikbedarf > Drahtbindemaschinen -Heimwerkerbedarf > Elektrikbedarf > Drahtspulen -Heimwerkerbedarf > Elektrikbedarf > Elektronische Leitungen -Heimwerkerbedarf > Elektrikbedarf > Elektroschalter -Heimwerkerbedarf > Elektrikbedarf > Elektroschalter > Lichtschalter -Heimwerkerbedarf > Elektrikbedarf > Laderegler -Heimwerkerbedarf > Elektrikbedarf > Relais -Heimwerkerbedarf > Elektrikbedarf > Schrumpfschläuche -Heimwerkerbedarf > Elektrikbedarf > Steckdosen -Heimwerkerbedarf > Elektrikbedarf > Steckdosenabdeckungen -Heimwerkerbedarf > Elektrikbedarf > Verlängerungskabel -Heimwerkerbedarf > Elektrikbedarf > Verlängerungskabelzubehör -Heimwerkerbedarf > Elektrikbedarf > Verlängerungskabelzubehör > Kabeltrommeln für Verlängerungskabel -Heimwerkerbedarf > Elektrikbedarf > Verlängerungskabelzubehör > Verlängerungskabelstecker -Heimwerkerbedarf > Elektrikbedarf > Verteilerkästen -Heimwerkerbedarf > Elektrikbedarf > Vorschaltgeräte & Starter -Heimwerkerbedarf > Elektrikbedarf > Wandplatten -Heimwerkerbedarf > Elektrikbedarf > Zeitschaltuhren -Heimwerkerbedarf > Elektrikbedarf > Überbrückungsschalter -Heimwerkerbedarf > Erneuerbare Energie -Heimwerkerbedarf > Erneuerbare Energie > Solarenergie -Heimwerkerbedarf > Erneuerbare Energie > Solarenergie > Solarenergie-Kits -Heimwerkerbedarf > Erneuerbare Energie > Solarenergie > Solarpaneele -Heimwerkerbedarf > Erneuerbare Energie > Windkraft -Heimwerkerbedarf > Erneuerbare Energie > Windkraft > Windturbinen -Heimwerkerbedarf > Generatoren +Heimwerkerbedarf > Bauzubehör > Zylinderstifte & Dübelstangen +Heimwerkerbedarf > Brennstoffbehälter & -tanks +Heimwerkerbedarf > Elektrobedarf +Heimwerkerbedarf > Elektrobedarf > Anker, Rotoren und Statoren +Heimwerkerbedarf > Elektrobedarf > Anschlussdosen & Halterungen +Heimwerkerbedarf > Elektrobedarf > Drahtbindemaschinen +Heimwerkerbedarf > Elektrobedarf > Elektrische Leitungen +Heimwerkerbedarf > Elektrobedarf > Elektromotoren +Heimwerkerbedarf > Elektrobedarf > Elektroschalter +Heimwerkerbedarf > Elektrobedarf > Elektroschalter > Lichtschalter +Heimwerkerbedarf > Elektrobedarf > Elektroschalter > Spezielle elektrische Schalter +Heimwerkerbedarf > Elektrobedarf > Generatoren +Heimwerkerbedarf > Elektrobedarf > Gerätestecker +Heimwerkerbedarf > Elektrobedarf > Heim- & Gebäudeautomatisierung +Heimwerkerbedarf > Elektrobedarf > Kabelanschlüsse & Steckverbinder +Heimwerkerbedarf > Elektrobedarf > Kohlebürsten +Heimwerkerbedarf > Elektrobedarf > Leitungen & Gehäuse +Heimwerkerbedarf > Elektrobedarf > Leitungen & Gehäuse > Kabelkanäle +Heimwerkerbedarf > Elektrobedarf > Leitungen & Gehäuse > Schrumpfschläuche +Heimwerkerbedarf > Elektrobedarf > Schaltnetzteile +Heimwerkerbedarf > Elektrobedarf > Solarenergie-Kits +Heimwerkerbedarf > Elektrobedarf > Solarpaneele +Heimwerkerbedarf > Elektrobedarf > Spannungswandler & -regler +Heimwerkerbedarf > Elektrobedarf > Steckdosen +Heimwerkerbedarf > Elektrobedarf > Steckdosen-Zeitschaltuhren & -Sensoren +Heimwerkerbedarf > Elektrobedarf > Steckdosenabdeckungen +Heimwerkerbedarf > Elektrobedarf > Telefon- & Internetbuchsen +Heimwerkerbedarf > Elektrobedarf > Verlängerungskabel +Heimwerkerbedarf > Elektrobedarf > Verlängerungskabelzubehör +Heimwerkerbedarf > Elektrobedarf > Verteilerkästen +Heimwerkerbedarf > Elektrobedarf > Vorschaltgeräte & Starter +Heimwerkerbedarf > Elektrobedarf > Wandplatten +Heimwerkerbedarf > Elektrobedarf > Wechselrichter +Heimwerkerbedarf > Elektrobedarf > Zubehör für Generatoren Heimwerkerbedarf > Hauseinzäunung +Heimwerkerbedarf > Hauseinzäunung > Absperrungen +Heimwerkerbedarf > Hauseinzäunung > Beeteinfassungen Heimwerkerbedarf > Hauseinzäunung > Tore +Heimwerkerbedarf > Hauseinzäunung > Zaunfelder +Heimwerkerbedarf > Hauseinzäunung > Zaungitter Heimwerkerbedarf > Hauseinzäunung > Zaunlatten -Heimwerkerbedarf > Hauseinzäunung > Zaunpfosten -Heimwerkerbedarf > Hauseinzäunung > Zäune -Heimwerkerbedarf > Hauseinzäunung > Zäune > Drahtzaun -Heimwerkerbedarf > Hauseinzäunung > Zäune > Drahtzaun > Maschendraht -Heimwerkerbedarf > Hauseinzäunung > Zäune > Drahtzaun > Stacheldraht -Heimwerkerbedarf > Isolierung -Heimwerkerbedarf > Isolierung > Fenster- & Türisolierung -Heimwerkerbedarf > Isolierung > Gebäudeisolierung -Heimwerkerbedarf > Isolierung > Rohrisolierung -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Beschichtungen -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Dichtungsmittel -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Fugendichtmittel -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Fugenmassen -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Leime -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Leime > Glitzerkleber -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Leime > Gummikleber -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Leime > Heißklebesticks -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Leime > Holzleim -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Leime > Klebestifte -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Leime > Sekundenkleber -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Leime > Weißleim -Heimwerkerbedarf > Leime, Beschichtungen & Dichtungsmittel > Zement -Heimwerkerbedarf > Möbelbau -Heimwerkerbedarf > Sanitärinstallationen -Heimwerkerbedarf > Sanitärinstallationen > Abflusszubehör -Heimwerkerbedarf > Sanitärinstallationen > Abflusszubehör > Abflussauskleidung bzw. -Abdichtung -Heimwerkerbedarf > Sanitärinstallationen > Abflusszubehör > Abflussgitter -Heimwerkerbedarf > Sanitärinstallationen > Abflusszubehör > Abflussreinigungswerkzeug -Heimwerkerbedarf > Sanitärinstallationen > Abflusszubehör > Kanal- & Abflussstäbe -Heimwerkerbedarf > Sanitärinstallationen > Abflusszubehör > Sanitärabläufe -Heimwerkerbedarf > Sanitärinstallationen > Brunnenbedarf -Heimwerkerbedarf > Sanitärinstallationen > Druckminderer -Heimwerkerbedarf > Sanitärinstallationen > Düsen -Heimwerkerbedarf > Sanitärinstallationen > Installationsrohre -Heimwerkerbedarf > Sanitärinstallationen > Installationsrohre > Rohrnippel -Heimwerkerbedarf > Sanitärinstallationen > Installationsrohre > Stahlrohre -Heimwerkerbedarf > Sanitärinstallationen > Installationsrohre > Steckverbindungsrohre -Heimwerkerbedarf > Sanitärinstallationen > Kraftstoffleitungen & Zubehör -Heimwerkerbedarf > Sanitärinstallationen > Kugelhähne & Verschlüsse -Heimwerkerbedarf > Sanitärinstallationen > Pumpen -Heimwerkerbedarf > Sanitärinstallationen > Reparatursätze -Heimwerkerbedarf > Sanitärinstallationen > Reparatursätze > Rohr-Reparatursätze -Heimwerkerbedarf > Sanitärinstallationen > Rohrreiniger -Heimwerkerbedarf > Sanitärinstallationen > Rohrverbindungsstücke -Heimwerkerbedarf > Sanitärinstallationen > Rohrverbindungsstücke > Flansche -Heimwerkerbedarf > Sanitärinstallationen > Rohrverbindungsstücke > Rohranschlüsse & Reduzierstücke -Heimwerkerbedarf > Sanitärinstallationen > Rohrverbindungsstücke > Rohrkappen & -verschlüsse -Heimwerkerbedarf > Sanitärinstallationen > Rohrverbindungsstücke > Rohrschellen -Heimwerkerbedarf > Sanitärinstallationen > Rohrverbindungsstücke > Rohrverbinder -Heimwerkerbedarf > Sanitärinstallationen > Rohrverbindungsstücke > Sanitärdichtungen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärarmaturen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärarmaturen > Gashähne -Heimwerkerbedarf > Sanitärinstallationen > Sanitärarmaturen > Schlauchhähne -Heimwerkerbedarf > Sanitärinstallationen > Sanitärarmaturen > Wasserhähne -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Badarmaturen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Badarmaturen > Wasserhähne zum Vorspülen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Badewannen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Brauseeckventile -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Duschgriffe -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Duschkabinen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Duschkabinen & -türen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Duschköpfe -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Duschpaneele -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Duschschläuche -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Duschvorhangstangen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Duschwannen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Duschwände -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Stangenhalterungen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschen > Wasserfilter für Duschen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Halterungsplatten -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten & Bidets -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten & Bidets > Bidets -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten & Bidets > Toiletten -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten & Bidets > Urinale -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten- und Bidetzubehör -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten- und Bidetzubehör > Spülkastenabdeckungen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten- und Bidetzubehör > Spülkästen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten- und Bidetzubehör > Toilettendeckel -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten- und Bidetzubehör > Toilettensitze -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten- und Bidetzubehör > Toilettenverkleidung -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten- und Bidetzubehör > WC-Papiersitze -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten- und Bidetzubehör > WC-Spülkastengriffe -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Waschbecken & Spülen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Waschbecken & Spülen > Badezimmerwaschbecken -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Waschbecken & Spülen > Küchenspülen & Spülbecken -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Waschbecken- & Spülenzubehör -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Waschbecken- & Spülenzubehör > Waschbeckenfüße -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Zubehör für Armaturen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Zubehör für Badewannen -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Zubehör für Badewannen > Badewanneneinläufe -Heimwerkerbedarf > Sanitärinstallationen > Sanitärkeramik & Armaturen > Zubehör für Badewannen > Badewannenverkleidungen -Heimwerkerbedarf > Sanitärinstallationen > Schläuche -Heimwerkerbedarf > Sanitärinstallationen > Siphons & Siebe -Heimwerkerbedarf > Sanitärinstallationen > Siphons & Siebe > Ablaufgarnituren für Badewannen -Heimwerkerbedarf > Sanitärinstallationen > Siphons & Siebe > Ablaufgarnituren für Duschen -Heimwerkerbedarf > Sanitärinstallationen > Siphons & Siebe > Flaschensiphons -Heimwerkerbedarf > Sanitärinstallationen > Sperrventile -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Wasserdestillationsgeräte -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Wasserenthärter -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Wasserenthärtungszubehör -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Wasserenthärtungszubehör > Salz zur Wasserenthärtung -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Wasserleitungsfilter -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Wasserleitungsfilter > Wasserfilter für Kühlschränke -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Wasserspender -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Wasserspender > Trinkbrunnen -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Wasserspender > Wasserkühler -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Zubehör für Wasserfilter -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Zubehör für Wasserfilter > Wasserfiltergehäuse -Heimwerkerbedarf > Sanitärinstallationen > Trinkwasserspender & Filter > Zubehör für Wasserfilter > Wasserfilterkartuschen -Heimwerkerbedarf > Sanitärinstallationen > Ventile -Heimwerkerbedarf > Sanitärinstallationen > Ventile > Kugelhähne -Heimwerkerbedarf > Sanitärinstallationen > Wasserschaltuhren -Heimwerkerbedarf > Sanitärinstallationen > Wasserstandsregler -Heimwerkerbedarf > Sanitärinstallationen > Wärmefühler -Heimwerkerbedarf > Schlösser & Schlossereibedarf -Heimwerkerbedarf > Schlösser & Schlossereibedarf > Kartenschließsysteme -Heimwerkerbedarf > Schlösser & Schlossereibedarf > Riegelschlösser -Heimwerkerbedarf > Schlösser & Schlossereibedarf > Taschenschiebetür-Schlösser -Heimwerkerbedarf > Schlösser & Schlossereibedarf > Vorhängeschlösser -Heimwerkerbedarf > Schlösser & Schlossereibedarf > Zylinderschlösser -Heimwerkerbedarf > Schlüsselrohlinge -Heimwerkerbedarf > Taschenlampen +Heimwerkerbedarf > Hauseinzäunung > Zaunpfosten & -querlatten +Heimwerkerbedarf > Hauseinzäunung > Zubehör für Zäune & Gartentore +Heimwerkerbedarf > Heizen, Belüftung & Klimaanlagen +Heimwerkerbedarf > Heizen, Belüftung & Klimaanlagen > Filtertrockner +Heimwerkerbedarf > Heizen, Belüftung & Klimaanlagen > Luftdurchlässe & Abzugsrohre +Heimwerkerbedarf > Heizen, Belüftung & Klimaanlagen > Luftkanäle +Heimwerkerbedarf > Heizen, Belüftung & Klimaanlagen > Steuerungen +Heimwerkerbedarf > Heizen, Belüftung & Klimaanlagen > Steuerungen > Feuchtigkeitsregler +Heimwerkerbedarf > Heizen, Belüftung & Klimaanlagen > Steuerungen > Schalt- & Bedienungstafeln +Heimwerkerbedarf > Heizen, Belüftung & Klimaanlagen > Steuerungen > Thermostate +Heimwerkerbedarf > Kleinmotoren +Heimwerkerbedarf > Küchen- und Sanitärinstallationen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Brunnenbedarf +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Installationsrohre +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Rohrverbindungsstücke & -halterungen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Rohrverbindungsstücke & -halterungen > Dichtungen & O-Ringe +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Rohrverbindungsstücke & -halterungen > Druckminderer +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Rohrverbindungsstücke & -halterungen > Düsen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Rohrverbindungsstücke & -halterungen > In der Wand verbaute Trägerleisten +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Rohrverbindungsstücke & -halterungen > Rohranschlüsse & Reduzierstücke +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Rohrverbindungsstücke & -halterungen > Rohrflansche +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Rohrverbindungsstücke & -halterungen > Rohrkappen & -verschlüsse +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Rohrverbindungsstücke & -halterungen > Rohrschellen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Rohrverbindungsstücke & -halterungen > Rohrverbinder +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Rohrverbindungsstücke & -halterungen > Ventile +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitär-Reparatursätze +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen > Armaturen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen > Badewannen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen > Badezimmerausstattungen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen > Duschkabinen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten & Bidets +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten & Bidets > Bidets +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten & Bidets > Toiletten +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen > Toiletten & Bidets > Urinale +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen > Waschbecken & Spülen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen > Waschbecken & Spülen > Badezimmerwaschbecken +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Sanitärkeramik & Armaturen > Waschbecken & Spülen > Küchenspülen & Spülbecken +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Schläuche & Leitungen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Trinkwasserspender & Filter +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Trinkwasserspender & Filter > Salz zur Wasserenthärtung +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Trinkwasserspender & Filter > Wasserdestillationsgeräte +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Trinkwasserspender & Filter > Wasserenthärter +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Trinkwasserspender & Filter > Wasserleitungsfilter +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Trinkwasserspender & Filter > Wasserspender +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Trinkwasserspender & Filter > Wasserspender > Trinkbrunnen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Trinkwasserspender & Filter > Wasserspender > Wasserkühler +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Trinkwasserspender & Filter > Zubehör für Wasserfilter +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Trinkwasserspender & Filter > Zubehör für Wasserfilter > Wasserfiltergehäuse +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Trinkwasserspender & Filter > Zubehör für Wasserfilter > Wasserfilterkartuschen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Wasserschaltuhren +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Wasserstandsregler +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Abflusskomponenten +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Abflusskomponenten > Abflussauskleidung bzw. -Abdichtung +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Abflusskomponenten > Abflussgitter +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Abflusskomponenten > Abflussreinigungswerkzeug +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Abflusskomponenten > Abflussstöpsel & -filter +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Abflusskomponenten > Geruchsverschlüsse +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Abflusskomponenten > Kanal- & Abflussstäbe +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Abflusskomponenten > Sanitärabläufe +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Abflüsse +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Duscharmaturen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Duscharmaturen > Badewannen- & Duschdüsen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Duscharmaturen > Duscharme & Verbindungsteile +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Duscharmaturen > Duschkabinen & -türen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Duscharmaturen > Duschköpfe +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Duscharmaturen > Duschpaneele +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Duscharmaturen > Duschwannen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Duscharmaturen > Duschwände +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Duscharmaturen > Elektrische & Powerduschen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Duscharmaturen > Wasserfilter für Duschen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Halterungsplatten +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Toiletten- und Bidetzubehör +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Toiletten- und Bidetzubehör > Bidet-Hähne & -Sprüher +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Toiletten- und Bidetzubehör > Kugelhähne & Verschlüsse +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Toiletten- und Bidetzubehör > Spülkastenabdeckungen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Toiletten- und Bidetzubehör > Spülkästen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Toiletten- und Bidetzubehör > Toilettendeckel +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Toiletten- und Bidetzubehör > Toilettensitze +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Toiletten- und Bidetzubehör > Toilettenverkleidung +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Toiletten- und Bidetzubehör > WC-Papiersitze +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Toiletten- und Bidetzubehör > WC-Spülkastengriffe +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Waschbecken- & Spülenzubehör +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Waschbecken- & Spülenzubehör > Waschbeckenfüße +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Zubehör für Armaturen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Zubehör für Armaturen > Armaturen-Griffe +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Zubehör für Armaturen > Strahlregler +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Zubehör für Badewannen +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Zubehör für Badewannen > Badewanneneinläufe +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Zubehör für Badewannen > Badewannenunterbau & -füße +Heimwerkerbedarf > Küchen- und Sanitärinstallationen > Zubehör für sanitäre Anlagen > Zubehör für Badewannen > Badewannenverkleidungen +Heimwerkerbedarf > Metallpumpen +Heimwerkerbedarf > Metallpumpen > Bewässerungs-, Sprinkler- und Boosterpumpen +Heimwerkerbedarf > Metallpumpen > Brunnenpumpen und -systeme +Heimwerkerbedarf > Metallpumpen > Haushaltsgerätepumpen +Heimwerkerbedarf > Metallpumpen > Pool-, Brunnen- und Teichpumpen +Heimwerkerbedarf > Metallpumpen > Pumpen zur Entwässerung und Wasserleitung +Heimwerkerbedarf > Metallpumpen > Sickergruben-, Abwasser- und Abflusspumpen +Heimwerkerbedarf > Schlösser & Schlüssel +Heimwerkerbedarf > Schlösser & Schlüssel > Kartenschließsysteme +Heimwerkerbedarf > Schlösser & Schlüssel > Schlösser & Riegel +Heimwerkerbedarf > Schlösser & Schlüssel > Schlüsselkappen +Heimwerkerbedarf > Schlösser & Schlüssel > Schlüsselrohlinge Heimwerkerbedarf > Vorratsbehälter -Heimwerkerbedarf > Vorratsbehälter > Nahrungsmittelbehälter -Heimwerkerbedarf > Vorratsbehälter > Wassertanks -Heimwerkerbedarf > Vorratsbehälter > Öltanks -Heimwerkerbedarf > Wandfarben & Tapeten -Heimwerkerbedarf > Wandfarben & Tapeten > Abbeizmittel -Heimwerkerbedarf > Wandfarben & Tapeten > Anstrichfarben -Heimwerkerbedarf > Wandfarben & Tapeten > Farbe -Heimwerkerbedarf > Wandfarben & Tapeten > Farbenbindemittel -Heimwerkerbedarf > Wandfarben & Tapeten > Farbstoffe -Heimwerkerbedarf > Wandfarben & Tapeten > Grundierungen -Heimwerkerbedarf > Wandfarben & Tapeten > Lacke -Heimwerkerbedarf > Wandfarben & Tapeten > Lösungsmittel -Heimwerkerbedarf > Wandfarben & Tapeten > Tapeten -Heimwerkerbedarf > Werkstatthocker Heimwerkerbedarf > Werkzeuge -Heimwerkerbedarf > Werkzeuge > Ahlen Heimwerkerbedarf > Werkzeuge > Ambosse Heimwerkerbedarf > Werkzeuge > Arbeitslampen -Heimwerkerbedarf > Werkzeuge > Arbeitslampen > Stirnlampen Heimwerkerbedarf > Werkzeuge > Arbeitsscheinwerfer -Heimwerkerbedarf > Werkzeuge > Aufzüge & Hebeeinrichtungen -Heimwerkerbedarf > Werkzeuge > Aufzüge & Hebeeinrichtungen > Bauaufzüge -Heimwerkerbedarf > Werkzeuge > Aufzüge & Hebeeinrichtungen > Hubwagen -Heimwerkerbedarf > Werkzeuge > Aufzüge & Hebeeinrichtungen > Kettenzüge -Heimwerkerbedarf > Werkzeuge > Aufzüge & Hebeeinrichtungen > Pneumatische Hebevorrichtungen -Heimwerkerbedarf > Werkzeuge > Aufzüge & Hebeeinrichtungen > Seilzüge -Heimwerkerbedarf > Werkzeuge > Aufzüge & Hebeeinrichtungen > Wagenheber Heimwerkerbedarf > Werkzeuge > Bauwerkzeuge Heimwerkerbedarf > Werkzeuge > Bauwerkzeuge > Betonmischer Heimwerkerbedarf > Werkzeuge > Bauwerkzeuge > Fugenkellen Heimwerkerbedarf > Werkzeuge > Bauwerkzeuge > Fugenschwamm Heimwerkerbedarf > Werkzeuge > Bauwerkzeuge > Glättkellen Heimwerkerbedarf > Werkzeuge > Bauwerkzeuge > Glättmaschinen -Heimwerkerbedarf > Werkzeuge > Bauwerkzeuge > Kantenwerkzeug +Heimwerkerbedarf > Werkzeuge > Bauwerkzeuge > Kanten- & Schneidwerkzeuge Heimwerkerbedarf > Werkzeuge > Bauwerkzeuge > Maurerkellen Heimwerkerbedarf > Werkzeuge > Bauwerkzeuge > Maurerschnüre Heimwerkerbedarf > Werkzeuge > Bauwerkzeuge > Maurerwerkzeuge Heimwerkerbedarf > Werkzeuge > Betonbesen -Heimwerkerbedarf > Werkzeuge > Biegewerkzeuge +Heimwerkerbedarf > Werkzeuge > Biegewerkzeuge für Rohre & Stangen Heimwerkerbedarf > Werkzeuge > Bodenverdichter Heimwerkerbedarf > Werkzeuge > Bohrmaschinen Heimwerkerbedarf > Werkzeuge > Bohrmaschinen > Druckluftbohrer @@ -3219,75 +2761,56 @@ Heimwerkerbedarf > Werkzeuge > Bohrmaschinen > Schneckenbohrer Heimwerkerbedarf > Werkzeuge > Bohrmaschinen > Stemmmaschinen Heimwerkerbedarf > Werkzeuge > Bohrmaschinen > Ständerbohrmaschinen Heimwerkerbedarf > Werkzeuge > Brecheisen -Heimwerkerbedarf > Werkzeuge > Bürsten -Heimwerkerbedarf > Werkzeuge > Bürsten > Airbrushpistolen -Heimwerkerbedarf > Werkzeuge > Bürsten > Kaminbürste -Heimwerkerbedarf > Werkzeuge > Bürsten > Malerpinsel -Heimwerkerbedarf > Werkzeuge > Bürsten > Rohrbürsten Heimwerkerbedarf > Werkzeuge > Drehmaschinen -Heimwerkerbedarf > Werkzeuge > Druckluftpistolen Heimwerkerbedarf > Werkzeuge > Einziehhilfen Heimwerkerbedarf > Werkzeuge > Elektrohobel Heimwerkerbedarf > Werkzeuge > Entgrater -Heimwerkerbedarf > Werkzeuge > Fettpistolen -Heimwerkerbedarf > Werkzeuge > Formeinsätze +Heimwerkerbedarf > Werkzeuge > Fettpressen +Heimwerkerbedarf > Werkzeuge > Feuerzeuge & Streichhölzer +Heimwerkerbedarf > Werkzeuge > Gewindebohrer Heimwerkerbedarf > Werkzeuge > Gewindeschneidmaschinen Heimwerkerbedarf > Werkzeuge > Glühbirnenwechsler Heimwerkerbedarf > Werkzeuge > Griffe +Heimwerkerbedarf > Werkzeuge > Hacken & Spitzhacken +Heimwerkerbedarf > Werkzeuge > Handmischmaschinen Heimwerkerbedarf > Werkzeuge > Handwerkzeuge für Drähte und Kabel -Heimwerkerbedarf > Werkzeuge > Handwerkzeuge für Drähte und Kabel > Abisolierzangen -Heimwerkerbedarf > Werkzeuge > Handwerkzeuge für Drähte und Kabel > Drahtschneider -Heimwerkerbedarf > Werkzeuge > Handwerkzeuge für Drähte und Kabel > Kabelklemmzangen -Heimwerkerbedarf > Werkzeuge > Handwerkzeuge für Drähte und Kabel > Kabelschneider -Heimwerkerbedarf > Werkzeuge > Handwerkzeuge für Drähte und Kabel > Seitenschneider Heimwerkerbedarf > Werkzeuge > Heißluftpistole Heimwerkerbedarf > Werkzeuge > Hobel Heimwerkerbedarf > Werkzeuge > Holzspalter -Heimwerkerbedarf > Werkzeuge > Hämmern -Heimwerkerbedarf > Werkzeuge > Hämmern > Drucklufthammer -Heimwerkerbedarf > Werkzeuge > Hämmern > Elektrohammer -Heimwerkerbedarf > Werkzeuge > Hämmern > Hammer -Heimwerkerbedarf > Werkzeuge > Hämmern > Hammer > Holzhammer & Gummihammer -Heimwerkerbedarf > Werkzeuge > Hämmern > Hammer > Vorschlaghammer -Heimwerkerbedarf > Werkzeuge > Hämmern > Presslufthammer -Heimwerkerbedarf > Werkzeuge > Industriescheren +Heimwerkerbedarf > Werkzeuge > Hämmer +Heimwerkerbedarf > Werkzeuge > Hämmer > Manuelle Hämmer +Heimwerkerbedarf > Werkzeuge > Hämmer > Maschinenbetriebene Hämmer Heimwerkerbedarf > Werkzeuge > Industrievibratoren +Heimwerkerbedarf > Werkzeuge > Kaminbürste Heimwerkerbedarf > Werkzeuge > Kittmesser & -spachtel -Heimwerkerbedarf > Werkzeuge > Klebepistolen Heimwerkerbedarf > Werkzeuge > Kompressoren -Heimwerkerbedarf > Werkzeuge > Kompressoren > Gaskompressoren -Heimwerkerbedarf > Werkzeuge > Kompressoren > Luftkompressoren Heimwerkerbedarf > Werkzeuge > Kontrollspiegel Heimwerkerbedarf > Werkzeuge > Lasthebemagnete Heimwerkerbedarf > Werkzeuge > Leitern & Gerüste Heimwerkerbedarf > Werkzeuge > Leitern & Gerüste > Arbeitsplattformen Heimwerkerbedarf > Werkzeuge > Leitern & Gerüste > Gerüste Heimwerkerbedarf > Werkzeuge > Leitern & Gerüste > Leitern -Heimwerkerbedarf > Werkzeuge > Leitern & Gerüste > Leitern > Aluminiumleitern -Heimwerkerbedarf > Werkzeuge > Leitern & Gerüste > Leitern > Holzleitern Heimwerkerbedarf > Werkzeuge > Leitern & Gerüste > Leiterwagen Heimwerkerbedarf > Werkzeuge > Leitern & Gerüste > Tritthocker -Heimwerkerbedarf > Werkzeuge > Locheisen +Heimwerkerbedarf > Werkzeuge > Locheisen & Ahlen Heimwerkerbedarf > Werkzeuge > Lochschaufeln Heimwerkerbedarf > Werkzeuge > Lötkolben Heimwerkerbedarf > Werkzeuge > Malerwerkzeug +Heimwerkerbedarf > Werkzeuge > Malerwerkzeug > Airbrushpistolen Heimwerkerbedarf > Werkzeuge > Malerwerkzeug > Farbrollen -Heimwerkerbedarf > Werkzeuge > Malerwerkzeug > Farbschaber +Heimwerkerbedarf > Werkzeuge > Malerwerkzeug > Farbrüttler +Heimwerkerbedarf > Werkzeuge > Malerwerkzeug > Farbsiebe Heimwerkerbedarf > Werkzeuge > Malerwerkzeug > Farbspritzpistolen Heimwerkerbedarf > Werkzeuge > Malerwerkzeug > Farbwannen Heimwerkerbedarf > Werkzeuge > Malerwerkzeug > Kantenmaler +Heimwerkerbedarf > Werkzeuge > Malerwerkzeug > Malerpinsel Heimwerkerbedarf > Werkzeuge > Malerwerkzeug > Malschwämme Heimwerkerbedarf > Werkzeuge > Markierwerkzeuge -Heimwerkerbedarf > Werkzeuge > Markierwerkzeuge > Markiereisen -Heimwerkerbedarf > Werkzeuge > Markierwerkzeuge > Markiermesser -Heimwerkerbedarf > Werkzeuge > Markierwerkzeuge > Schlagschnüre -Heimwerkerbedarf > Werkzeuge > Markierwerkzeuge > Zimmermannsbleistifte Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Balkensucher Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Barometer -Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Diagnostische Wärmekamera -Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Drucksensoren & Wandler Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Durchflussmesser +Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Elektrische Prüfgeräte Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Entfernungsmesser Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Feuchtigkeitsmesser Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Gasdetektoren @@ -3295,43 +2818,39 @@ Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Geigerzähler Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Haarlineale Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Hygrometer Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Höhenmesser -Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Kabelprüfgeräte -Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Kabelsuchgeräte +Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Infrarotthermometer Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Kluppen Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Lautstärkemesser -Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Lecksucher Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Maßbänder Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Messerführungen Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Messinstrumente Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Messräder Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Messschieber -Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Metallsuchgeräte & Spannungsdetektoren -Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Multimeter Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Raumluftmessgeräte Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Richtwaagen Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Richtwaagen > Laser-Wasserwaagen Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Richtwaagen > Schaugläser Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Richtwaagen > Wasserwaagen -Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Rohrsuchgeräte Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Schwingungsmesser Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Seismographen Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Sonden & Suchgeräte Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Stahlbewehrungssucher Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Stangenzirkel Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Theodolite -Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Thermometer -Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Thermometer > Infrarotthermometer +Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Thermoelemente & -säulen +Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Transducer & Messumformer Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > UV-Lichtmesser Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Waagen Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Wetterstationen +Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Windgeschwindigkeitsmesser Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Winkel Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Winkelmesser +Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Wärmekameras Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > Zollstöcke Heimwerkerbedarf > Werkzeuge > Messwerkzeuge & Messwertgeber > pH-Meter Heimwerkerbedarf > Werkzeuge > Multifunktionales Elektrowerkzeug Heimwerkerbedarf > Werkzeuge > Mühlen Heimwerkerbedarf > Werkzeuge > Müll- & Papierkrallen -Heimwerkerbedarf > Werkzeuge > Müll- & Papierkrallen > Greifzangen Heimwerkerbedarf > Werkzeuge > Nageleisen Heimwerkerbedarf > Werkzeuge > Nagler & Tacker Heimwerkerbedarf > Werkzeuge > Nietwerkzeuge @@ -3339,6 +2858,8 @@ Heimwerkerbedarf > Werkzeuge > Nietwerkzeuge > Nietpistolen Heimwerkerbedarf > Werkzeuge > Nietwerkzeuge > Nietzangen Heimwerkerbedarf > Werkzeuge > Oberfräsen Heimwerkerbedarf > Werkzeuge > Reibahlen +Heimwerkerbedarf > Werkzeuge > Rohrbürsten +Heimwerkerbedarf > Werkzeuge > Rohrreiniger Heimwerkerbedarf > Werkzeuge > Rollwagen & Sackkarren Heimwerkerbedarf > Werkzeuge > Sandstrahler Heimwerkerbedarf > Werkzeuge > Saugglocken @@ -3352,87 +2873,80 @@ Heimwerkerbedarf > Werkzeuge > Schneidegeräte > Bolzenschneider Heimwerkerbedarf > Werkzeuge > Schneidegeräte > Cutter Heimwerkerbedarf > Werkzeuge > Schneidegeräte > Fliesen-, Ziegel- & Schindelschneider Heimwerkerbedarf > Werkzeuge > Schneidegeräte > Glasschneider -Heimwerkerbedarf > Werkzeuge > Schneidegeräte > Nibbelmaschinen +Heimwerkerbedarf > Werkzeuge > Schneidegeräte > Metallscheren & Nibbelmaschinen Heimwerkerbedarf > Werkzeuge > Schneidegeräte > Rohrschneider -Heimwerkerbedarf > Werkzeuge > Schneidegeräte > Zwickzange -Heimwerkerbedarf > Werkzeuge > Schnitzwerkzeuge -Heimwerkerbedarf > Werkzeuge > Schnitzwerkzeuge > Hohleisen -Heimwerkerbedarf > Werkzeuge > Schnitzwerkzeuge > Hohlmeißel -Heimwerkerbedarf > Werkzeuge > Schnitzwerkzeuge > Holzschnitzmesser -Heimwerkerbedarf > Werkzeuge > Schnitzwerkzeuge > Meißel +Heimwerkerbedarf > Werkzeuge > Schneidegeräte > Zwickzangen +Heimwerkerbedarf > Werkzeuge > Schnitzwerkzeuge & Hohlmeißel Heimwerkerbedarf > Werkzeuge > Schraubendreher -Heimwerkerbedarf > Werkzeuge > Schraubendreher > Elektrische Schraubenzieher -Heimwerkerbedarf > Werkzeuge > Schraubendreher > Handschraubenzieher Heimwerkerbedarf > Werkzeuge > Schraubenschlüssel -Heimwerkerbedarf > Werkzeuge > Schraubenschlüssel > Innensechskantschlüssel -Heimwerkerbedarf > Werkzeuge > Schraubenschlüssel > Knarren -Heimwerkerbedarf > Werkzeuge > Schweißwerkzeuge +Heimwerkerbedarf > Werkzeuge > Schraubzwingen & -stöcke +Heimwerkerbedarf > Werkzeuge > Schweißwerkzeuge & Plasmaschneider Heimwerkerbedarf > Werkzeuge > Schwingschleifer Heimwerkerbedarf > Werkzeuge > Schärfgeräte -Heimwerkerbedarf > Werkzeuge > Schärfgeräte > Schleifsteine -Heimwerkerbedarf > Werkzeuge > Schärfgeräte > Schärfgeräte für Kettensägen Heimwerkerbedarf > Werkzeuge > Steckschlüssel Heimwerkerbedarf > Werkzeuge > Stemmwerkzeuge -Heimwerkerbedarf > Werkzeuge > Stemmwerkzeuge > Fugenpistolen -Heimwerkerbedarf > Werkzeuge > Stemmwerkzeuge > Kalfateisen -Heimwerkerbedarf > Werkzeuge > Stemmwerkzeuge > Kalfathammer Heimwerkerbedarf > Werkzeuge > Stoßmaschinen Heimwerkerbedarf > Werkzeuge > Sägeböcke Heimwerkerbedarf > Werkzeuge > Sägen Heimwerkerbedarf > Werkzeuge > Sägen > Bandsägen -Heimwerkerbedarf > Werkzeuge > Sägen > Bügelsägen Heimwerkerbedarf > Werkzeuge > Sägen > Dekupiersägen Heimwerkerbedarf > Werkzeuge > Sägen > Fliesensägen Heimwerkerbedarf > Werkzeuge > Sägen > Gehrungssägen Heimwerkerbedarf > Werkzeuge > Sägen > Handkreissägen Heimwerkerbedarf > Werkzeuge > Sägen > Handsägen +Heimwerkerbedarf > Werkzeuge > Sägen > Plattensägen Heimwerkerbedarf > Werkzeuge > Sägen > Stichsägen Heimwerkerbedarf > Werkzeuge > Sägen > Säbelsägen Heimwerkerbedarf > Werkzeuge > Sägen > Tischkreissägen Heimwerkerbedarf > Werkzeuge > Sägen > Trennsägen +Heimwerkerbedarf > Werkzeuge > Taschenlampen & Stirnlampen Heimwerkerbedarf > Werkzeuge > Tischlerbänke Heimwerkerbedarf > Werkzeuge > Wasseranschluss-Nachschleifwerkzeuge Heimwerkerbedarf > Werkzeuge > Werkzeugfeilen Heimwerkerbedarf > Werkzeuge > Werkzeugmesser -Heimwerkerbedarf > Werkzeuge > Werkzeugmesser > Rigipsmesser Heimwerkerbedarf > Werkzeuge > Werkzeugschlüssel -Heimwerkerbedarf > Werkzeuge > Werkzeugschlüssel > Absperrhahn Schlüssel -Heimwerkerbedarf > Werkzeuge > Werkzeugschlüssel > Heizkörperschlüssel -Heimwerkerbedarf > Werkzeuge > Werkzeugschlüssel > Schachtdeckelheber Heimwerkerbedarf > Werkzeuge > Werkzeugsets Heimwerkerbedarf > Werkzeuge > Werkzeugsets > Elektrowerkzeug-Combo-Sets Heimwerkerbedarf > Werkzeuge > Werkzeugsets > Handwerkzeugsets -Heimwerkerbedarf > Werkzeuge > Werkzeugspanner -Heimwerkerbedarf > Werkzeuge > Werkzeugspanner > Schraubzwingen Heimwerkerbedarf > Werkzeuge > Zangen Heimwerkerbedarf > Werkzeuge > Äxte Heimwerkerbedarf > Werkzeuge > Ölfilterabfluss Heimwerkerbedarf > Werkzeugzubehör -Heimwerkerbedarf > Werkzeugzubehör > Airbrushzubehör Heimwerkerbedarf > Werkzeugzubehör > Anstreichzubehör +Heimwerkerbedarf > Werkzeugzubehör > Anstreichzubehör > Airbrushzubehör Heimwerkerbedarf > Werkzeugzubehör > Anstreichzubehör > Anstreichrollenzubehör +Heimwerkerbedarf > Werkzeugzubehör > Anstreichzubehör > Pinselreiniger Heimwerkerbedarf > Werkzeugzubehör > Axtzubehör +Heimwerkerbedarf > Werkzeugzubehör > Axtzubehör > Axtgriffe Heimwerkerbedarf > Werkzeugzubehör > Axtzubehör > Axtköpfe Heimwerkerbedarf > Werkzeugzubehör > Bohrzubehör -Heimwerkerbedarf > Werkzeugzubehör > Bohrzubehör > Bohrer +Heimwerkerbedarf > Werkzeugzubehör > Bohrzubehör > Bohrer & Bits Heimwerkerbedarf > Werkzeugzubehör > Bohrzubehör > Bohrerschleifgeräte Heimwerkerbedarf > Werkzeugzubehör > Bohrzubehör > Bohrerverlängerungen +Heimwerkerbedarf > Werkzeugzubehör > Bohrzubehör > Bohrfutter +Heimwerkerbedarf > Werkzeugzubehör > Bohrzubehör > Bohrständer & -führungen Heimwerkerbedarf > Werkzeugzubehör > Bohrzubehör > Lochsägen Heimwerkerbedarf > Werkzeugzubehör > Drehmaschinenzubehör Heimwerkerbedarf > Werkzeugzubehör > Elektrowerkzeug-Akkus Heimwerkerbedarf > Werkzeugzubehör > Hammerzubehör Heimwerkerbedarf > Werkzeugzubehör > Hammerzubehör > Drucklufthammerzubehör -Heimwerkerbedarf > Werkzeugzubehör > Hammerzubehör > Hammerkeile Heimwerkerbedarf > Werkzeugzubehör > Hammerzubehör > Hammerköpfe +Heimwerkerbedarf > Werkzeugzubehör > Hammerzubehör > Hammerstiele +Heimwerkerbedarf > Werkzeugzubehör > Keile für Werkzeuggriffe +Heimwerkerbedarf > Werkzeugzubehör > Keilwerkzeuge Heimwerkerbedarf > Werkzeugzubehör > Klammern Heimwerkerbedarf > Werkzeugzubehör > Ladegeräte für Elektrowerkzeuge Heimwerkerbedarf > Werkzeugzubehör > Magnetisierer & Entmagnetisierer -Heimwerkerbedarf > Werkzeugzubehör > Sandstrahlerzubehör -Heimwerkerbedarf > Werkzeugzubehör > Sandstrahlerzubehör > Sandstrahlkabinen +Heimwerkerbedarf > Werkzeugzubehör > Mischwerkzeuge +Heimwerkerbedarf > Werkzeugzubehör > Sandstrahler-Zubehör +Heimwerkerbedarf > Werkzeugzubehör > Sandstrahler-Zubehör > Sandstrahlkabinen +Heimwerkerbedarf > Werkzeugzubehör > Schleifmaschinenzubehör +Heimwerkerbedarf > Werkzeugzubehör > Schleifmaschinenzubehör > Schleifscheiben & -stifte Heimwerkerbedarf > Werkzeugzubehör > Schleifzubehör Heimwerkerbedarf > Werkzeugzubehör > Schleifzubehör > Schleifpapier & Schleifschwämme -Heimwerkerbedarf > Werkzeugzubehör > Schraubendreherzubehör -Heimwerkerbedarf > Werkzeugzubehör > Schraubendreherzubehör > Schraubendrehereinsätze +Heimwerkerbedarf > Werkzeugzubehör > Schneidwerkzeug-Zubehör +Heimwerkerbedarf > Werkzeugzubehör > Schneidwerkzeug-Zubehör > Nibbelmaschinenstempel +Heimwerkerbedarf > Werkzeugzubehör > Schweißzubehör Heimwerkerbedarf > Werkzeugzubehör > Spannvorrichtungen Heimwerkerbedarf > Werkzeugzubehör > Steckschlüsseleinsätze Heimwerkerbedarf > Werkzeugzubehör > Sägenzubehör @@ -3441,24 +2955,22 @@ Heimwerkerbedarf > Werkzeugzubehör > Sägenzubehör > Handkreissägen-Zubehör Heimwerkerbedarf > Werkzeugzubehör > Sägenzubehör > Kreissägenzubehör Heimwerkerbedarf > Werkzeugzubehör > Sägenzubehör > Laubsägenzubehör Heimwerkerbedarf > Werkzeugzubehör > Sägenzubehör > Tischsägenzubehör -Heimwerkerbedarf > Werkzeugzubehör > Werkzeuggriffe -Heimwerkerbedarf > Werkzeugzubehör > Werkzeuggriffe > Axtgriffe -Heimwerkerbedarf > Werkzeugzubehör > Werkzeuggriffe > Hammerstiele Heimwerkerbedarf > Werkzeugzubehör > Werkzeugklingen & -blätter +Heimwerkerbedarf > Werkzeugzubehör > Werkzeugklingen & -blätter > Cutter-, Schaber- & Spachtelklingen Heimwerkerbedarf > Werkzeugzubehör > Werkzeugklingen & -blätter > Sägeblätter -Heimwerkerbedarf > Werkzeugzubehör > Werkzeugklingen & -blätter > Teppichmesserklingen +Heimwerkerbedarf > Werkzeugzubehör > Werkzeugsicherungen Heimwerkerbedarf > Werkzeugzubehör > Werkzeugständer Heimwerkerbedarf > Werkzeugzubehör > Werkzeugständer > Sägeständer -Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Generatoren -Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Generatoren > Generator-Ersatzteile -Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Generatoren > Generatorabdeckungen +Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Hacken & Pickel +Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Hacken & Pickel > Griffe für Hacken & Pickel Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Lötkolben Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Lötkolben > Lötkolbenspitzen Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Lötkolben > Lötkolbenständer Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Messwerkzeuge & Messwertgeber Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Messwerkzeuge & Messwertgeber > Multimeter-Zubehör Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Messwerkzeuge & Messwertgeber > Zubehör für Gasdetektoren -Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Messwerkzeuge & Messwertgeber > Zubehör für Metallsuchgeräte & Spannungsdetektoren +Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Messwerkzeuge & Messwertgeber > Zubehör für Messskalen +Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Messwerkzeuge & Messwertgeber > Zubehör für Spannungsmesser Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Oberfräsen Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Oberfräsen > Oberfräsenteile Heimwerkerbedarf > Werkzeugzubehör > Zubehör für Oberfräsen > Oberfräsentische @@ -3470,16 +2982,17 @@ Kameras & Optik > Fotografie Kameras & Optik > Fotografie > Aufbewahrung für Negative & Kontaktbögen Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Belichtungsmesser -Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Blitzdiffusoren -Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Blitzdiffusoren > Kamera-Blitzdiffusoren -Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Blitzreflektoren -Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Hintergründe -Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Lichtfilter & Filterfolien Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Lichtsteuerung -Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Softboxen -Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Studioausstattung +Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Lichtsteuerung > Blitzdiffusoren +Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Lichtsteuerung > Blitzreflektoren +Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Lichtsteuerung > Lichtfilter & Filterfolien +Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Lichtsteuerung > Softboxen +Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Studio-Halterungen +Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Studiohintergründe Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Studioscheinwerfer & -blitzgeräte Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Zubehör für Belichtungsmesser +Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Zubehör für Studiobeleuchtung & Blitzgeräte +Kameras & Optik > Fotografie > Beleuchtung & Studiobedarf > Zubehör für Studioständer & -halterungen Kameras & Optik > Fotografie > Dunkelkammer Kameras & Optik > Fotografie > Dunkelkammer > Ausrüstung, Entwicklung & Verarbeitung Kameras & Optik > Fotografie > Dunkelkammer > Ausrüstung, Entwicklung & Verarbeitung > Dunkelkammerbecken @@ -3489,11 +3002,6 @@ Kameras & Optik > Fotografie > Dunkelkammer > Ausrüstung, Entwicklung & Verarbe Kameras & Optik > Fotografie > Dunkelkammer > Ausrüstung, Entwicklung & Verarbeitung > Retuschierbedarf & -zubehör Kameras & Optik > Fotografie > Dunkelkammer > Dunkelkammerleuchten Kameras & Optik > Fotografie > Dunkelkammer > Fotochemikalien -Kameras & Optik > Fotografie > Dunkelkammer > Fotochemikalien > Farbfotokits -Kameras & Optik > Fotografie > Dunkelkammer > Fotochemikalien > Fixierer -Kameras & Optik > Fotografie > Dunkelkammer > Fotochemikalien > Fotoentwickler -Kameras & Optik > Fotografie > Dunkelkammer > Fotochemikalien > Fototoner -Kameras & Optik > Fotografie > Dunkelkammer > Fotochemikalien > Stoppbäder Kameras & Optik > Fotografie > Dunkelkammer > Fotopapier Kameras & Optik > Fotografie > Dunkelkammer > Vergrößerungszubehör Kameras & Optik > Fotografie > Dunkelkammer > Vergrößerungszubehör > Dunkelkammer-Timer @@ -3501,259 +3009,259 @@ Kameras & Optik > Fotografie > Dunkelkammer > Vergrößerungszubehör > Dunkelka Kameras & Optik > Fotografie > Dunkelkammer > Vergrößerungszubehör > Fokussierhilfen Kameras & Optik > Fotografie > Dunkelkammer > Vergrößerungszubehör > Fotovergrößerer Kameras & Optik > Fotografie > Dunkelkammer > Vergrößerungszubehör > Lichtmesser +Kameras & Optik > Fotografie > Passepartouts & Aufziehmaterialien Kameras & Optik > Kamera- & Optisches Zubehör -Kameras & Optik > Kamera- & Optisches Zubehör > Kamera- & Optische Linsen -Kameras & Optik > Kamera- & Optisches Zubehör > Kamera- & Optische Linsen > Kameraobjektive -Kameras & Optik > Kamera- & Optisches Zubehör > Kamera- & Optische Linsen > Objektive für Überwachungskameras -Kameras & Optik > Kamera- & Optisches Zubehör > Kamera- & Optische Linsen > Videokamera-Objektive +Kameras & Optik > Kamera- & Optisches Zubehör > Kamera- & Video-Objektive +Kameras & Optik > Kamera- & Optisches Zubehör > Kamera- & Video-Objektive > Kameraobjektive +Kameras & Optik > Kamera- & Optisches Zubehör > Kamera- & Video-Objektive > Objektive für Überwachungskameras +Kameras & Optik > Kamera- & Optisches Zubehör > Kamera- & Video-Objektive > Videokamera-Objektive +Kameras & Optik > Kamera- & Optisches Zubehör > Kamera-Ersatzkabel Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör & Zubehör für optische Linsen -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör & Zubehör für optische Linsen > Objektiv- & Filteradapter -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör & Zubehör für optische Linsen > Objektivabdeckungen -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör & Zubehör für optische Linsen > Objektivfilter -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör & Zubehör für optische Linsen > Objektivfilter > Makrofilter -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör & Zubehör für optische Linsen > Objektivtaschen -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör & Zubehör für optische Linsen > Streulichtblenden -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör & Zubehör für optische Linsen > Telekonverter +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Bildsensoren Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Blitzhaltesysteme -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Fototasche -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Fototasche > Kamerataschen -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Fototasche > Videokamerataschen +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Digitale Rückteile +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Ersatzbildschirme & -Displays +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Ersatzgehäuseteile & -klappen +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Ersatztasten & -knöpfe +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Fokusvorrichtungen für Kameras Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kamera-Blitzgeräte Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kamera-Blitzlichtzubehör Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kamera-Fernbedienungen +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kameraausrüstungen Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kamerafilme -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kameragriffe Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kameragurte -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kamerahalterungen -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Unterwasser-Kamerahüllen -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Unterwasser-Kamerahüllen > Unterwassergehäuse für Kameras -Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Unterwasser-Kamerahüllen > Unterwassergehäuse für Videokameras +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kamerahaltegriffe +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kameramonitore +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kamerastabilisatoren und -stützen +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kamerataschen & -koffer +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Kamerazubehör-Sets +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Schalldämmung & Sound Blimps für Kameras +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Sonnenschutz für Kameras & Sucheraufsätze +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Unterwassergehäuse für Kameras und Videokameras Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Videokameraleuchten +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Zoomkomponenten für Objektive +Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Zubehör für Unterwasserkameragehäuse Kameras & Optik > Kamera- & Optisches Zubehör > Kamerazubehör > Überwachungskamera-Zubehör Kameras & Optik > Kamera- & Optisches Zubehör > Optisches Zubehör Kameras & Optik > Kamera- & Optisches Zubehör > Optisches Zubehör > Fernglas- & Fernrohrzubehör -Kameras & Optik > Kamera- & Optisches Zubehör > Optisches Zubehör > Optisches Zubehör für Nachtsichtsysteme Kameras & Optik > Kamera- & Optisches Zubehör > Optisches Zubehör > Taschen & Koffer für optische Instrumente Kameras & Optik > Kamera- & Optisches Zubehör > Optisches Zubehör > Wärmebildzubehör -Kameras & Optik > Kamera- & Optisches Zubehör > Optisches Zubehör > Zubehör für Messsucher +Kameras & Optik > Kamera- & Optisches Zubehör > Optisches Zubehör > Zubehör für Entferungsmesser Kameras & Optik > Kamera- & Optisches Zubehör > Optisches Zubehör > Zubehör für Spektive -Kameras & Optik > Kamera- & Optisches Zubehör > Optisches Zubehör > Zubehör für Spektive > Spektivokulare Kameras & Optik > Kamera- & Optisches Zubehör > Optisches Zubehör > Zubehör für Teleskope Kameras & Optik > Kamera- & Optisches Zubehör > Optisches Zubehör > Zubehör für Zielfernrohre Kameras & Optik > Kamera- & Optisches Zubehör > Stative +Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Kameraobjektive +Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Kameraobjektive > Objektiv- & Filteradapter +Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Kameraobjektive > Objektivabdeckungen +Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Kameraobjektive > Objektivfilter +Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Kameraobjektive > Objektivkonverter +Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Kameraobjektive > Objektivtaschen +Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Kameraobjektive > Streulichtblenden Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Stative Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Stative > Schellen & Halterungen für Stative Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Stative > Stativaufbewahrung +Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Stative > Stativgriffe Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Stative > Stativköpfe +Kameras & Optik > Kamera- & Optisches Zubehör > Zubehör für Stative > Stativspinnen Kameras & Optik > Kameras Kameras & Optik > Kameras > Analogkameras -Kameras & Optik > Kameras > Analogkameras > Sofortbildkameras +Kameras & Optik > Kameras > Boroskope Kameras & Optik > Kameras > Digitalkameras -Kameras & Optik > Kameras > Digitalkameras > Digitale Kompaktkameras -Kameras & Optik > Kameras > Digitalkameras > Digitale Spiegelreflexkameras -Kameras & Optik > Kameras > Digitalkameras > Spiegellose Systemkameras Kameras & Optik > Kameras > Einwegkameras Kameras & Optik > Kameras > Videokameras Kameras & Optik > Kameras > Webcams Kameras & Optik > Kameras > Wildkameras Kameras & Optik > Kameras > Überwachungskameras -Kameras & Optik > Kameras > Überwachungskameras > Versteckte Kameras Kameras & Optik > Optische Geräte Kameras & Optik > Optische Geräte > Ferngläser -Kameras & Optik > Optische Geräte > Ferngläser > Operngläser Kameras & Optik > Optische Geräte > Fernrohre Kameras & Optik > Optische Geräte > Fernrohre > Spektive Kameras & Optik > Optische Geräte > Fernrohre > Teleskope +Kameras & Optik > Optische Geräte > Fernrohre > Waffenspektive & Zielfernrohre Kameras & Optik > Optische Geräte > Monokulare Kameras & Optik > Optische Geräte > Optische Entfernungsmesser Kunst & Unterhaltung +Kunst & Unterhaltung > Eintrittskarten Kunst & Unterhaltung > Hobby & Kunst -Kunst & Unterhaltung > Hobby & Kunst > Bildende Kunst -Kunst & Unterhaltung > Hobby & Kunst > Bildende Kunst > Figuren, Skulpturen & Statuen -Kunst & Unterhaltung > Hobby & Kunst > Bildende Kunst > Fotografien -Kunst & Unterhaltung > Hobby & Kunst > Bildende Kunst > Fotografien > Stock-Fotografie -Kunst & Unterhaltung > Hobby & Kunst > Bildende Kunst > Gemälde -Kunst & Unterhaltung > Hobby & Kunst > Bildende Kunst > Kartonschablonen -Kunst & Unterhaltung > Hobby & Kunst > Bildende Kunst > Mixed-Media-Kunst -Kunst & Unterhaltung > Hobby & Kunst > Bildende Kunst > Poster & Nachdrucke -Kunst & Unterhaltung > Hobby & Kunst > Bildende Kunst > Zeichnungen & Illustrationen +Kunst & Unterhaltung > Hobby & Kunst > Jonglieren Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Aufbewahrungsmöglichkeiten für Handwerk & Hobby -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Aufbewahrungsmöglichkeiten für Handwerk & Hobby > Behälter für Strick- & Häkelnadeln -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Aufbewahrungsmöglichkeiten für Handwerk & Hobby > Nähkörbe -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Aufbewahrungsmöglichkeiten für Handwerk & Hobby > Ordnungsboxen für Garn -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Bastelmagnete -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Bier & Wein selbst herstellen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Bier & Wein selbst herstellen > Bierbrauen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Bier & Wein selbst herstellen > Weinherstellung -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Blumenstecken -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Blumenstecken > Blumenklebeband -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Blumenstecken > Vasenfüller & Streudeko -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Dekorative Aufkleber -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Druckgrafik -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Druckgrafik > Siebdruck -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Druckgrafik > Siebdruck > Handroller -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Druckgrafik > Siebdruck > Siebdruck-Rakel -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Formen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Glashandwerk -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Glitzer -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Holzbearbeitung -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > JoJos aus Stoff -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Jonglieren -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kerzen- & Seifenherstellung -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kerzen- & Seifenherstellung > Dochtaufhänger -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kerzen- & Seifenherstellung > Dochte -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kränze und Gebinde -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kränze und Gebinde > Rahmen zur Kranzbindung -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Lederhandwerk -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Leinwände & Keilrahmen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Leuchtkästen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Magnetband -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Modellbau -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Modellbau > Modellbausätze -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Modellbau > Modelleisenbahn & Eisenbahnsets -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Modellbau > Modelleisenbahnzubehör -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Modellbau > Modellraketen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Moosgummi & Styropor -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Mosaike -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Mosaike > Mosaikbausätze -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Fasernweberei-Muster -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Fasernweberei-Muster > Kreuzstich- und Stickmuster -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Fasernweberei-Muster > Nähmuster -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Fasernweberei-Muster > Strick- & Häkelmuster -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Filzen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Filzen > Filzformen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Filzen > Filznadeln -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Filzen > Filzpolster & -matten -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Faden- & Garnführer -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Faden- & Garnspulen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Faden- & Garnwickler -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Fadenabschneider -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Fingerhüte -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Flicksets -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Herstellungs-Sets für Jo-Jos -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Herstellungs-Sets für Pompons & Schleifen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Kardierer -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Maschenmarkierer & -zähler -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Nadeleinfädler -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Nadelkissen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Nahttrenner -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Nähnadeln -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Schablonen für Stoff-Zuschneidemaschinen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Schneiderpuppen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Sicherheitsnadeln -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Spanndrähte -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Spannmatten -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Stecknadeln -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Stoff-Zuschneidemaschinen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Stoffscheren -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Textilkleber -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Textilkleber > Schmucksteinkleber -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Textilkleber > Textilklebeband -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Textilkunst-Messgeräte -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Textilkunst-Rahmen, -Reifen & -Spannvorrichtungen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Textilmarker -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Geräte & Zubehör für Textilkunst > Webmaschinen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Gobelinstickerei -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Gobelinstickerei > Gobelinstickerei-Sets -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Gobelinstickerei > Leinwand für Gobelinstickerei -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Häkeln -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Häkeln > Häkelgarne -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Häkeln > Häkelnadeln -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Kreuzstich -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Quilten -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Spinnen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Spinnen > Handspindeln -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Spinnen > Spinnräder -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Spinnen > Spinnzubehör -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Spitze & Borten -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Stricken -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Stricken > Stricknadeln -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Aufnäher -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Druckknöpfe -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Elastikbund -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Füllmaterial -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Garne -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Klettverschlüsse -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Knöpfe -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Plastiksegeltuch -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Reißverschlüsse -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Rohwolle -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Stoffe -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Textilaufnäher- und aufbügler -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Textilfarben -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Textilfärbemittel -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Vlieseline -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Textilkunst-Verbrauchsmaterialien > Zwirn -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Weben -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Weben > Haken für Locker-Hooking -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Weben > Webschlagkämme -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Nähen, Häkeln, Stricken & Sticken > Weben > Webschützen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Pailletten -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papier -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papier > Aquarellpapier -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papier > Bastelpapier -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papier > Origami-Papier -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papier > Pauspapier -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papier > Pergament -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papier > Skizzenblöcke -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papier > Tonpapier & Karton -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papier > Transferpapier -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papierkunst -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papierkunst > Kartenbastelsets -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papierkunst > Prägen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papierkunst > Prägen > Prägemappen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papierkunst > Prägen > Prägemaschinen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papierkunst > Prägen > Prägepuder -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papierkunst > Prägen > Prägestempel -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papierkunst > Prägen > Prägestifte -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papierkunst > Prägen > Werkzeuge zum Prägen mit Hitze -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Papierkunst > Rill- und Perforiergeräte für Papier -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Pfeifenreiniger -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Schablonenkunst -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Schablonenkunst > Schablonen & Matrizen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Schablonenkunst > Schablonendruckmaschinen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Schmuckherstellung -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Schmuckherstellung > Perlen -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Schmuckherstellung > Schmuck- & Perlenband -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Schmuckherstellung > Schmuck- & Perlendraht -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Schmuckherstellung > Schmuck-Basisteile -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Scrapbooking -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Sets für die Räucherwerkherstellung -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Stempeln -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Stempeln > Lederstempel & Sets -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Stempeln > Metallstempel & Sets -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Strass -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Töpferei & Bildhauerei -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Töpferei & Bildhauerei > Modellierton & Modelliermasse -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Uhrreparatursätze -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Vordrucke -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Vorlagen für Perlenarbeiten -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zauberbedarf -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zeichnen & Malerei -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zeichnen & Malerei > Künstlerpinsel -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zeichnen & Malerei > Malerei -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zeichnen & Malerei > Malerei > Farbpaletten -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zeichnen & Malerei > Malerei > Künstlerfarben -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zeichnen & Malerei > Malerei > Malgründe -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zeichnen & Malerei > Malerei > Palettenmesser -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zeichnen & Malerei > Skizzieren -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zeichnen & Malerei > Skizzieren > Zeichenzirkel -Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zeichnen & Malerei > Zeichen- & Malsets +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Aufbewahrung für Kunst- & Bastelmaterial +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Aufbewahrung für Kunst- & Bastelmaterial > Arbeitssets für Näh- und Textilkunstarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Aufbewahrung für Kunst- & Bastelmaterial > Aufbewahrung für Fäden & Garne +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Aufbewahrung für Kunst- & Bastelmaterial > Aufbewahrung für Näh-, Strick- & Häkelnadeln +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Basteldraht +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Basteldraht > Blumendraht +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Basteldraht > Pfeifenreiniger +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Basteldraht > Schmuck- & Perlendraht +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelfarben, -tinten & -lacke +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelfarben, -tinten & -lacke > Fixative +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelfarben, -tinten & -lacke > Färbemittel für Kunstarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelfarben, -tinten & -lacke > Keramik- & Tonglasuren +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelfarben, -tinten & -lacke > Kunst- & Bastelfarben +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelfarben, -tinten & -lacke > Malgründe +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelfarben, -tinten & -lacke > Stempelkissen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelfarben, -tinten & -lacke > Tinte für Kunstarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelklebstoffe & -magnete +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelklebstoffe & -magnete > Aufbügelbänder +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelklebstoffe & -magnete > Bastel- & Büroklebstoff +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelklebstoffe & -magnete > Bastelmagnete +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelklebstoffe & -magnete > Blumenklebeband +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelklebstoffe & -magnete > Deko-Klebeband +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelwerkstoffe +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelwerkstoffe > Garne +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelwerkstoffe > Rohwolle +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelwerkstoffe > Schmuck- & Perlenband +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Bastelwerkstoffe > Zwirn +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Befestigungen & Verschlüsse +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Befestigungen & Verschlüsse > Klettverschlüsse +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Befestigungen & Verschlüsse > Knöpfe +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Befestigungen & Verschlüsse > Reißverschlussziehgriffe +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Befestigungen & Verschlüsse > Reißverschlüsse +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Befestigungen & Verschlüsse > Schnallen & Haken +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Befestigungen & Verschlüsse > Ösen & Tüllen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Dochte +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Dochthalter +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Formen & Rohlinge für Kunstarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Formen & Rohlinge für Kunstarbeiten > Holz & Formen für Kunstarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Formen & Rohlinge für Kunstarbeiten > Kranz- & Gesteckunterlagen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Formen & Rohlinge für Kunstarbeiten > Moosgummi & Styropor +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Formen & Rohlinge für Kunstarbeiten > Pappmaschee-Formen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Füllmaterial +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Füllmaterial > Füllgranulate +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Füllmaterial > Kissenformen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Füllmaterial > Wattierung & Füllung +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Leder & Vinyl +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Papier für Kunstarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Papier für Kunstarbeiten > Bastelfolie +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Papier für Kunstarbeiten > Bastelpapier +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Papier für Kunstarbeiten > Kartenkarton & Scrapbooking-Papier +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Papier für Kunstarbeiten > Origami-Papier +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Papier für Kunstarbeiten > Pergament +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Papier für Kunstarbeiten > Transferpapier +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Papier für Kunstarbeiten > Zeichen- & Malpapier +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Prägepuder +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Rohes Kerzenwachs +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Textilien +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Textilien > Bastelleinwände +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Textilien > Bastelleinwände > Leinwände & Keilrahmen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Textilien > Bastelleinwände > Plastiksegeltuch +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Textilien > Bastelleinwände > Stickleinwände +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Textilien > Bedruckbare Gewebe +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Textilien > Stoffe +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Textilien > Vlieseline +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Töpferei & Bildhauerei +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Töpferei & Bildhauerei > Engoben +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Töpferei & Bildhauerei > Gipsgewebe +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Töpferei & Bildhauerei > Modellierton & Modelliermasse +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Töpferei & Bildhauerei > Pappmaschee-Mischungen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Aufnäher +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Bänder & Einfassungen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Deko-Federn +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Dekorative Aufkleber +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Edelsteine +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Elastikbund +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Pailletten & Glitzer +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Perlen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Schleifen & Rosetten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Schmuck-Basisteile +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Strass & Deko-Elemente +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelmaterialien > Verzierungen & Trimmungen > Textilaufnäher & -aufbügler +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelsets +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelsets > Bastelsets +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelsets > Flicksets +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelsets > Kerzenherstellungssets +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelsets > Mosaikbausätze +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelsets > Schmuckherstellungssets +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelsets > Scrapbooking- & Stempelsets +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelsets > Sets für die Räucherwerkherstellung +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelsets > Sticksets +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelsets > Zeichen- & Malsets +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Dekorationswerkzeuge für Kunstarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Farbmischwerkzeuge +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Farbmischwerkzeuge > Farbpaletten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Farbmischwerkzeuge > Palettenmesser +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Filzpolster & -matten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Fingerhüte +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Klebepistolen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Leuchtkästen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Maschinen für Textilarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Maschinen für Textilarbeiten > Filznadeln & -maschinen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Maschinen für Textilarbeiten > Handwebstühle +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Maschinen für Textilarbeiten > Mechanische Webstühle +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Maschinen für Textilarbeiten > Nähmaschinen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Maschinen für Textilarbeiten > Spinnräder +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Mess- & Markierwerkzeuge für Kunstarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Mess- & Markierwerkzeuge für Kunstarbeiten > Handroller +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Mess- & Markierwerkzeuge für Kunstarbeiten > Holzbrennwerkzeuge +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Mess- & Markierwerkzeuge für Kunstarbeiten > Künstlerpinsel +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Mess- & Markierwerkzeuge für Kunstarbeiten > Maschenmarkierer & -zähler +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Mess- & Markierwerkzeuge für Kunstarbeiten > Motivstempel +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Mess- & Markierwerkzeuge für Kunstarbeiten > Schablonen & Matrizen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Mess- & Markierwerkzeuge für Kunstarbeiten > Schablonendruckmaschinen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Mess- & Markierwerkzeuge für Kunstarbeiten > Siebdruck-Rakel +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Mess- & Markierwerkzeuge für Kunstarbeiten > Textilkunst-Messgeräte +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Mess- & Markierwerkzeuge für Kunstarbeiten > Zeichenzirkel +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Näh-, Strick- & Häkelnadeln +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Näh-, Strick- & Häkelnadeln > Häkelnadeln +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Näh-, Strick- & Häkelnadeln > Knüpfhaken +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Näh-, Strick- & Häkelnadeln > Nähmaschinennadeln +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Näh-, Strick- & Häkelnadeln > Nähnadeln +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Näh-, Strick- & Häkelnadeln > Stricknadeln +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Rahmen, Reifen & Spannvorrichtungen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Schneidematten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Schneiderpuppen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Sicherheitsnadeln +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Spanndrähte +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Spannmatten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Stanz- & Prägewerkzeuge für Kunstarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Stanz- & Prägewerkzeuge für Kunstarbeiten > Bastel- & Büroscheren +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Stanz- & Prägewerkzeuge für Kunstarbeiten > Bastelmesser +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Stanz- & Prägewerkzeuge für Kunstarbeiten > Fadenabschneider +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Stanz- & Prägewerkzeuge für Kunstarbeiten > Heißluftgeräte +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Stanz- & Prägewerkzeuge für Kunstarbeiten > Nahttrenner +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Stanz- & Prägewerkzeuge für Kunstarbeiten > Prägestifte +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Stanz- & Prägewerkzeuge für Kunstarbeiten > Rill- & Perforiergeräte für Papier +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Stanz- & Prägewerkzeuge für Kunstarbeiten > Stanzer & Präger für Kunstarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Stecknadeln +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Werkzeuge für die Faden- & Garnverarbeitung +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Werkzeuge für die Faden- & Garnverarbeitung > Faden- & Garnführer +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Werkzeuge für die Faden- & Garnverarbeitung > Faden- & Garnspulen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Werkzeuge für die Faden- & Garnverarbeitung > Faden- & Garnwickler +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Werkzeuge für die Faden- & Garnverarbeitung > Handspindeln +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Werkzeuge für die Faden- & Garnverarbeitung > Kardierer +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Werkzeuge für die Faden- & Garnverarbeitung > Nadeleinfädler +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Werkzeuge für die Faden- & Garnverarbeitung > Webschlagkämme +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Kunst- & Bastelwerkzeuge > Werkzeuge für die Faden- & Garnverarbeitung > Webschützen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Muster & Formen für Kunstarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Muster & Formen für Kunstarbeiten > Filzformen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Muster & Formen für Kunstarbeiten > Gießformen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Muster & Formen für Kunstarbeiten > Nähmuster +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Muster & Formen für Kunstarbeiten > Stick-, Strick- & Häkelmuster +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Muster & Formen für Kunstarbeiten > Vorlagen für Perlenarbeiten +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zubehör für Kunst- & Bastelwerkzeuge +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zubehör für Kunst- & Bastelwerkzeuge > Ersatzteile für Nähmaschinen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zubehör für Kunst- & Bastelwerkzeuge > Klingen für Bastelmesser +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zubehör für Kunst- & Bastelwerkzeuge > Nähmaschinenfüße +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zubehör für Kunst- & Bastelwerkzeuge > Nähmaschinengehäuse & -abdeckungen +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zubehör für Kunst- & Bastelwerkzeuge > Spinnzubehör +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zubehör für Kunst- & Bastelwerkzeuge > Stempelblöcke +Kunst & Unterhaltung > Hobby & Kunst > Kunsthandwerk & Hobby > Zubehör für Kunst- & Bastelwerkzeuge > Verlängerungstische für Nähmaschinen +Kunst & Unterhaltung > Hobby & Kunst > Modellbau +Kunst & Unterhaltung > Hobby & Kunst > Modellbau > Modellbausätze +Kunst & Unterhaltung > Hobby & Kunst > Modellbau > Modelleisenbahn & Eisenbahnsets +Kunst & Unterhaltung > Hobby & Kunst > Modellbau > Modelleisenbahnzubehör +Kunst & Unterhaltung > Hobby & Kunst > Modellbau > Modellraketen Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Akkordeons & Konzertinas Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente > Alt- & Baritonhörner Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente > Euphonien -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente > Flügelhörner -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente > Kornette -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente > Mellophone Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente > Posaunen -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente > Sousaphone -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente > Trompeten +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente > Trompeten & Kornetts Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente > Tubas Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Blechblasinstrumente > Waldhörner Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Dudelsäcke @@ -3767,15 +3275,14 @@ Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Blockflöten Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Fagotte Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Flöten -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Flöten > Schwegelpfeifen Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Kinderflöten Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Klarinetten +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Längsflöten & Panflöten Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Maultrommeln Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Melodicas Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Mundharmonikas Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Oboen Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Okarinas -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Panflöten Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Saxophone Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Holzblasinstrumente > Zugpfeife Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Klaviere @@ -3783,9 +3290,6 @@ Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Saiteninstrumente Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Saiteninstrumente > Bratschen Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Saiteninstrumente > Cellos Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Saiteninstrumente > Gitarren -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Saiteninstrumente > Gitarren > Akustikgitarren -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Saiteninstrumente > Gitarren > Bassgitarren -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Saiteninstrumente > Gitarren > Elektrische Gitarren Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Saiteninstrumente > Harfen Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Saiteninstrumente > Kontrabässe Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Saiteninstrumente > Violinen @@ -3797,22 +3301,21 @@ Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Gl Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Gongs Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Finger- & Handzimbeln -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Handglocken Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Handschellen & -glocken +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Handtrommeln +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Handtrommeln > Beckentrommeln +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Handtrommeln > Bongos +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Handtrommeln > Cajons +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Handtrommeln > Congas +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Handtrommeln > Rahmentrommeln +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Handtrommeln > Sprechtrommeln +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Handtrommeln > Tablas Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Holzblöcke -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Kasagnetten +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Kastagnetten Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Kuhglocken Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Rasseln -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Scraper +Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Scraper & Schnarren Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Tamburine -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Tamburins -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Tamburins > Beckentrommel -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Tamburins > Bongos -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Tamburins > Cajons -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Tamburins > Congas -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Tamburins > Sprechtrommeln -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Tamburins > Tablas -Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Tamburins > Tympanons Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Triangeln Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Handperkussionsinstrumente > Vibraslaps Kunst & Unterhaltung > Hobby & Kunst > Musikinstrumente > Schlaginstrumente > Hi-Hats @@ -3828,13 +3331,11 @@ Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Modellbau-Zubehör Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Münzen & Währungen Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Retro-Werbung Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammelkarten -Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammelkarten > Sport-Sammelkarten -Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammelkarten > Sport-Sammelkarten > American Football-Sammelkarten -Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammelkarten > Sport-Sammelkarten > Baseball-Sammelkarten -Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammelkarten > Sport-Sammelkarten > Basketball-Sammelkarten -Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammelkarten > Sport-Sammelkarten > Fußball-Sammelkarten -Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammelkarten > Sport-Sammelkarten > Hockey-Sammelkarten -Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammelkarten > Sport-Sammelkarten > Rennsport-Sammelkarten +Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammlerwaffen +Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammlerwaffen > Dekowaffen +Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammlerwaffen > Sammlermesser +Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammlerwaffen > Schwerter +Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sammlerwaffen > Schwertständer & Schwerthalter Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte > Sport-Fanartikel Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte > Sport-Fanartikel > American Football-Fanartikel @@ -3848,84 +3349,78 @@ Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte > Sp Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte > Sportartikel mit Autogramm > American Football-Artikel mit Autogramm Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte > Sportartikel mit Autogramm > Baseball- & Softballartikel mit Autogramm Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte > Sportartikel mit Autogramm > Basketballartikel mit Autogramm +Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte > Sportartikel mit Autogramm > Boxartikel mit Autogramm Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte > Sportartikel mit Autogramm > Fußballartikel mit Autogramm Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte > Sportartikel mit Autogramm > Hockeyartikel mit Autogramm Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte > Sportartikel mit Autogramm > Rennsportartikel mit Autogramm Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Sport-Sammelobjekte > Sportartikel mit Autogramm > Tennisartikel mit Autogramm Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Steine & Fossilien Kunst & Unterhaltung > Hobby & Kunst > Sammlerstücke > Stempelsiegel +Kunst & Unterhaltung > Hobby & Kunst > Zauberbedarf +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Bier- & Weinherstellung +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Bier- & Weinherstellung > Abfüllflaschen +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Bier- & Weinherstellung > Brauzutaten +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Bier- & Weinherstellung > Sets zum Selbstherstellen von Bier & Wein +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Bier- & Weinherstellung > Weinherstellung Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Keyboardzubehör Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Keyboardzubehör > Keyboardständer Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Keyboardzubehör > Keyboardtaschen Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Keyboardzubehör > Sustain-Pedale -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Klavierzubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Klavierzubehör > Klavierbänke +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Klavierbänke & Musikerhocker +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Marschgabeln und Notenhalter +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Metronome Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Notenständer Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Dämpfer für Schlagzeug & Becken -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Elektronische Schlagzeugmodule +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Module für elektronische Schlagzeuge Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Schlagzeug-Hardware Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Schlagzeug-Hardware > Beater Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Schlagzeug-Hardware > Fußmaschinen Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Schlagzeug-Hardware > Schlagzeughalterungen Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Schlagzeugfelle -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Schlagzeughocker Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Schlagzeugkoffer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Schlagzeugstockzubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Schlagzeugstockzubehör > Taschen für Schlagzeugstöcke +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Sticks & Besen Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Stimmschlüssel Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Ständer für Perkussionsinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Trommelschlägel -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Trommelstöcke Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Zubehör für Handperkussionsinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Zubehör für Handperkussionsinstrumente > Ständer für Handperkussionsinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Zubehör für Handperkussionsinstrumente > Taschen für Handperkussionsinstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Zubehör für Sticks & Besen +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Schlagzeugzubehör > Zubehör für Sticks & Besen > Taschen für Sticks & Besen Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Stimmgeräte Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Ständer für Musikinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Ständer für Musikinstrumente > Beleuchtung für Instrumentenständer Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Ständer für Musikinstrumente > Notenklemmen Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Ständer für Musikinstrumente > Taschen für Instrumentenständer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Ständer für Verstärker +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Taktstöcke Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Verstärker für Musikinstrumente -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Verstärkergehäuse +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Verstärkerzubehör +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Verstärkerzubehör > Ständer für Verstärker +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Verstärkerzubehör > Verstärker-Fußschalter +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Verstärkerzubehör > Verstärkerabdeckungen +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Verstärkerzubehör > Verstärkergehäuse +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Verstärkerzubehör > Verstärkerknöpfe +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Verstärkerzubehör > Verstärkerröhren Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Aufhängungen & Ständer für Blechinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Blechblasinstrumentenpflege &-reinigung -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Blechblasinstrumentenpflege &-reinigung > Bürsten, Reinigungsspiralen & Putzstöcke für Blechblasinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Blechblasinstrumentenpflege &-reinigung > Klappenöl für Blechblasinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Blechblasinstrumentenpflege &-reinigung > Pflegesets für Blechblasinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Blechblasinstrumentenpflege &-reinigung > Poliertücher für Blechblasinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Blechblasinstrumentenpflege &-reinigung > Reinigungs- & Desinfektionsmittel für Blechblasinstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Blechblasinstrumentenpflege &-reinigung > Reinigungswerkzeug für Blechblasinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Blechblasinstrumentenpflege &-reinigung > Schutzvorrichtungen für Blechblasinstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Dämpfer für Blechinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Ersatzteile für Blechblasinstrumente -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Euphoniumzubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Euphoniumzubehör > Dämpfer für Euphonien -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Euphoniumzubehör > Euphoniummundstücke -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Euphoniumzubehör > Euphoniumständer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Euphoniumzubehör > Koffer & Transporttaschen für Euphonien -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Posaunenzubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Posaunenzubehör > Dämpfer für Posaunen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Posaunenzubehör > Koffer & Transporttaschen für Posaunen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Posaunenzubehör > Posaunenmarschgabeln -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Posaunenzubehör > Posaunenmundstücke -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Posaunenzubehör > Posaunenständer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Trompeten- & Hornzubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Trompeten- & Hornzubehör > Dämpfer für Trompeten & Hörner -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Trompeten- & Hornzubehör > Koffer & Transporttaschen für Trompeten -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Trompeten- & Hornzubehör > Trompeten- & Hornmarschgabeln -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Trompeten- & Hornzubehör > Trompeten- & Hornmundstücke -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Trompeten- & Hornzubehör > Trompeten- & Hornständer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Tubazubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Tubazubehör > Dämpfer für Tubas -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Tubazubehör > Koffer & Transporttaschen für Tubas -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Tubazubehör > Tubamundstücke -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Tubazubehör > Tubaständer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Waldhornzubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Waldhornzubehör > Dämpfer für Waldhörner -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Waldhornzubehör > Koffer & Transporttaschen für Waldhörner -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Waldhornzubehör > Waldhornmundstücke -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Waldhornzubehör > Waldhornständer +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Koffer & Gigbags für Blechinstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Blechblasinstrumente > Mundstücke für Blechinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Blockflötenzubehör +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Blockflötenzubehör > Blockflötenkoffer +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Blockflötenzubehör > Blockflötenteile +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Blockflötenzubehör > Pflege- & Reinigungsmittel für Blockflöten Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Blätteretui für Holzblasinstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Fagottblattmesser Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Fagottzubehör @@ -3940,7 +3435,6 @@ Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Fagottzubehör > Koffer & Transporttaschen für Fagotte Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Flötenzubehör Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Flötenzubehör > Flötenhalter & -ständer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Flötenzubehör > Flötenmarschgabeln Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Flötenzubehör > Flötenpflege & -reinigung Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Flötenzubehör > Flötenpflege & -reinigung > Flötenpflegesets Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Flötenzubehör > Flötenpflege & -reinigung > Flötenreinigungsstab @@ -3953,7 +3447,6 @@ Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Klarinettenzubehör > Blattschrauben & Kappen für Klarinetten Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Klarinettenzubehör > Klarinettenblätter Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Klarinettenzubehör > Klarinettenhalter/-ständer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Klarinettenzubehör > Klarinettenmarschgabeln Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Klarinettenzubehör > Klarinettenpflege &-reinigung Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Klarinettenzubehör > Klarinettenpflege &-reinigung > Klarinettenklappenschutz Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Klarinettenzubehör > Klarinettenpflege &-reinigung > Klarinettenpflegesets @@ -3985,7 +3478,6 @@ Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Zubehör für Saxophone > Koffer & Transporttaschen für Saxophone Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Zubehör für Saxophone > Rohrblätter für Saxophone Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Zubehör für Saxophone > Saxophonhalter & -ständer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Zubehör für Saxophone > Saxophonmarschgabeln Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Zubehör für Saxophone > Saxophonpflege &-reinigung Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Zubehör für Saxophone > Saxophonpflege &-reinigung > Saxophonklappenschutz Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Zubehör für Saxophone > Saxophonpflege &-reinigung > Saxophonpflegesets @@ -3996,77 +3488,61 @@ Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Zubehör für Saxophone > Saxophonteile > Mundstücke für Saxophone Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Holzblasinstrumente > Zubehör für Saxophone > Saxophonteile > Saxophonhälse Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Cellozubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Cellozubehör > Cellobögen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Cellozubehör > Cellokoffer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Cellozubehör > Cellosaiten -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Cellozubehör > Dämpfer für Cellos Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Gitarren-Slides Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Gitarrenbefeuchter +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Gitarrengurte Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Gitarrenkoffer & -taschen Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Gitarrenplektren Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Gitarrensaiten -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Gitarrenstimmgeräte +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Gitarrensaitenspanner +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Gitarrenstimmwirbel Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Gitarrenständer +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Gitarrenzubehör & -teile Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Kapodaster -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Stegstifte für Gitarren Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Tonabnehmer für Akustikgitarren Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Gitarrenzubehör > Tonabnehmer für E-Gitarren -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Harfenzubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Harfenzubehör > Harfenkoffer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Harfenzubehör > Harfensaiten -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Kontrabasszubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Kontrabasszubehör > Dämpfer für Kontrabässe -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Kontrabasszubehör > Kontrabassbögen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Kontrabasszubehör > Kontrabasskoffer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Kontrabasszubehör > Kontrabasssaiten Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Saiteninstrumentenpflege &-reinigung Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Saiteninstrumentenpflege &-reinigung > Bogenharz Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Saiteninstrumentenpflege &-reinigung > Politur für Saiteninstrumente Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Saiteninstrumentenpflege &-reinigung > Reinigungstücher für Saiteninstrumente -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violazubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violazubehör > Dämpfer für Violas -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violazubehör > Violabögen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violazubehör > Violakoffer -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violazubehör > Violasaiten -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violinenzubehör -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violinenzubehör > Dämpfer für Violinen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violinenzubehör > Kinnhalter für Violinen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violinenzubehör > Saitenhalter für Violinen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violinenzubehör > Schulterstützen für Violinen & Bratschen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violinenzubehör > Stimmwirbel für Violinen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violinenzubehör > Tonabnehmer für Violinen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violinenzubehör > Violinbögen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violinenzubehör > Violinenkoffer & -taschen -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violinenzubehör > Violinensaiten -Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Violinenzubehör > Violinenstege +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Zubehör für Streichinstrumente & Harfen +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Zubehör für Streichinstrumente & Harfen > Bogenkästen für Streichinstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Zubehör für Streichinstrumente & Harfen > Bögen für Saiteninstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Zubehör für Streichinstrumente & Harfen > Dämpfer für Saiteninstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Zubehör für Streichinstrumente & Harfen > Koffer für Saiteninstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Zubehör für Streichinstrumente & Harfen > Saiten für Saiteninstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Zubehör für Streichinstrumente & Harfen > Ständer für Saiteninstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Zubehör für Streichinstrumente & Harfen > Tonabnehmer für Saiteninstrumente +Kunst & Unterhaltung > Hobby & Kunst > Zubehör für Musikinstrumente > Zubehör für Saiteninstrumente > Zubehör für Streichinstrumente & Harfen > Zubehör & Ersatzteile für Saiteninstrumente Kunst & Unterhaltung > Party & Feiern Kunst & Unterhaltung > Party & Feiern > Partyzubehör Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Antwortkarten -Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Antwortkarten > Antwortkarten für die Hochzeit +Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Aufblasbare Partydeko Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Ballon-Sets Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Banner Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Briefsiegel Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Cocktaildekorationen +Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Dekorative Pompons Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Einladungskarten -Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Einladungskarten > Geburtstagseinladungen -Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Einladungskarten > Hochzeitseinladungen Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Eventprogramme +Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Feuerwerkskörper Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Geburtstagskerzen -Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Getränkekühler -Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Kartons & andere Behältnisse für Hochzeitskarten +Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Halter für Karten zu besonderen Anlässen Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Konfetti Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Luftballons -Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Luftschlangen Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Luftschlangenspray Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Partyartikel Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Partyartikel > Kleine Geschenke für Hochzeitsgäste +Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Partygirlanden & -vorhänge Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Partyhüte Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Partyspiele +Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Partyzubehörsets Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Piñatas Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Platzkarten Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Platzkartenhalter +Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Spruchkarten +Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Stuhlbezüge Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Trinkhalme & Rührer Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Trinkspiele Kunst & Unterhaltung > Party & Feiern > Partyzubehör > Trinkspiele > Bierpong @@ -4079,10 +3555,10 @@ Kunst & Unterhaltung > Party & Feiern > Schenken > Befestigungsnadeln für Anste Kunst & Unterhaltung > Party & Feiern > Schenken > Frische Schnittblumen Kunst & Unterhaltung > Party & Feiern > Schenken > Geschenkgutscheine Kunst & Unterhaltung > Party & Feiern > Schenken > Geschenkverpackungen +Kunst & Unterhaltung > Party & Feiern > Schenken > Geschenkverpackungen > Geschenkanhänger Kunst & Unterhaltung > Party & Feiern > Schenken > Geschenkverpackungen > Geschenkboxen & -dosen Kunst & Unterhaltung > Party & Feiern > Schenken > Geschenkverpackungen > Geschenkpapier Kunst & Unterhaltung > Party & Feiern > Schenken > Geschenkverpackungen > Geschenktaschen -Kunst & Unterhaltung > Party & Feiern > Schenken > Geschenkverpackungen > Schleifen & Bänder Kunst & Unterhaltung > Party & Feiern > Schenken > Geschenkverpackungen > Seidenpapier Kunst & Unterhaltung > Party & Feiern > Schenken > Grußkarten Kunst & Unterhaltung > Party & Feiern > Trophäen & Preise @@ -4091,73 +3567,22 @@ Kunst & Unterhaltung > Party & Feiern > Visuelle Effekte > Diskokugeln Kunst & Unterhaltung > Party & Feiern > Visuelle Effekte > Lichteffektbeleuchtung Kunst & Unterhaltung > Party & Feiern > Visuelle Effekte > Lichtmischpulte Kunst & Unterhaltung > Party & Feiern > Visuelle Effekte > Nebelmaschinen +Kunst & Unterhaltung > Party & Feiern > Visuelle Effekte > Ständer für Lichteffektbeleuchtung Medien +Medien > Baupläne für Tischler- und Holzarbeiten Medien > Bücher -Medien > Bücher > Belletristik -Medien > Bücher > Belletristik > Comics -Medien > Bücher > Belletristik > Horrorromane -Medien > Bücher > Belletristik > Kinderbücher -Medien > Bücher > Belletristik > Kriminalromane -Medien > Bücher > Belletristik > Liebesromane -Medien > Bücher > Belletristik > Literatur -Medien > Bücher > Belletristik > Science Fiction- & Fantasy-Romane -Medien > Bücher > Sachliteratur -Medien > Bücher > Sachliteratur > Architekturbücher -Medien > Bücher > Sachliteratur > Biografien -Medien > Bücher > Sachliteratur > Business- & Wirtschaftsbücher -Medien > Bücher > Sachliteratur > Bücher zu Bildung & Erziehung -Medien > Bücher > Sachliteratur > Bücher zu Bildung & Erziehung > Fremdsprachenbücher -Medien > Bücher > Sachliteratur > Bücher zu Bildung & Erziehung > Lernhilfen -Medien > Bücher > Sachliteratur > Bücher zu Handwerk, Hobby & Beschäftigung -Medien > Bücher > Sachliteratur > Bücher zu Körper, Geist & Seele -Medien > Bücher > Sachliteratur > Bücher zum Verkehrswesen -Medien > Bücher > Sachliteratur > Bücher über wahre Kriminalfälle -Medien > Bücher > Sachliteratur > Computerbücher -Medien > Bücher > Sachliteratur > Familienratgeber -Medien > Bücher > Sachliteratur > Fotografiebücher -Medien > Bücher > Sachliteratur > Gartenbücher -Medien > Bücher > Sachliteratur > Geschichtsbücher -Medien > Bücher > Sachliteratur > Gesundheits- & Fitnessbücher -Medien > Bücher > Sachliteratur > Haustierratgeber -Medien > Bücher > Sachliteratur > Heimwerkerbücher -Medien > Bücher > Sachliteratur > Humorvolle Bücher -Medien > Bücher > Sachliteratur > Kochbücher -Medien > Bücher > Sachliteratur > Kunstbücher -Medien > Bücher > Sachliteratur > Literaturwissenschaften -Medien > Bücher > Sachliteratur > Malbücher -Medien > Bücher > Sachliteratur > Mathematikbücher -Medien > Bücher > Sachliteratur > Medizinische Bücher -Medien > Bücher > Sachliteratur > Musikbücher -Medien > Bücher > Sachliteratur > Nachschlagewerke -Medien > Bücher > Sachliteratur > Performance-Kunst-Bücher -Medien > Bücher > Sachliteratur > Philosophiebücher -Medien > Bücher > Sachliteratur > Poesie -Medien > Bücher > Sachliteratur > Politikwissenschaftliche Bücher -Medien > Bücher > Sachliteratur > Psychologiebücher -Medien > Bücher > Sachliteratur > Rechtsbücher -Medien > Bücher > Sachliteratur > Reiseliteratur -Medien > Bücher > Sachliteratur > Religionsbücher -Medien > Bücher > Sachliteratur > Rätselbücher -Medien > Bücher > Sachliteratur > Sachliteratur für Jugendliche -Medien > Bücher > Sachliteratur > Schauspiel -Medien > Bücher > Sachliteratur > Selbsthilfebücher -Medien > Bücher > Sachliteratur > Sozialwissenschaftliche Bücher -Medien > Bücher > Sachliteratur > Spielbücher -Medien > Bücher > Sachliteratur > Sportbücher -Medien > Bücher > Sachliteratur > Sprach- & Linguistikbücher -Medien > Bücher > Sachliteratur > Technologiebücher -Medien > Bücher > Sachliteratur > Tier- & Naturbücher -Medien > Bücher > Sachliteratur > Wissenschaftsbücher Medien > DVDs & Videos -Medien > Musik +Medien > Musik & Tonaufnahmen Medien > Notenblätter Medien > Produkthandbücher & Gebrauchsanleitungen +Medien > Produkthandbücher & Gebrauchsanleitungen > Bürobedarf-Handbücher Medien > Produkthandbücher & Gebrauchsanleitungen > Handbücher für Elektrogeräte Medien > Produkthandbücher & Gebrauchsanleitungen > Handbücher für Elektrowerkzeuge Medien > Produkthandbücher & Gebrauchsanleitungen > Handbücher für Haushaltsgeräte Medien > Produkthandbücher & Gebrauchsanleitungen > Handbücher für Küchengeräte -Medien > Produkthandbücher & Gebrauchsanleitungen > Handbücher für Modelle und Spielzeuge -Medien > Produkthandbücher & Gebrauchsanleitungen > KFZ-Handbücher +Medien > Produkthandbücher & Gebrauchsanleitungen > Handbücher für Modellbau und Spielzeuge +Medien > Produkthandbücher & Gebrauchsanleitungen > Kamera- & Optikhandbücher +Medien > Produkthandbücher & Gebrauchsanleitungen > Kfz-Handbücher Medien > Produkthandbücher & Gebrauchsanleitungen > Sport- & Freizeithandbücher Medien > Zeitschriften & Zeitungen Möbel @@ -4166,38 +3591,26 @@ Möbel > Baby- & Kleinkindmöbel > Gitter- & Kinderbettzubehör Möbel > Baby- & Kleinkindmöbel > Gitter- & Kinderbettzubehör > Bettnestchen & Randschutz für Gitterbetten Möbel > Baby- & Kleinkindmöbel > Gitter- & Kinderbettzubehör > Umbausätze für Gitterbetten Möbel > Baby- & Kleinkindmöbel > Gitterbetten & Kleinkinderbetten -Möbel > Baby- & Kleinkindmöbel > Hochstühle und Sitzerhöhungen -Möbel > Baby- & Kleinkindmöbel > Möbelgarnituren für Babies und Kleinkinder -Möbel > Baby- & Kleinkindmöbel > Spielmöbel +Möbel > Baby- & Kleinkindmöbel > Hochstühle & Sitzerhöhungen +Möbel > Baby- & Kleinkindmöbel > Möbelgarnituren für Babies & Kleinkinder Möbel > Baby- & Kleinkindmöbel > Wickeltische Möbel > Baby- & Kleinkindmöbel > Wiegen & Stubenwagen Möbel > Baby- & Kleinkindmöbel > Wiegen- & Stubenwagenzubehör -Möbel > Baby- & Kleinkindmöbel > Wiegen- & Stubenwagenzubehör > Untergestelle für Wiegen Möbel > Baby- & Kleinkindmöbel > Zubehör für Hochstühle und Sitzerhöhungen Möbel > Betten & Zubehör -Möbel > Betten & Zubehör > Betten -Möbel > Betten & Zubehör > Betten > Ausziehbetten -Möbel > Betten & Zubehör > Betten > Etagenbetten -Möbel > Betten & Zubehör > Betten > Schlafcouch -Möbel > Betten & Zubehör > Betten > Schrankbetten -Möbel > Betten & Zubehör > Bettrahmen -Möbel > Betten & Zubehör > Bettzubehör -Möbel > Betten & Zubehör > Bettzubehör > Wasserbettzubehör +Möbel > Betten & Zubehör > Bett- & Bettrahmenzubehör +Möbel > Betten & Zubehör > Betten & Bettgestelle Möbel > Betten & Zubehör > Kopf- & Fußenden Möbel > Betten & Zubehör > Matratzen -Möbel > Betten & Zubehör > Matratzen > Wasserbetten Möbel > Betten & Zubehör > Matratzen-Untergestelle Möbel > Büromöbel Möbel > Büromöbel > Arbeitsplätze im Großraumbüro Möbel > Büromöbel > Arbeitstische Möbel > Büromöbel > Arbeitstische > Konferenztische Möbel > Büromöbel > Arbeitstische > Verstellbare Zeichentische -Möbel > Büromöbel > Bürostuhl-Zubehör -Möbel > Büromöbel > Bürostühle -Möbel > Büromöbel > Bürostühle > Kniestühle +Möbel > Büromöbel > Büro- & Schreibtischstühle +Möbel > Büromöbel > Büromöbelgarnituren Möbel > Büromöbel > Schreibtische -Möbel > Büromöbel > Sekretäre -Möbel > Büromöbel > Zubehör für Arbeitsplätze im Großraumbüro Möbel > Futonauflagen Möbel > Futonrahmen Möbel > Futons @@ -4208,6 +3621,7 @@ Möbel > Gartenmöbel > Gartenhocker Möbel > Gartenmöbel > Gartenmöbelgarnituren Möbel > Gartenmöbel > Gartensitzmöbel Möbel > Gartenmöbel > Gartensitzmöbel > Gartenbänke +Möbel > Gartenmöbel > Gartensitzmöbel > Gartensofaelemente Möbel > Gartenmöbel > Gartensitzmöbel > Gartensofas Möbel > Gartenmöbel > Gartensitzmöbel > Gartenstühle Möbel > Gartenmöbel > Gartensitzmöbel > Sonnenliegen @@ -4217,33 +3631,33 @@ Möbel > Gartenmöbelzubehör > Abdeckungen für Gartenmöbel Möbel > Kücheninseln & Küchenwagen Möbel > Kücheninseln & Küchenwagen > Kücheninseln Möbel > Kücheninseln & Küchenwagen > Küchenwagen +Möbel > Möbel für Unterhaltungselektronik Möbel > Möbelgarnituren +Möbel > Möbelgarnituren > Badezimmergarnituren Möbel > Möbelgarnituren > Küchen- & Esszimmergarnituren Möbel > Möbelgarnituren > Schlafzimmergarnituren Möbel > Möbelgarnituren > Wohnzimmergarnituren Möbel > Polsterhocker -Möbel > Polsterhocker > Fußhocker -Möbel > Polsterhocker > Hocker mit Stauraum -Möbel > Polsterhocker > Konvertierbare Hocker Möbel > Raumteiler Möbel > Raumteilerzubehör Möbel > Regalsysteme Möbel > Regalsysteme > Bücherregale & Bücherschränke +Möbel > Regalsysteme > Wandregale & Simse Möbel > Regalzubehör Möbel > Regalzubehör > Ersatzregale Möbel > Schränke Möbel > Schränke > Aktenschränke -Möbel > Schränke > Anrichten Möbel > Schränke > Aufbewahrungstruhen Möbel > Schränke > Aufbewahrungstruhen > Aussteuertruhen Möbel > Schränke > Aufbewahrungstruhen > Spielzeugtruhen Möbel > Schränke > Buffets & Sideboards +Möbel > Schränke > Bügelcenter +Möbel > Schränke > Büroschränke Möbel > Schränke > Geschirrschränke Möbel > Schränke > Kleiderschränke Möbel > Schränke > Kommoden +Möbel > Schränke > Küchenschränke Möbel > Schränke > Mediaaufbewahrung -Möbel > Schränke > Mediaaufbewahrung > Mediaschränke -Möbel > Schränke > Mediaaufbewahrung > TV- & Mediamöbel Möbel > Schränke > Spirituosenschränke Möbel > Schränke > Wasch- und Schminktische Möbel > Schränke > Wasch- und Schminktische > Badezimmerwaschtische @@ -4255,15 +3669,15 @@ Möbel > Sitzbänke > Küchen- & Eckbänke Möbel > Sitzbänke > Schminktischhocker Möbel > Sitzbänke > Truhen- & Flurbänke Möbel > Sofa-Zubehör +Möbel > Sofa-Zubehör > Anbausofas Möbel > Sofa-Zubehör > Sitzverstärkung für Stühle & Sofas Möbel > Sofas -Möbel > Sofas > Loveseats -Möbel > Sofas > Modulsofas -Möbel > Sofas > Schlafsofas Möbel > Stuhlzubehör +Möbel > Stuhlzubehör > Ersatzteile für Hängesessel Möbel > Stühle -Möbel > Stühle > Barhocker +Möbel > Stühle > Bodenstühle Möbel > Stühle > Elektrische Massagesessel +Möbel > Stühle > Hocker & Barhocker Möbel > Stühle > Hängesessel Möbel > Stühle > Klappstühle & Holzhocker Möbel > Stühle > Küchen- und Esszimmerstühle @@ -4271,17 +3685,13 @@ Möbel > Stühle > Liegemöbel Möbel > Stühle > Multimediasessel Möbel > Stühle > Schaukelstühle Möbel > Stühle > Sessel -Möbel > Stühle > Sessel > Armsessel -Möbel > Stühle > Sessel > Recliner-Sessel -Möbel > Stühle > Sessel > Schlafsessel Möbel > Stühle > Sessel ohne Armlehnen Möbel > Stühle > Sitzsäcke -Möbel > TV- & HiFi-Möbel Möbel > Tische Möbel > Tische > Kinderspieltische Möbel > Tische > Klapptische +Möbel > Tische > Kotatsu Möbel > Tische > Küchen- & Esszimmertische -Möbel > Tische > Küchen- & Esszimmertische > Bartische & Stehtische Möbel > Tische > Nachttische Möbel > Tische > Nähmaschinentische Möbel > Tische > Poker- & Spieltische @@ -4292,85 +3702,55 @@ Möbel > Tische > Ziertische > Konsolentische Möbel > Tischzubehör Möbel > Tischzubehör > Tischbeine Möbel > Tischzubehör > Tischplatten +Möbel > Zubehör für Büromöbel +Möbel > Zubehör für Büromöbel > Bürostuhl-Zubehör +Möbel > Zubehör für Büromöbel > Schreibtischteile & -zubehör +Möbel > Zubehör für Büromöbel > Zubehör für Arbeitsplätze im Großraumbüro Nahrungsmittel, Getränke & Tabak Nahrungsmittel, Getränke & Tabak > Getränke -Nahrungsmittel, Getränke & Tabak > Getränke > Alkopops -Nahrungsmittel, Getränke & Tabak > Getränke > Bier -Nahrungsmittel, Getränke & Tabak > Getränke > Bier > Alkoholfreies Bier +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Alkopops +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Bier +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Cidre +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Cocktail-Bitter +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Cocktail-Premix +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Liköre & Spirituosen +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Liköre & Spirituosen > Absinth +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Liköre & Spirituosen > Gin +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Liköre & Spirituosen > Liköre +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Liköre & Spirituosen > Rum +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Liköre & Spirituosen > Shochu & Soju +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Liköre & Spirituosen > Tequila +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Liköre & Spirituosen > Weinbrand +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Liköre & Spirituosen > Whiskey +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Liköre & Spirituosen > Wodka +Nahrungsmittel, Getränke & Tabak > Getränke > Alkoholische Getränke > Wein Nahrungsmittel, Getränke & Tabak > Getränke > Buttermilch -Nahrungsmittel, Getränke & Tabak > Getränke > Cidre -Nahrungsmittel, Getränke & Tabak > Getränke > Cocktail-Premix Nahrungsmittel, Getränke & Tabak > Getränke > Eierlikör Nahrungsmittel, Getränke & Tabak > Getränke > Essighaltige Getränke Nahrungsmittel, Getränke & Tabak > Getränke > Getränke mit Fruchtgeschmack +Nahrungsmittel, Getränke & Tabak > Getränke > Getränkepulver Nahrungsmittel, Getränke & Tabak > Getränke > Kaffee -Nahrungsmittel, Getränke & Tabak > Getränke > Kaffee > Gemahlener & ungemahlener Kaffee -Nahrungsmittel, Getränke & Tabak > Getränke > Kaffee > Instantkaffee -Nahrungsmittel, Getränke & Tabak > Getränke > Kaffee > Kaffeegetränke in Flaschen -Nahrungsmittel, Getränke & Tabak > Getränke > Kaffee > Kaffeekapseln Nahrungsmittel, Getränke & Tabak > Getränke > Kakao -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Gin -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Liköre -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Rum -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Schnäpse -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Tequila -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Weinbrand -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Weinbrand > Cognac -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Whiskey -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Whiskey > Amerikanischer Whiskey -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Whiskey > Irischer Whiskey -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Whiskey > Schottischer Whisky -Nahrungsmittel, Getränke & Tabak > Getränke > Liköre & Spirituosen > Wodka Nahrungsmittel, Getränke & Tabak > Getränke > Limonaden -Nahrungsmittel, Getränke & Tabak > Getränke > Magenbitter Nahrungsmittel, Getränke & Tabak > Getränke > Milch -Nahrungsmittel, Getränke & Tabak > Getränke > Milch > Trockenmilch -Nahrungsmittel, Getränke & Tabak > Getränke > Mischgetränke Nahrungsmittel, Getränke & Tabak > Getränke > Nicht-tierische Milch -Nahrungsmittel, Getränke & Tabak > Getränke > Nicht-tierische Milch > Hafermilch -Nahrungsmittel, Getränke & Tabak > Getränke > Nicht-tierische Milch > Hanfmilch -Nahrungsmittel, Getränke & Tabak > Getränke > Nicht-tierische Milch > Kokosmilch -Nahrungsmittel, Getränke & Tabak > Getränke > Nicht-tierische Milch > Mandelmilch -Nahrungsmittel, Getränke & Tabak > Getränke > Nicht-tierische Milch > Reismilch -Nahrungsmittel, Getränke & Tabak > Getränke > Nicht-tierische Milch > Sojamilch Nahrungsmittel, Getränke & Tabak > Getränke > Saft Nahrungsmittel, Getränke & Tabak > Getränke > Sportgetränke & Energy Drinks Nahrungsmittel, Getränke & Tabak > Getränke > Tees & Aufgüsse -Nahrungsmittel, Getränke & Tabak > Getränke > Tees & Aufgüsse > Teebeutel & Lose Teeblätter -Nahrungsmittel, Getränke & Tabak > Getränke > Tees & Aufgüsse > Teegetränke in Flaschen -Nahrungsmittel, Getränke & Tabak > Getränke > Tees & Aufgüsse > Teepulver Nahrungsmittel, Getränke & Tabak > Getränke > Wasser -Nahrungsmittel, Getränke & Tabak > Getränke > Wasser > Aromatisiertes Wasser -Nahrungsmittel, Getränke & Tabak > Getränke > Wasser > Mineralwasser -Nahrungsmittel, Getränke & Tabak > Getränke > Wasser > Tonic Water -Nahrungsmittel, Getränke & Tabak > Getränke > Wasser > Wasser in Flaschen -Nahrungsmittel, Getränke & Tabak > Getränke > Wein -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Alkoholfreie Weine -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Blush-Roséwein -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Dessertwein -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Dessertwein > Portwein -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Dessertwein > Sherry -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Dessertwein > Wermut -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Met -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Reiswein -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Roséwein -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Rotwein -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Sangria -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Sekt -Nahrungsmittel, Getränke & Tabak > Getränke > Wein > Weißwein Nahrungsmittel, Getränke & Tabak > Lebensmittel Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Backwaren-Mischungen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Bagels Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Brot & Brötchen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Coffee Cake Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Cupcakes -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Dessert-Platten Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Donuts Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Eistüten Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Feingebäck -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Fudge +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Karamell-Fudge Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Kuchen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Kuchen zum Kaffee Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Muffins Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Pasteten & Torten Nahrungsmittel, Getränke & Tabak > Lebensmittel > Backwaren > Plätzchen @@ -4383,26 +3763,14 @@ Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Frisc Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Gemüsedip Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Guacamole Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Hummus -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Käseaufstrich +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Käse-Dips & -aufstriche Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Marmeladen & Gelees Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Nussbutter -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Nussbutter > Erdnussbutter -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Nussbutter > Haselnussbutter -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Nussbutter > Mandelbutter Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Salsa-Saucen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Dips & Brotaufstriche > Tapenade Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Fertigvorspeisen & -beilagen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Fertigvorspeisen & -beilagen > Feinkost-Sandwiches -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Fertigvorspeisen & -beilagen > Fertigbeilagen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Fertigvorspeisen & -beilagen > Fertighauptgerichte -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Fertigvorspeisen & -beilagen > Frühstückssandwiches -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Fertigvorspeisen & -beilagen > Pizza -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Fertigvorspeisen & -beilagen > Waffeln, Pfannkuchen & French Toast -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Hors d'Oeuvre-Tabletts -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Pfannengerichte -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Sushi -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Tiefkühlgerichte +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Fertige Mahlzeiten und Hauptgerichte +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fertiggerichte > Fertige Vorspeisen und Beilagen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fleisch, Fisch, Meeresfrüchte & Eier Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fleisch, Fisch, Meeresfrüchte & Eier > Eier Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fleisch, Fisch, Meeresfrüchte & Eier > Fisch & Meeresfrüchte @@ -4411,40 +3779,25 @@ Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fleisch, Fisch, Meeresfrücht Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fleisch, Fisch, Meeresfrüchte & Eier > Fleisch Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fleisch, Fisch, Meeresfrüchte & Eier > Fleisch > Fleisch- & Wurstwaren Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fleisch, Fisch, Meeresfrüchte & Eier > Fleisch > Fleischkonserven -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fleisch, Fisch, Meeresfrüchte & Eier > Fleisch > Frikadellen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fleisch, Fisch, Meeresfrüchte & Eier > Fleisch > Frisches & Tiefgefrorenes Fleisch -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Fleisch, Fisch, Meeresfrüchte & Eier > Fleisch > Würstchen & Hotdogs Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backaromen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backaromen > Mandelextrakt -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backaromen > Vanilleextrakt Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backfett & Schmalz Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backmischungen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backmischungen > Backmischungen für Brot & Muffins -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backmischungen > Backmischungen für Brot & Muffins > Backmischungen für Muffins -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backmischungen > Backmischungen für Brot & Muffins > Brotmischungen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backmischungen > Backmischungen für Kuchen und Brownies -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backmischungen > Backmischungen für Waffeln und Pfannkuchen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backmischungen > Brezel-Backmischungen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backmischungen > Brotpudding-Backmischungen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backmischungen > Plätzchenmischungen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backmischungen für Waffeln und Pfannkuchen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backpulver Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backsoda +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Backwaren-Deko-Sets Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Blockschokoloade Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Bohnenpasten & -saucen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Dosen- und Trockenmilch Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Essbare Backdekoration -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Essbare Backdekoration > Essbare Backwaren-Deko -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Essbare Backdekoration > Farbzucker -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Essbare Backdekoration > Streusel -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Essbare Backdekoration > Zuckerperlen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Fertigbackmischungen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Essig +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Gemahlene Getreideprodukte Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Geschmacksneutrale Gelatine -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Gezuckerte Kondensmilch Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Hefe Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Kochwein -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Kokosraspeln -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Kuchenfüllung -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Künstliche Süßstoffe +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Kuchen- & Pastetenfüllungen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Lebensmittelfarben Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Maissirup Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Maisstärke @@ -4454,29 +3807,16 @@ Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Melasse Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Paniermehl Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Raspeln, Flocken & Tröpfchen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Chili-Öl -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Erdnussöl -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Hanföl -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Kochspray -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Kokosöl -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Maisöl -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Olivenöl -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Pflanzenöl -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Rapsöl -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Reisöl -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Sesamöl -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Sojabohnenöl -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Speiseöle > Walnussöl Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Starterkulturen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Stärkemehl & Paniermischungen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Tapiokaperlen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Teig Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Teig > Brot- & Gebäckteig -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Teig > Brot- & Gebäckteig > Pizzateig Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Teig > Pastetenteig -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Teig > Plätzchen- und Brownieteig +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Teig > Plätzchen- & Brownieteig Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Tomatenmark -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Ungesüßte Kondensmilch -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Zucker +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Zitronen- und Limettensaft +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Zucker & Süßstoffe Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Zuckerguss & Glasur Nahrungsmittel, Getränke & Tabak > Lebensmittel > Koch- & Backzutaten > Zuckerwatte Nahrungsmittel, Getränke & Tabak > Lebensmittel > Körner, Reis & Getreide @@ -4487,7 +3827,6 @@ Nahrungsmittel, Getränke & Tabak > Lebensmittel > Körner, Reis & Getreide > Fr Nahrungsmittel, Getränke & Tabak > Lebensmittel > Körner, Reis & Getreide > Gerste Nahrungsmittel, Getränke & Tabak > Lebensmittel > Körner, Reis & Getreide > Haferflocken, Grütze & Grieß Nahrungsmittel, Getränke & Tabak > Lebensmittel > Körner, Reis & Getreide > Hirse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Körner, Reis & Getreide > Maismehl Nahrungsmittel, Getränke & Tabak > Lebensmittel > Körner, Reis & Getreide > Quinoa Nahrungsmittel, Getränke & Tabak > Lebensmittel > Körner, Reis & Getreide > Reis Nahrungsmittel, Getränke & Tabak > Lebensmittel > Körner, Reis & Getreide > Roggen @@ -4496,96 +3835,72 @@ Nahrungsmittel, Getränke & Tabak > Lebensmittel > Milchprodukte Nahrungsmittel, Getränke & Tabak > Lebensmittel > Milchprodukte > Butter & Margarine Nahrungsmittel, Getränke & Tabak > Lebensmittel > Milchprodukte > Hüttenkäse Nahrungsmittel, Getränke & Tabak > Lebensmittel > Milchprodukte > Joghurt +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Milchprodukte > Kaffeeweißer Nahrungsmittel, Getränke & Tabak > Lebensmittel > Milchprodukte > Käse Nahrungsmittel, Getränke & Tabak > Lebensmittel > Milchprodukte > Sahne Nahrungsmittel, Getränke & Tabak > Lebensmittel > Milchprodukte > Sauerrahm Nahrungsmittel, Getränke & Tabak > Lebensmittel > Milchprodukte > Schlagsahne Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Brotnüsse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Cashewnüsse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Dika-Nüsse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Erdnüsse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Esskastanien -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Gemischte Nüsse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Geröstete Sojabohnen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Haselnüsse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Kolanüsse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Kürbiskerne -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Macadamia-Nüsse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Mamoncillo -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Mandeln -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Nüsse mit Schokoladenüberzug -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Paradiesnüsse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Paranuss -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Pekanuss -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Pinienkerne -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Pistazien -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Sesamkörner -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Sonnenblumenkerne -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Nüsse & Samen > Walnüsse Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Dosenbohnen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Dosenfrüchte Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Alfalfasprossen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Arakachas Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Artischocken Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Auberginen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Choi Sum +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Gartensalat +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Grünkohl +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Mangold +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Pak Choi +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Rauke +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Rübenblätter +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Salatmischungen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Spinat +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Wasserspinat +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Wegwarte +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blattgemüse > Yu Choy Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Blumenkohl Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Bohnen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Bohnensprossen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Borretsch Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Brokkoli Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Chinesischer Brokkoli -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Choi Sum Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Erbsen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Farnspitzen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Fenchelknollen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Flaschenkürbis -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Gartenkürbis -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Gartensalat +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Gemüsemischungen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Gobo-Wurzel -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Grünkohl Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Gurken Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Ingwer Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Kaktusblätter Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Kartoffeln -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Kichererbsen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Knoblauch Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Kohl Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Kohlrabi -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Kohlrüben +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Kürbisse Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Lotoswurzel Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Mais -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Mangold Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Maniok Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Möhren Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Okra -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Pak Choi -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Palmkohl Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Paprikaschoten Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Pastinaken Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Petersilienwurzeln Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Pilze Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Porree Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Radieschen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Rauke Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Rettich Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Rhabarber Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Rosenkohl Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Rutenkohl Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Rüben -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Rübenblätter -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Salatmischungen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Schalotten Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Sellerie Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Sellerieknollen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Spanische Artischocken Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Spargel -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Speisekürbisse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Spinat -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Steckrüben +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Sprossen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Steckrüben & Kohlrüben Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Süßkartoffeln Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Tamarillos Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Tannias @@ -4594,21 +3909,13 @@ Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & T Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Topinambur Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Wasserkastanien Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Wasserkresse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Wasserspinat -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Wegwarte -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Wegwarte > Chicorée -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Wegwarte > Krausblättrige Endivien -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Wegwarte > Radicchio -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Wegwarte > Winterendivie Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Weizengras Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Yambohnen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Yams -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Yu Choy Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Zuckerrohr Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Gemüse > Zwiebeln Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Ananas -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Aprikosen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Atemoyas Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Avocados Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Bananen @@ -4622,94 +3929,61 @@ Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & T Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Datteln Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Feigen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Granatäpfel -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Grapefruits Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Große Sapoten Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Guaven Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Homli-Früchte Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Kaktusfeigen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Kirschen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Kiwis Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Kokosnüsse -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Korinthen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Kumquats -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Limequats -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Limetten Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Longan Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Loquats Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Lychees -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Madroaa-Früchte -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Mandarinen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Mangos +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Madrono-Früchte Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Mangostanfrüchte Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Melonen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Melonen > Bittermelonen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Melonen > Horngurken -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Nektarinen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Orangen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Obstmischungen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Papayas Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Passionsfrüchte Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Persimonen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Pfirsiche -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Pflaumen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Physalis Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Pitahayas -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Plumcots Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Quitten Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Rambutans Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Sapoten Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Stachelannonen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Steinfrüchte +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Steinfrüchte > Aprikosen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Steinfrüchte > Kirschen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Steinfrüchte > Mangos +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Steinfrüchte > Pfirsiche & Nektarinen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Steinfrüchte > Pflaumen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Steinfrüchte > Plumcots Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Sternfrüchte Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Tamarinden -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Tangelos Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Trauben Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Zimtäpfel -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Zitronen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Zitrusfrüchte +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Zitrusfrüchte > Grapefruits +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Zitrusfrüchte > Kumquats +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Zitrusfrüchte > Limequats +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Zitrusfrüchte > Limetten +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Zitrusfrüchte > Orangen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Zitrusfrüchte > Tangelos +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Zitrusfrüchte > Zitronen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Frisches & Tiefgefrorenes Obst > Äpfel Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Fruchtsoßen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Fruchtsoßen > Apfelsoße -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Fruchtsoßen > Preiselbeersoße Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Gemüsekonserven Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Getrocknete Bohnen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Getrocknete Früchte -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Getrocknetes Gemüse +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Obstkonserven +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Obst & Gemüse > Trockengemüse Nahrungsmittel, Getränke & Tabak > Lebensmittel > Pasta & Nudeln +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Schokofrüchte Nahrungsmittel, Getränke & Tabak > Lebensmittel > Suppen & Brühen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Suppen & Brühen > Brühwürfel -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Suppen & Brühen > Dosensuppen & -brühen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Suppen & Brühen > Fertigsuppen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Kaugummi -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Bonbons -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Candy Corn -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Dauerlutscher -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Frucht- & Weingummi -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Fruchtgummis -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Früchte mit Schokoladenüberzug -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Geleebohnen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Karamellbonbons -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Kaubonbons -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Lakritze -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Marshmallow-Bonbons -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Minzbonbons -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Nougat -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Peanut Butter Cups -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Peppermint-Patties -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Pralinen-Sortiments -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Schokoladenriegel & Miniriegel -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Schokoladentrüffel -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Schokoriegel -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Süßigkeiten mit Krokant -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Süßwaren aus Karamell -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Taffy -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Kaugummi > Süßigkeiten & Schokolade > Zuckerstangen +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Süßigkeiten & Schokolade Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tiefkühl-Desserts & -Neuheiten -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tiefkühl-Desserts & -Neuheiten > Eishörnchen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tiefkühl-Desserts & -Neuheiten > Frozen Yogurt -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tiefkühl-Desserts & -Neuheiten > Fruchteis -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tiefkühl-Desserts & -Neuheiten > Gefrorene Desserts ohne Milchanteil -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tiefkühl-Desserts & -Neuheiten > Sorbet -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tiefkühl-Desserts & -Neuheiten > Speiseeis +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tiefkühl-Desserts & -Neuheiten > Eissnacks +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tiefkühl-Desserts & -Neuheiten > Speiseeis und gefrorener Joghurt Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tiefkühl-Desserts & -Neuheiten > Stieleis Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tofu- & Soja-Produkte Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tofu- & Soja-Produkte > Fleischalternativen @@ -4720,25 +3994,12 @@ Nahrungsmittel, Getränke & Tabak > Lebensmittel > Tofu- & Soja-Produkte > Tofu Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Brezeln Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Chips -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Chips > Bagel-Chips -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Chips > Kartoffelchips -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Chips > Mais- & Tortilla-Chips -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Chips > Pita-Chips Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Croûtons Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Dessertküchlein Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Fruchtsnacks Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Getreide- & Müsliriegel Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Grissini Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Kräcker -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Kräcker > Austerncracker -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Kräcker > Crackermischungen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Kräcker > Einfache Cracker -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Kräcker > Fladenbrotcracker -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Kräcker > Graham-Cracker -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Kräcker > Käsecracker -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Kräcker > Matzecracker -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Kräcker > Salzcracker -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Kräcker > Sandwich-Cracker Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Käse-Flips Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Popcorn Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Pudding & Gelatine-Snacks @@ -4749,106 +4010,71 @@ Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Schwein Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Sesamsticks Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Studentenfutter Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Trockenfleisch -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Trockenfleisch > Beef Jerky -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Trockenfleisch > Pork Jerky -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Vorspeisen & Snacks > Trockenfleisch > Turkey Jerky Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Anis -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Basilikum -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Bocksbart -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Bohnenkraut -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Chilipulver -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Dill -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Estragon -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Fenchelblätter -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Knoblauchpulver -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Koriander -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Kreuzkümmel -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Loorbeerblätter -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Majoran -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Minze -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Oregano -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Paprikapulver -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Petersilie -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Rosmarin -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Salbei -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Sauerampfer -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Schnittlauch -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Thymian -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Vanilleschoten -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Zimt -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Zitronengras -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Kräuter & Gewürze > Zwiebelpulver Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Mononatriumglutamat Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Pfeffer Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Salz -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzen & Verfeinern > Würzmischungen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Barbecue-Sauce Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Bratensoße Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Chilisauce -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Chutney Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Cocktailsaucen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Currysauce Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Dessertsaucen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Dessertsaucen > Aufschlagmittel Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Dessertsaucen > Eissaucen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Dessertsaucen > Fruchttopping -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Essig +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Eingelegtes Obst und Gemüse Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Fischsauce Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Honig -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Kaffeeweißer Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Ketchup -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Marinaden +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Marinaden und Grillsoßen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Mayonnaise Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Meerrettichsaucen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Oliven & Kapern Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Pastasoßen -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Pickles & Relishes Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Pizzasoße Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Salatdressing Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Sataysauce Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Sauce tartare Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Senf Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Sirup -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Sirup > Agavensirup -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Sirup > Ahornsirup -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Sirup > Aromatisierter Sirup -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Sirup > Sirup für Schneeballeis Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Sojasauce -Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Steaksauce +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Süßsaure Soßen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Tahini +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Weiße Soßen und Rahmsoßen Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Worcestershiresauce +Nahrungsmittel, Getränke & Tabak > Lebensmittel > Würzmittel & Saucen > Würzsoße und Chutney Nahrungsmittel, Getränke & Tabak > Tabakprodukte Nahrungsmittel, Getränke & Tabak > Tabakprodukte > Kautabak -Nahrungsmittel, Getränke & Tabak > Tabakprodukte > Rauchlose Zigaretten +Nahrungsmittel, Getränke & Tabak > Tabakprodukte > Pfeifen Nahrungsmittel, Getränke & Tabak > Tabakprodukte > Tabak +Nahrungsmittel, Getränke & Tabak > Tabakprodukte > Vaporizer & Rauchlose Zigaretten Nahrungsmittel, Getränke & Tabak > Tabakprodukte > Zigaretten Nahrungsmittel, Getränke & Tabak > Tabakprodukte > Zigarren Religion & Feierlichkeiten Religion & Feierlichkeiten > Artikel für Gedenkzeremonien Religion & Feierlichkeiten > Artikel für Gedenkzeremonien > Urnen Religion & Feierlichkeiten > Artikel für Hochzeitszeremonien +Religion & Feierlichkeiten > Artikel für Hochzeitszeremonien > Hochzeitsläufer Religion & Feierlichkeiten > Artikel für Hochzeitszeremonien > Körbe für Blumenmädchen Religion & Feierlichkeiten > Artikel für Hochzeitszeremonien > Ringkissen Religion & Feierlichkeiten > Religiöse Artikel Religion & Feierlichkeiten > Religiöse Artikel > Altare +Religion & Feierlichkeiten > Religiöse Artikel > Gebetskarten Religion & Feierlichkeiten > Religiöse Artikel > Gebetsketten Religion & Feierlichkeiten > Religiöse Artikel > Religiöse Schleier +Religion & Feierlichkeiten > Religiöse Artikel > Tarotkarten Software Software > Computersoftware Software > Computersoftware > Antiviren- & Sicherheitssoftware Software > Computersoftware > Betriebssysteme Software > Computersoftware > Büroanwendungssoftware Software > Computersoftware > Compiler & Programmierungstools -Software > Computersoftware > Computer-Dienstprogramme & -Wartung +Software > Computersoftware > Computerdienstprogramme & -wartung +Software > Computersoftware > GPS-Karten & -Software Software > Computersoftware > Handheld- & PDA-Software Software > Computersoftware > Lernsoftware Software > Computersoftware > Netzwerksoftware -Software > Computersoftware > Referenzsoftware -Software > Computersoftware > Referenzsoftware > GPS-Karten & -Software -Software > Computersoftware > Referenzsoftware > Wörterbuch- & Übersetzungssoftware Software > Computersoftware > Software für Multimedia & Design Software > Computersoftware > Software für Multimedia & Design > Animationsbearbeitungssoftware Software > Computersoftware > Software für Multimedia & Design > Graphic-Design-Software @@ -4863,58 +4089,20 @@ Software > Computersoftware > Software für Multimedia & Design > Web-Design-Sof Software > Computersoftware > Steuer- & Buchhaltungssoftware Software > Computersoftware > Unternehmens- & Produktivitätssoftware Software > Computersoftware > Wiederherstellungsdatenträger +Software > Computersoftware > Wörterbuch- & Übersetzungssoftware Software > Digital-Content-Software -Software > Digital-Content-Software > Clipart Software > Digital-Content-Software > Computer-Icons Software > Digital-Content-Software > Desktop-Hintergrundbilder +Software > Digital-Content-Software > Digitales Bildmaterial Software > Digital-Content-Software > Dokumentvorlagen Software > Digital-Content-Software > Fonts +Software > Digital-Content-Software > Stock-Fotografie & Videoclips Software > Digital-Content-Software > Virtuelles Geld Software > PC- & Videospiele -Software > PC- & Videospiele > Atari 2600-Spiele -Software > PC- & Videospiele > Atari 5200-Spiele -Software > PC- & Videospiele > Atari Lynx-Spiele -Software > PC- & Videospiele > ColecoVision-Spiele -Software > PC- & Videospiele > Computerspiele -Software > PC- & Videospiele > Computerspiele > Mac-Spiele -Software > PC- & Videospiele > Computerspiele > PC-Spiele -Software > PC- & Videospiele > Dreamcast-Spiele -Software > PC- & Videospiele > Game Boy-Spiele (gesamt) -Software > PC- & Videospiele > Game Boy-Spiele (gesamt) > Game Boy Advance-Spiele -Software > PC- & Videospiele > Game Boy-Spiele (gesamt) > Gameboy-Spiele -Software > PC- & Videospiele > Game Gear-Spiele -Software > PC- & Videospiele > GameCube-Spiele -Software > PC- & Videospiele > Intellivision-Spiele -Software > PC- & Videospiele > Jaguar-Spiele -Software > PC- & Videospiele > LeapFrog-Spiele -Software > PC- & Videospiele > N-Gage-Spiele -Software > PC- & Videospiele > Neo Geo Pocket-Spiele -Software > PC- & Videospiele > Neo Geo-Spiele -Software > PC- & Videospiele > Nintendo 64-Spiele -Software > PC- & Videospiele > Nintendo DS-Spiele -Software > PC- & Videospiele > Nintendo Entertainment System-Spiele -Software > PC- & Videospiele > PC Engine-Spiele -Software > PC- & Videospiele > PlayStation Portable-Spiele -Software > PC- & Videospiele > PlayStation Vita-Spiele -Software > PC- & Videospiele > PlayStation-Spiele (gesamt) -Software > PC- & Videospiele > PlayStation-Spiele (gesamt) > PlayStation (Original)-Spiele -Software > PC- & Videospiele > PlayStation-Spiele (gesamt) > PlayStation 2-Spiele -Software > PC- & Videospiele > PlayStation-Spiele (gesamt) > PlayStation 3-Spiele -Software > PC- & Videospiele > Sega Genesis-Spiele -Software > PC- & Videospiele > Sega Master System-Spiele -Software > PC- & Videospiele > Super Nintendo-Spiele -Software > PC- & Videospiele > Wii U-Spiele -Software > PC- & Videospiele > Wii-Spiele -Software > PC- & Videospiele > Xbox-Spiele (gesamt) -Software > PC- & Videospiele > Xbox-Spiele (gesamt) > Xbox (Original)-Spiele -Software > PC- & Videospiele > Xbox-Spiele (gesamt) > Xbox 360-Spiele Spielzeuge & Spiele Spielzeuge & Spiele > Puzzles & Geduldspiele Spielzeuge & Spiele > Puzzles & Geduldspiele > Holzsteckpuzzle Spielzeuge & Spiele > Puzzles & Geduldspiele > Mechanische Geduldspiele -Spielzeuge & Spiele > Puzzles & Geduldspiele > Mechanische Geduldspiele > Entwirrspiele -Spielzeuge & Spiele > Puzzles & Geduldspiele > Mechanische Geduldspiele > Geschicklichkeitsspiele -Spielzeuge & Spiele > Puzzles & Geduldspiele > Mechanische Geduldspiele > Zusammensetzspiele Spielzeuge & Spiele > Puzzles & Geduldspiele > Puzzle-Zubehör Spielzeuge & Spiele > Puzzles & Geduldspiele > Puzzles Spielzeuge & Spiele > Spiel-Timer @@ -4923,40 +4111,33 @@ Spielzeuge & Spiele > Spiele > Battle Tops Spielzeuge & Spiele > Spiele > Bingo-Spiele Spielzeuge & Spiele > Spiele > Blackjack- & Würfelspiel-Sets Spielzeuge & Spiele > Spiele > Brettspiele -Spielzeuge & Spiele > Spiele > Brettspiele > Backgammon-Spiele -Spielzeuge & Spiele > Spiele > Brettspiele > Damespiele -Spielzeuge & Spiele > Spiele > Brettspiele > Parcheesi -Spielzeuge & Spiele > Spiele > Brettspiele > Schachspiele +Spielzeuge & Spiele > Spiele > Geschicklichkeitsspiele +Spielzeuge & Spiele > Spiele > Glücksspielautomaten Spielzeuge & Spiele > Spiele > Kampfkreiselzubehör Spielzeuge & Spiele > Spiele > Kartenspiele -Spielzeuge & Spiele > Spiele > Kartenspiele > Sammelkartenspiele -Spielzeuge & Spiele > Spiele > Kartenspiele > Traditionelle Spielkarten Spielzeuge & Spiele > Spiele > Kartenspielzubehör Spielzeuge & Spiele > Spiele > Legespiele -Spielzeuge & Spiele > Spiele > Legespiele > Dominospiele -Spielzeuge & Spiele > Spiele > Legespiele > Mah-Jongg Spielzeuge & Spiele > Spiele > Pokerchip-Zubehör Spielzeuge & Spiele > Spiele > Pokerchip-Zubehör > Behälter für Pokerchips Spielzeuge & Spiele > Spiele > Pokerchips & -sets -Spielzeuge & Spiele > Spiele > Rouletterad & Zubehör -Spielzeuge & Spiele > Spiele > Stapelspiele +Spielzeuge & Spiele > Spiele > Rouletteräder & -zubehör Spielzeuge & Spiele > Spiele > Tragbare elektronische Spiele Spielzeuge & Spiele > Spiele > Würfelsets & -spiele -Spielzeuge & Spiele > Spiele > Würfelsets & -spiele > Würfel & Würfelsets -Spielzeuge & Spiele > Spiele > Würfelsets & -spiele > Würfelspiele Spielzeuge & Spiele > Spielzeug für draußen Spielzeuge & Spiele > Spielzeug für draußen > Hüpfburg-Zubehör Spielzeuge & Spiele > Spielzeug für draußen > Hüpfburgen Spielzeuge & Spiele > Spielzeug für draußen > Rutschen Spielzeuge & Spiele > Spielzeug für draußen > Sandkästen -Spielzeuge & Spiele > Spielzeug für draußen > Schaukeln & Rutschen +Spielzeuge & Spiele > Spielzeug für draußen > Schaukeln Spielzeuge & Spiele > Spielzeug für draußen > Schaukeln & Spielplatzgeräte -Spielzeuge & Spiele > Spielzeug für draußen > Spielfallschirme Spielzeuge & Spiele > Spielzeug für draußen > Spielhäuser Spielzeuge & Spiele > Spielzeug für draußen > Spielzelte & -tunnel +Spielzeuge & Spiele > Spielzeug für draußen > Springstöcke +Spielzeuge & Spiele > Spielzeug für draußen > Stelzen +Spielzeuge & Spiele > Spielzeug für draußen > Trampoline +Spielzeuge & Spiele > Spielzeug für draußen > Trampolinzubehör Spielzeuge & Spiele > Spielzeug für draußen > Wasserspielgeräte -Spielzeuge & Spiele > Spielzeug für draußen > Wasserspielgeräte > Wasserparks -Spielzeuge & Spiele > Spielzeug für draußen > Wasserspielgeräte > Wasserrutschen für den Garten +Spielzeuge & Spiele > Spielzeug für draußen > Wasserspielgeräte > Planschbecken und Wasserrutschen Spielzeuge & Spiele > Spielzeug für draußen > Wasserspielgeräte > Wassersprenger für Kinder Spielzeuge & Spiele > Spielzeug für draußen > Wasserspielgeräte > Wassertische Spielzeuge & Spiele > Spielzeug für draußen > Wippen @@ -4972,10 +4153,9 @@ Spielzeuge & Spiele > Spielzeuge > Bausteine & Bauspielzeug > Schaumstoffbaustei Spielzeuge & Spiele > Spielzeuge > Bausteine & Bauspielzeug > Steckbausteine Spielzeuge & Spiele > Spielzeuge > Bällebad-Zubehör Spielzeuge & Spiele > Spielzeuge > Bällebad-Zubehör > Bälle für Bällebad +Spielzeuge & Spiele > Spielzeuge > Bällebäder Spielzeuge & Spiele > Spielzeuge > Bürospielzeug Spielzeuge & Spiele > Spielzeuge > Bürospielzeug > Magnetspielzeug -Spielzeuge & Spiele > Spielzeuge > Elektronisches Spielzeug -Spielzeuge & Spiele > Spielzeuge > Elektronisches Spielzeug > Roboter Spielzeuge & Spiele > Spielzeuge > Fahr, Wipp- & Schaukelspielzeug Spielzeuge & Spiele > Spielzeuge > Fahr, Wipp- & Schaukelspielzeug > Elektrische Kinderfahrzeuge Spielzeuge & Spiele > Spielzeuge > Fahr, Wipp- & Schaukelspielzeug > Hand- & Bollerwagen @@ -4985,8 +4165,6 @@ Spielzeuge & Spiele > Spielzeuge > Fahr, Wipp- & Schaukelspielzeug > Wipp- & Sch Spielzeuge & Spiele > Spielzeuge > Ferngesteuertes Spielzeug Spielzeuge & Spiele > Spielzeuge > Ferngesteuertes Spielzeug > Ferngesteuerte Autos Spielzeuge & Spiele > Spielzeuge > Ferngesteuertes Spielzeug > Ferngesteuerte Boote -Spielzeuge & Spiele > Spielzeuge > Ferngesteuertes Spielzeug > Ferngesteuerte Boote > Ferngesteuerte U-Boote -Spielzeuge & Spiele > Spielzeuge > Ferngesteuertes Spielzeug > Ferngesteuerte Figuren Spielzeuge & Spiele > Spielzeuge > Ferngesteuertes Spielzeug > Ferngesteuerte Flugzeuge Spielzeuge & Spiele > Spielzeuge > Ferngesteuertes Spielzeug > Ferngesteuerte Hubschrauber Spielzeuge & Spiele > Spielzeuge > Ferngesteuertes Spielzeug > Ferngesteuerte Motorräder @@ -4999,7 +4177,7 @@ Spielzeuge & Spiele > Spielzeuge > Fliegendes Spielzeug > Luft- & Wasserraketen Spielzeuge & Spiele > Spielzeuge > Fliegendes Spielzeug > Spielzeug-Fallschirme Spielzeuge & Spiele > Spielzeuge > Fliegendes Spielzeug > Spielzeug-Segelflugzeuge Spielzeuge & Spiele > Spielzeuge > Interaktives Spielzeug -Spielzeuge & Spiele > Spielzeuge > Interaktives Spielzeug > Ball- und Becherspiele +Spielzeuge & Spiele > Spielzeuge > Interaktives Spielzeug > Ball- & Becherspiele Spielzeuge & Spiele > Spielzeuge > Interaktives Spielzeug > Flummis Spielzeuge & Spiele > Spielzeuge > Interaktives Spielzeug > Hampelmänner Spielzeuge & Spiele > Spielzeuge > Interaktives Spielzeug > Jo-Jos @@ -5011,22 +4189,23 @@ Spielzeuge & Spiele > Spielzeuge > Interaktives Spielzeug > Seifenblasen-Spielze Spielzeuge & Spiele > Spielzeuge > Interaktives Spielzeug > Spiele mit Bändern Spielzeuge & Spiele > Spielzeuge > Interaktives Spielzeug > Springspirale Spielzeuge & Spiele > Spielzeuge > Interaktives Spielzeug > Teile und Zubehör für Jo-Jos -Spielzeuge & Spiele > Spielzeuge > Interaktives Spielzeug > Teile und Zubehör für Jo-Jos > JoJo-Handschuhe Spielzeuge & Spiele > Spielzeuge > Kinder-Rollenspiele Spielzeuge & Spiele > Spielzeuge > Kinder-Rollenspiele > Berufespielzeug & Rollenspiele +Spielzeuge & Spiele > Spielzeuge > Kinder-Rollenspiele > Einkaufsladen Spielzeuge & Spiele > Spielzeuge > Kinder-Rollenspiele > Elektronisches Rollenspielzeug Spielzeuge & Spiele > Spielzeuge > Kinder-Rollenspiele > Gartengeräte für Kinder Spielzeuge & Spiele > Spielzeuge > Kinder-Rollenspiele > Haushaltsspielzeug Spielzeuge & Spiele > Spielzeuge > Kinder-Rollenspiele > Spielgeld & Banken Spielzeuge & Spiele > Spielzeuge > Kinder-Rollenspiele > Spielwerkzeug Spielzeuge & Spiele > Spielzeuge > Kinder-Rollenspiele > Spielzeugküchen & Spielzeuglebensmittel -Spielzeuge & Spiele > Spielzeuge > Kugelbäder Spielzeuge & Spiele > Spielzeuge > Lernspielzeug Spielzeuge & Spiele > Spielzeuge > Lernspielzeug > Ameisenfarmen +Spielzeuge & Spiele > Spielzeuge > Lernspielzeug > Astronomisches Spielzeug und astronomische Modelle Spielzeuge & Spiele > Spielzeuge > Lernspielzeug > Insektensammlung +Spielzeuge & Spiele > Spielzeuge > Lernspielzeug > Lernkarten Spielzeuge & Spiele > Spielzeuge > Lernspielzeug > Lesespiele Spielzeuge & Spiele > Spielzeuge > Lernspielzeug > Schieberechner -Spielzeuge & Spiele > Spielzeuge > Lernspielzeug > Wissenschafts-Sets +Spielzeuge & Spiele > Spielzeuge > Lernspielzeug > Wissenschaftssets Spielzeuge & Spiele > Spielzeuge > Musikspielzeuge Spielzeuge & Spiele > Spielzeuge > Musikspielzeuge > Spielzeuginstrumente Spielzeuge & Spiele > Spielzeuge > Optische Spielzeuge @@ -5034,11 +4213,6 @@ Spielzeuge & Spiele > Spielzeuge > Optische Spielzeuge > Kaleidoskope Spielzeuge & Spiele > Spielzeuge > Optische Spielzeuge > Prismen Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Action- & Spielzeugfiguren -Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Action- & Spielzeugfiguren > Action-Figuren -Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Action- & Spielzeugfiguren > Spielzeugsoldaten -Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Action- & Spielzeugfiguren > Tierfiguren -Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Action- & Spielzeugfiguren > Tierfiguren > Dinosaurierfiguren -Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Action- & Spielzeugfiguren > Tierfiguren > Pferde- & Ponyfiguren Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Marionetten Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Papier- & Magnetpuppen Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Puppen @@ -5050,13 +4224,11 @@ Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Wackelfiguren Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Zubehör für Puppen & Actionfiguren Spielzeuge & Spiele > Spielzeuge > Puppen, Spielkombinationen & Spielzeugfiguren > Zubehör für Puppen & Puppentheater +Spielzeuge & Spiele > Spielzeuge > Roboter Spielzeuge & Spiele > Spielzeuge > Spielfahrzeug-Zubehör -Spielzeuge & Spiele > Spielzeuge > Spielfahrzeug-Zubehör > Kinderfahrzeugteile & Zubehör -Spielzeuge & Spiele > Spielzeuge > Spielfahrzeug-Zubehör > Zubehör für Schiebefahrzeuge mit Pedalen -Spielzeuge & Spiele > Spielzeuge > Spielfahrzeug-Zubehör > Zubehör für elektrisches Spielfahrzeug -Spielzeuge & Spiele > Spielzeuge > Spielfahrzeug-Zubehör > Zubehör für elektrisches Spielfahrzeug > Ladegeräte für elektrisches Spielfahrzeug +Spielzeuge & Spiele > Spielzeuge > Spielzeug-Geschenkkörbe Spielzeuge & Spiele > Spielzeuge > Spielzeuge zum Malen & Zeichnen -Spielzeuge & Spiele > Spielzeuge > Spielzeuge zum Malen & Zeichnen > Bastelsets +Spielzeuge & Spiele > Spielzeuge > Spielzeuge zum Malen & Zeichnen > Spielteig & Knetmasse Spielzeuge & Spiele > Spielzeuge > Spielzeuge zum Malen & Zeichnen > Zeichentafeln Spielzeuge & Spiele > Spielzeuge > Spielzeugfahrzeuge Spielzeuge & Spiele > Spielzeuge > Spielzeugfahrzeuge > Rennbahnen @@ -5069,249 +4241,533 @@ Spielzeuge & Spiele > Spielzeuge > Spielzeugfahrzeuge > Spielzeugmotorräder Spielzeuge & Spiele > Spielzeuge > Spielzeugfahrzeuge > Spielzeugraumschiffe Spielzeuge & Spiele > Spielzeuge > Spielzeugfahrzeuge > Züge & Eisenbahnsets Spielzeuge & Spiele > Spielzeuge > Spielzeugwaffen -Spielzeuge & Spiele > Spielzeuge > Spielzeugwaffen > Wasserpistolen +Spielzeuge & Spiele > Spielzeuge > Spielzeugwaffenzubehör +Spielzeuge & Spiele > Spielzeuge > Sportspielzeug-Zubehör +Spielzeuge & Spiele > Spielzeuge > Sportspielzeug-Zubehör > Fitnessspielzeug-Zubehör +Spielzeuge & Spiele > Spielzeuge > Sportspielzeug-Zubehör > Fitnessspielzeug-Zubehör > Hula-Hoop-Zubehör Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge -Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > American Football für Kinder -Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > American Football für Kinder > Fußbälle aus Schaumstoff +Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Angelspielzeug Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Baseball für Kinder Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Basketball für Kinder Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Bowling für Kinder Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Bumerangs +Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Fingerboards & Fingerboard-Sets Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Fitness-Spielzeug Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Fitness-Spielzeug > Hula-Hoop-Reifen Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Footbags Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Frisbees +Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Fußball für Kinder Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Golf für Kinder Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Hockey für Kinder +Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Kinderspielbälle +Spielzeuge & Spiele > Spielzeuge > Sportspielzeuge > Spielzeugschläger Spielzeuge & Spiele > Spielzeuge > Strand- & Sandspielzeug -Spielzeuge & Spiele > Spielzeuge > Strand- & Sandspielzeug > Sandeimer -Spielzeuge & Spiele > Spielzeuge > Strand- & Sandspielzeug > Strandbälle +Spielzeuge & Spiele > Spielzeuge > Zubehör für Flugspielzeug +Spielzeuge & Spiele > Spielzeuge > Zubehör für Flugspielzeug > Zubehör für Drachen +Spielzeuge & Spiele > Spielzeuge > Zubehör für Flugspielzeug > Zubehör für Drachen > Wickelspulen für Drachen Spielzeuge & Spiele > Spielzeuge > Zubehör für Spielzeugfahrzeuge Spielzeuge & Spiele > Spielzeuge > Zubehör für Spielzeugfahrzeuge > Autorennbahn-Zubehör Spielzeuge & Spiele > Spielzeuge > Zubehör für Spielzeugfahrzeuge > Zubehör für Spielzeugeisenbahn Spielzeuge & Spiele > Spielzeuge > Zubehör für ferngesteuerte Fahrzeuge -Spielzeuge & Spiele > Spielzeuge > Zubehör für fliegendes Spielzeug -Spielzeuge & Spiele > Spielzeuge > Zubehör für fliegendes Spielzeug > Zubehör für Drachen -Spielzeuge & Spiele > Spielzeuge > Zubehör für fliegendes Spielzeug > Zubehör für Drachen > Wickelspulen für Drachen Sportartikel -Sportartikel > Kampfsport -Sportartikel > Kampfsport > Box- & Trainingssackzubehör -Sportartikel > Kampfsport > Boxen -Sportartikel > Kampfsport > Boxen > Boxhandschuhe -Sportartikel > Kampfsport > Boxen > Boxringe -Sportartikel > Kampfsport > Boxen > Boxringelemente -Sportartikel > Kampfsport > Boxen > Boxsackzubehör -Sportartikel > Kampfsport > Boxschutzbekleidung -Sportartikel > Kampfsport > Boxsäcke -Sportartikel > Kampfsport > Fechten -Sportartikel > Kampfsport > Fechten > Fechtschutzbekleidung -Sportartikel > Kampfsport > Fechten > Fechtschutzbekleidung > Fechthandschuhe -Sportartikel > Kampfsport > Fechten > Fechtschutzbekleidung > Fechtmasken -Sportartikel > Kampfsport > Kampfkunst -Sportartikel > Kampfsport > Kampfkunst > Kampfsportgürtel -Sportartikel > Kampfsport > Ringen -Sportartikel > Kampfsport > Ringen > Ringermatten -Sportartikel > Kampfsport > Ringen > Schutzbekleidung für Ringer -Sportartikel > Kampfsport > Training-Dummys -Sportartikel > Luftsport -Sportartikel > Luftsport > Drachenfliegen -Sportartikel > Luftsport > Drachenfliegen > Hängegleiter -Sportartikel > Luftsport > Fallschirme -Sportartikel > Luftsport > Fluganzüge -Sportartikel > Mannschaftssportarten -Sportartikel > Mannschaftssportarten > American Football -Sportartikel > Mannschaftssportarten > American Football > American Football-Handschuhe -Sportartikel > Mannschaftssportarten > American Football > American Football-Kicking Tees -Sportartikel > Mannschaftssportarten > American Football > American Football-Schutzbekleidung -Sportartikel > Mannschaftssportarten > American Football > American Football-Schutzbekleidung > American Football-Girdle Shorts -Sportartikel > Mannschaftssportarten > American Football > American Football-Schutzbekleidung > American Football-Helme -Sportartikel > Mannschaftssportarten > American Football > American Football-Schutzbekleidung > American Football-Helmzubehör -Sportartikel > Mannschaftssportarten > American Football > American Football-Schutzbekleidung > American Football-Helmzubehör > American Football-Facemasks -Sportartikel > Mannschaftssportarten > American Football > American Football-Schutzbekleidung > American Football-Helmzubehör > American Football-Helmpolster -Sportartikel > Mannschaftssportarten > American Football > American Football-Schutzbekleidung > American Football-Helmzubehör > American Football-Helmvisiere -Sportartikel > Mannschaftssportarten > American Football > American Football-Schutzbekleidung > American Football-Helmzubehör > American Football-Kinnriemen -Sportartikel > Mannschaftssportarten > American Football > American Football-Schutzbekleidung > American Football-Nackenschutz -Sportartikel > Mannschaftssportarten > American Football > American Football-Schutzbekleidung > American Football-Schulterpads -Sportartikel > Mannschaftssportarten > American Football > American Football-Torstangen -Sportartikel > Mannschaftssportarten > American Football > American Football-Trainingsausrüstung -Sportartikel > Mannschaftssportarten > American Football > Football-Bälle -Sportartikel > Mannschaftssportarten > Ballpumpen -Sportartikel > Mannschaftssportarten > Balltaschen -Sportartikel > Mannschaftssportarten > Baseball -Sportartikel > Mannschaftssportarten > Baseball > Baseball- & Softballhandschuhe -Sportartikel > Mannschaftssportarten > Baseball > Baseball- & Softballhandschuhe > Baseballhandschuhe -Sportartikel > Mannschaftssportarten > Baseball > Baseball- & Softballhandschuhe > Softballhandschuhe -Sportartikel > Mannschaftssportarten > Baseball > Baseball-Bases -Sportartikel > Mannschaftssportarten > Baseball > Baseball-Bälle -Sportartikel > Mannschaftssportarten > Baseball > Baseball-Schutzbekleidung -Sportartikel > Mannschaftssportarten > Baseball > Baseball-Schutzbekleidung > Baseballhelme -Sportartikel > Mannschaftssportarten > Baseball > Baseballnetze -Sportartikel > Mannschaftssportarten > Baseball > Baseballschläger -Sportartikel > Mannschaftssportarten > Baseball > Schlaghandschuhe -Sportartikel > Mannschaftssportarten > Baseball > Wurfmaschinen -Sportartikel > Mannschaftssportarten > Basketball -Sportartikel > Mannschaftssportarten > Basketball > Basketballkorbteile & -zubehör -Sportartikel > Mannschaftssportarten > Basketball > Basketballkorbteile & -zubehör > Basketball-Korbbretter -Sportartikel > Mannschaftssportarten > Basketball > Basketballkorbteile & -zubehör > Basketballnetze -Sportartikel > Mannschaftssportarten > Basketball > Basketballkorbteile & -zubehör > Basketballpfosten -Sportartikel > Mannschaftssportarten > Basketball > Basketballkorbteile & -zubehör > Basketballringe -Sportartikel > Mannschaftssportarten > Basketball > Basketballkorbteile & -zubehör > Schutzverkleidung für Basketballkorb -Sportartikel > Mannschaftssportarten > Basketball > Basketballkörbe -Sportartikel > Mannschaftssportarten > Basketball > Basketbälle -Sportartikel > Mannschaftssportarten > Beachvolleyball -Sportartikel > Mannschaftssportarten > Beachvolleyball > Beachvolleyball-Bälle -Sportartikel > Mannschaftssportarten > Beachvolleyball > Beachvolleyball-Netze -Sportartikel > Mannschaftssportarten > Broomball -Sportartikel > Mannschaftssportarten > Broomball > Broomballschläger -Sportartikel > Mannschaftssportarten > Cheerleading -Sportartikel > Mannschaftssportarten > Cheerleading > Cheerleading-Megaphone -Sportartikel > Mannschaftssportarten > Cheerleading > Pom-Poms -Sportartikel > Mannschaftssportarten > Cricket -Sportartikel > Mannschaftssportarten > Cricket > Cricketbälle -Sportartikel > Mannschaftssportarten > Cricket > Crickethandschuhe -Sportartikel > Mannschaftssportarten > Cricket > Crickethelme -Sportartikel > Mannschaftssportarten > Cricket > Cricketschläger -Sportartikel > Mannschaftssportarten > Curling -Sportartikel > Mannschaftssportarten > Feldhockey -Sportartikel > Mannschaftssportarten > Feldhockey > Feldhockeybälle -Sportartikel > Mannschaftssportarten > Feldhockey > Feldhockeyhandschuhe -Sportartikel > Mannschaftssportarten > Feldhockey > Feldhockeyschläger -Sportartikel > Mannschaftssportarten > Feldhockey > Feldhockeytore -Sportartikel > Mannschaftssportarten > Fußball -Sportartikel > Mannschaftssportarten > Fußball > Eckfahnen -Sportartikel > Mannschaftssportarten > Fußball > Fußball-Kapitänsbinden -Sportartikel > Mannschaftssportarten > Fußball > Fußballschienbeinschützer -Sportartikel > Mannschaftssportarten > Fußball > Fußballtore & -netze -Sportartikel > Mannschaftssportarten > Fußball > Fußbälle -Sportartikel > Mannschaftssportarten > Fußball > Fußbälle > Futsal-Bälle -Sportartikel > Mannschaftssportarten > Fußball > Torwarthandschuhe -Sportartikel > Mannschaftssportarten > Fußball > Zubehör für Fußballtore -Sportartikel > Mannschaftssportarten > Handball -Sportartikel > Mannschaftssportarten > Hockey -Sportartikel > Mannschaftssportarten > Hockey > Hockey-Schutzausrüstung -Sportartikel > Mannschaftssportarten > Hockey > Hockey-Schutzausrüstung > Hockey-Körperpolster -Sportartikel > Mannschaftssportarten > Hockey > Hockey-Schutzausrüstung > Hockeyhandschuhe -Sportartikel > Mannschaftssportarten > Hockey > Hockey-Schutzausrüstung > Hockeyhandschuhe > Eishockeyhandschuhe -Sportartikel > Mannschaftssportarten > Hockey > Hockey-Schutzausrüstung > Hockeyhelme -Sportartikel > Mannschaftssportarten > Hockey > Hockey-Schutzausrüstung > Hockeyhelme > Eishockeyhelme -Sportartikel > Mannschaftssportarten > Hockey > Hockey-Schutzausrüstung > Hockeyhosen -Sportartikel > Mannschaftssportarten > Hockey > Hockeybälle & Pucks -Sportartikel > Mannschaftssportarten > Hockey > Hockeybälle & Pucks > Hockeypucks -Sportartikel > Mannschaftssportarten > Hockey > Hockeybälle & Pucks > Rollhockeybälle -Sportartikel > Mannschaftssportarten > Hockey > Hockeyschlitten -Sportartikel > Mannschaftssportarten > Hockey > Hockeyschläger -Sportartikel > Mannschaftssportarten > Hockey > Hockeyschläger > Eishockeyschläger -Sportartikel > Mannschaftssportarten > Hockey > Hockeyschläger > Hockeyschlager für Street- und Rollhockey -Sportartikel > Mannschaftssportarten > Hockey > Hockeyschlägerpflege -Sportartikel > Mannschaftssportarten > Hockey > Hockeyschlägerteile -Sportartikel > Mannschaftssportarten > Hockey > Hockeyschlägerteile > Hockeyschlägerkellen -Sportartikel > Mannschaftssportarten > Hockey > Hockeyschlägerteile > Hockeyschlägerschäfte -Sportartikel > Mannschaftssportarten > Hockey > Hockeytore -Sportartikel > Mannschaftssportarten > Hockey > Hockeytore > Eishockeytore -Sportartikel > Mannschaftssportarten > Lacrosse -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Bälle -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Schläger -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Schlägerteile -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Schlägerteile > Lacrosse-Schlägerköpfe -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Schlägerteile > Lacrosse-Schlägernetze & Strings -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Schlägerteile > Lacrosse-Schlägerschäfte -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Schutzbekleidung -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Schutzbekleidung > Lacrosse-Armschutz -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Schutzbekleidung > Lacrosse-Augenschutz -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Schutzbekleidung > Lacrosse-Handschuhe -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Schutzbekleidung > Lacrosse-Helme -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Schutzbekleidung > Lacrosse-Schulterschutz -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Tore -Sportartikel > Mannschaftssportarten > Lacrosse > Lacrosse-Trainingsausrüstung -Sportartikel > Mannschaftssportarten > Rounders -Sportartikel > Mannschaftssportarten > Rounders > Rounders-Handschuhe -Sportartikel > Mannschaftssportarten > Rounders > Rounders-Schläger -Sportartikel > Mannschaftssportarten > Rugby -Sportartikel > Mannschaftssportarten > Rugby > Rugby-Bälle -Sportartikel > Mannschaftssportarten > Rugby > Rugby-Handschuhe -Sportartikel > Mannschaftssportarten > Rugby > Rugby-Helme -Sportartikel > Mannschaftssportarten > Rugby > Rugby-Tore -Sportartikel > Mannschaftssportarten > Rugby > Rugby-Trainingsausrüstung -Sportartikel > Mannschaftssportarten > Schiedsrichterausrüstung -Sportartikel > Mannschaftssportarten > Schiedsrichterausrüstung > Linienrichterfahnen -Sportartikel > Mannschaftssportarten > Schiedsrichterausrüstung > Schiedsrichterpfeifen -Sportartikel > Mannschaftssportarten > Schiedsrichterausrüstung > Strafkarten -Sportartikel > Mannschaftssportarten > Softball -Sportartikel > Mannschaftssportarten > Softball > Softball-Bälle -Sportartikel > Mannschaftssportarten > Softball > Softball-Schutzbekleidung -Sportartikel > Mannschaftssportarten > Softball > Softballschläger -Sportartikel > Mannschaftssportarten > Volleyball -Sportartikel > Mannschaftssportarten > Volleyball > Volleyball-Bälle -Sportartikel > Mannschaftssportarten > Volleyball > Volleyballnetze -Sportartikel > Mannschaftssportarten > Volleyball > Volleyballtaschen -Sportartikel > Mannschaftssportarten > Zubehör zum Aufpumpen von Bällen -Sportartikel > Mannschaftssportarten > Zubehör zum Aufpumpen von Bällen > Ballpumpenaufsätze +Sportartikel > Athletik +Sportartikel > Athletik > Allgemeine Sportartikel +Sportartikel > Athletik > Allgemeine Sportartikel > Ablagen & Transportwagen für Gymnastikmatten +Sportartikel > Athletik > Allgemeine Sportartikel > Ballpumpen +Sportartikel > Athletik > Allgemeine Sportartikel > Ballpumpenzubehör +Sportartikel > Athletik > Allgemeine Sportartikel > Ballpumpenzubehör > Ballpumpenaufsätze +Sportartikel > Athletik > Allgemeine Sportartikel > Balltaschen & -wagen +Sportartikel > Athletik > Allgemeine Sportartikel > Chalk +Sportartikel > Athletik > Allgemeine Sportartikel > Gymnastikmatten +Sportartikel > Athletik > Allgemeine Sportartikel > Leitern & Hürden für Geschwindigkeits- & Koordinationstraining +Sportartikel > Athletik > Allgemeine Sportartikel > Markierungshütchen +Sportartikel > Athletik > Allgemeine Sportartikel > Masken für Höhentraining +Sportartikel > Athletik > Allgemeine Sportartikel > Megaphone für Sportveranstaltungen +Sportartikel > Athletik > Allgemeine Sportartikel > Sport-Mundschutz +Sportartikel > Athletik > Allgemeine Sportartikel > Stadionsitze und Polster +Sportartikel > Athletik > Allgemeine Sportartikel > Suspensorien für Athleten +Sportartikel > Athletik > Allgemeine Sportartikel > Übungsnetze +Sportartikel > Athletik > American Football +Sportartikel > Athletik > American Football > American Football-Handschuhe +Sportartikel > Athletik > American Football > American Football-Schutzbekleidung +Sportartikel > Athletik > American Football > American Football-Schutzbekleidung > American Football-Girdle Shorts +Sportartikel > Athletik > American Football > American Football-Schutzbekleidung > American Football-Helme +Sportartikel > Athletik > American Football > American Football-Schutzbekleidung > American Football-Helmzubehör +Sportartikel > Athletik > American Football > American Football-Schutzbekleidung > American Football-Helmzubehör > American Football-Facemasks +Sportartikel > Athletik > American Football > American Football-Schutzbekleidung > American Football-Helmzubehör > American Football-Helmpolster +Sportartikel > Athletik > American Football > American Football-Schutzbekleidung > American Football-Helmzubehör > American Football-Helmvisiere +Sportartikel > Athletik > American Football > American Football-Schutzbekleidung > American Football-Helmzubehör > American Football-Kinnriemen +Sportartikel > Athletik > American Football > American Football-Schutzbekleidung > American Football-Nackenschutz +Sportartikel > Athletik > American Football > American Football-Schutzbekleidung > American Football-Schulterpads +Sportartikel > Athletik > American Football > American Football-Schutzbekleidung > Schutz-Shirts und -Hemden +Sportartikel > Athletik > American Football > American Football-Torstangen +Sportartikel > Athletik > American Football > American Football-Trainingsausrüstung +Sportartikel > Athletik > American Football > American Football-Trainingsausrüstung > Dummys & Schlitten +Sportartikel > Athletik > American Football > Football-Bälle +Sportartikel > Athletik > American Football > Kicking Tees & Halter für Fußbälle +Sportartikel > Athletik > Baseball & Softball +Sportartikel > Athletik > Baseball & Softball > Baseball- & Softball-Bases +Sportartikel > Athletik > Baseball & Softball > Baseball- & Softball-Schlaghandschuhe +Sportartikel > Athletik > Baseball & Softball > Baseball- & Softball-Schutzkleidung +Sportartikel > Athletik > Baseball & Softball > Baseball- & Softball-Schutzkleidung > Ausrüstung für Fänger +Sportartikel > Athletik > Baseball & Softball > Baseball- & Softball-Schutzkleidung > Baseball- & Softball-Brustpanzer +Sportartikel > Athletik > Baseball & Softball > Baseball- & Softball-Schutzkleidung > Baseball- & Softball-Knie- & Schienbeinschoner +Sportartikel > Athletik > Baseball & Softball > Baseball- & Softball-Schutzkleidung > Baseball- & Softballhelme +Sportartikel > Athletik > Baseball & Softball > Baseball- & Softball-Schutzkleidung > Schutzhelme für Fänger +Sportartikel > Athletik > Baseball & Softball > Baseball- & Softball-Wurfhügel +Sportartikel > Athletik > Baseball & Softball > Baseball- & Softball-Wurfmale +Sportartikel > Athletik > Baseball & Softball > Baseball- & Softballhandschuhe +Sportartikel > Athletik > Baseball & Softball > Baseball-Bälle +Sportartikel > Athletik > Baseball & Softball > Baseballschläger +Sportartikel > Athletik > Baseball & Softball > Softball-Bälle +Sportartikel > Athletik > Baseball & Softball > Softballschläger +Sportartikel > Athletik > Baseball & Softball > Wurfmaschinen +Sportartikel > Athletik > Basketball +Sportartikel > Athletik > Basketball > Basketball-Trainingshilfen +Sportartikel > Athletik > Basketball > Basketballkorbteile & -zubehör +Sportartikel > Athletik > Basketball > Basketballkorbteile & -zubehör > Basketball-Korbbretter +Sportartikel > Athletik > Basketball > Basketballkorbteile & -zubehör > Basketballnetze +Sportartikel > Athletik > Basketball > Basketballkorbteile & -zubehör > Basketballringe +Sportartikel > Athletik > Basketball > Basketballkorbteile & -zubehör > Basketballständer +Sportartikel > Athletik > Basketball > Basketballkorbteile & -zubehör > Schutzverkleidung für Basketballkörbe +Sportartikel > Athletik > Basketball > Basketballkörbe +Sportartikel > Athletik > Basketball > Basketbälle +Sportartikel > Athletik > Boxen & Kampfsport +Sportartikel > Athletik > Boxen & Kampfsport > Boxringe +Sportartikel > Athletik > Boxen & Kampfsport > Boxringelemente +Sportartikel > Athletik > Boxen & Kampfsport > Boxschutzbekleidung +Sportartikel > Athletik > Boxen & Kampfsport > Boxschutzbekleidung > Armschutz +Sportartikel > Athletik > Boxen & Kampfsport > Boxschutzbekleidung > Boxhandschuhe +Sportartikel > Athletik > Boxen & Kampfsport > Boxschutzbekleidung > Handbandagen +Sportartikel > Athletik > Boxen & Kampfsport > Boxschutzbekleidung > Kopfschutz +Sportartikel > Athletik > Boxen & Kampfsport > Boxschutzbekleidung > Körperschutz +Sportartikel > Athletik > Boxen & Kampfsport > Boxschutzbekleidung > Schienbeinschoner +Sportartikel > Athletik > Boxen & Kampfsport > Kampfsportgürtel +Sportartikel > Athletik > Boxen & Kampfsport > Kampfsportwaffen +Sportartikel > Athletik > Boxen & Kampfsport > Trainingsausrüstung für Boxen & Kampfsport +Sportartikel > Athletik > Boxen & Kampfsport > Trainingsausrüstung für Boxen & Kampfsport > Box- & Trainingssackzubehör +Sportartikel > Athletik > Boxen & Kampfsport > Trainingsausrüstung für Boxen & Kampfsport > Box- & Trainingssäcke +Sportartikel > Athletik > Boxen & Kampfsport > Trainingsausrüstung für Boxen & Kampfsport > Boxpratzen +Sportartikel > Athletik > Boxen & Kampfsport > Trainingsausrüstung für Boxen & Kampfsport > Schlagpolster +Sportartikel > Athletik > Boxen & Kampfsport > Trainingsausrüstung für Boxen & Kampfsport > Training-Dummys +Sportartikel > Athletik > Broomball-Ausrüstung +Sportartikel > Athletik > Cheerleading +Sportartikel > Athletik > Cheerleading > Cheerleader-Pompons +Sportartikel > Athletik > Cricket +Sportartikel > Athletik > Cricket > Cricket-Aussrüstungssets +Sportartikel > Athletik > Cricket > Cricket-Schutzkleidung +Sportartikel > Athletik > Cricket > Cricket-Schutzkleidung > Cricket-Beinschutz +Sportartikel > Athletik > Cricket > Cricket-Schutzkleidung > Crickethandschuhe +Sportartikel > Athletik > Cricket > Cricket-Schutzkleidung > Crickethelme +Sportartikel > Athletik > Cricket > Cricketbälle +Sportartikel > Athletik > Cricket > Cricketschläger +Sportartikel > Athletik > Cricket > Cricketschläger-Zubehör +Sportartikel > Athletik > Cricket > Cricketschläger-Zubehör > Cricketschläger-Griffe +Sportartikel > Athletik > Cricket > Cricketstäbe +Sportartikel > Athletik > Eiskunstlauf & Hockey +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockey-Schutzausrüstung +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockey-Schutzausrüstung > Ausrüstungssets für Torwarte +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockey-Schutzausrüstung > Eishockeyhosenträger und -gürtel +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockey-Schutzausrüstung > Hockey-Ellenbogenschutz +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockey-Schutzausrüstung > Hockey-Schienbeinschoner & -Beinschutz +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockey-Schutzausrüstung > Hockey-Schulterpolster & -Brustpolster +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockey-Schutzausrüstung > Hockeyhandschuhe +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockey-Schutzausrüstung > Hockeyhelme +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockey-Schutzausrüstung > Hockeyhosen +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockeybälle & Pucks +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockeyschlitten +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockeyschläger +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockeyschlägerpflege +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockeyschlägerteile +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockeyschlägerteile > Hockey-Schlägerschäfte +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockeyschlägerteile > Hockeyschlägerkellen +Sportartikel > Athletik > Eiskunstlauf & Hockey > Hockeytore +Sportartikel > Athletik > Eiskunstlauf & Hockey > Schlittschuhe +Sportartikel > Athletik > Eiskunstlauf & Hockey > Schlittschuhzubehör +Sportartikel > Athletik > Eiskunstlauf & Hockey > Schlittschuhzubehör > Eiskunstlaufstiefel +Sportartikel > Athletik > Eiskunstlauf & Hockey > Schlittschuhzubehör > Kufenschoner +Sportartikel > Athletik > Eiskunstlauf & Hockey > Schlittschuhzubehör > Schlittschuhkufen +Sportartikel > Athletik > Eiskunstlauf & Hockey > Schlittschuhzubehör > Schlittschuhschleifer +Sportartikel > Athletik > Eiskunstlauf & Hockey > Schlittschuhzubehör > Schnürhilfen +Sportartikel > Athletik > Fechten +Sportartikel > Athletik > Fechten > Fechtschutzbekleidung +Sportartikel > Athletik > Fechten > Fechtschutzbekleidung > Fechthandschuhe +Sportartikel > Athletik > Fechten > Fechtschutzbekleidung > Fechtjacken & -E-Westen +Sportartikel > Athletik > Fechten > Fechtschutzbekleidung > Fechtmasken +Sportartikel > Athletik > Fechten > Sportfechtwaffen +Sportartikel > Athletik > Feldhockey & Lacrosse +Sportartikel > Athletik > Feldhockey & Lacrosse > Feldhockey- & Lacrosse-Schutzkleidung +Sportartikel > Athletik > Feldhockey & Lacrosse > Feldhockey- & Lacrosse-Schutzkleidung > Feldhockey- & Lacrosse-Handschuhe +Sportartikel > Athletik > Feldhockey & Lacrosse > Feldhockey- & Lacrosse-Schutzkleidung > Feldhockey- & Lacrosse-Pads +Sportartikel > Athletik > Feldhockey & Lacrosse > Feldhockey- & Lacrosse-Schutzkleidung > Hockey- & Lacrosse-Helme +Sportartikel > Athletik > Feldhockey & Lacrosse > Feldhockey- & Lacrosse-Schutzkleidung > Masken & Schutzbrillen +Sportartikel > Athletik > Feldhockey & Lacrosse > Feldhockeybälle +Sportartikel > Athletik > Feldhockey & Lacrosse > Feldhockeyschläger +Sportartikel > Athletik > Feldhockey & Lacrosse > Feldhockeytore +Sportartikel > Athletik > Feldhockey & Lacrosse > Lacrosse-Ausrüstungen +Sportartikel > Athletik > Feldhockey & Lacrosse > Lacrosse-Bälle +Sportartikel > Athletik > Feldhockey & Lacrosse > Lacrosse-Schläger +Sportartikel > Athletik > Feldhockey & Lacrosse > Lacrosse-Schlägerteile +Sportartikel > Athletik > Feldhockey & Lacrosse > Lacrosse-Schlägerteile > Lacrosse-Schlägerköpfe +Sportartikel > Athletik > Feldhockey & Lacrosse > Lacrosse-Schlägerteile > Lacrosse-Schlägernetze & Strings +Sportartikel > Athletik > Feldhockey & Lacrosse > Lacrosse-Schlägerteile > Lacrosse-Schlägerschäfte +Sportartikel > Athletik > Feldhockey & Lacrosse > Lacrosse-Tore +Sportartikel > Athletik > Feldhockey & Lacrosse > Lacrosse-Trainingszubehör +Sportartikel > Athletik > Fußball +Sportartikel > Athletik > Fußball > Eckfahnen +Sportartikel > Athletik > Fußball > Fußball-Schutzkleidung +Sportartikel > Athletik > Fußball > Fußball-Schutzkleidung > Fußballschienbeinschützer +Sportartikel > Athletik > Fußball > Fußballtore & -netze +Sportartikel > Athletik > Fußball > Fußbälle +Sportartikel > Athletik > Fußball > Torwarthandschuhe +Sportartikel > Athletik > Fußball > Zubehör für Fußballtore +Sportartikel > Athletik > Handball +Sportartikel > Athletik > Handball > Handbälle +Sportartikel > Athletik > Leichtathletik +Sportartikel > Athletik > Leichtathletik > Diskusscheiben +Sportartikel > Athletik > Leichtathletik > Hochsprunglatten +Sportartikel > Athletik > Leichtathletik > Hochsprungmatten +Sportartikel > Athletik > Leichtathletik > Hürden +Sportartikel > Athletik > Leichtathletik > Kugelstoß-Kugeln +Sportartikel > Athletik > Leichtathletik > Speere +Sportartikel > Athletik > Leichtathletik > Stabhochsprungmatten +Sportartikel > Athletik > Leichtathletik > Stabhochsprungstäbe +Sportartikel > Athletik > Leichtathletik > Staffelstäbe +Sportartikel > Athletik > Leichtathletik > Startblöcke +Sportartikel > Athletik > Leichtathletik > Startpistolen +Sportartikel > Athletik > Leichtathletik > Wurfhämmer +Sportartikel > Athletik > Racquetball & Squash +Sportartikel > Athletik > Racquetball & Squash > Racquetball- & Squash-Bälle +Sportartikel > Athletik > Racquetball & Squash > Racquetball-Schläger +Sportartikel > Athletik > Racquetball & Squash > Squash- & Racquetball-Handschuhe +Sportartikel > Athletik > Racquetball & Squash > Squash- & Racquetball-Schutzbrillen +Sportartikel > Athletik > Racquetball & Squash > Squashschläger +Sportartikel > Athletik > Ringen +Sportartikel > Athletik > Ringen > Schutzbekleidung für Ringer +Sportartikel > Athletik > Ringen > Schutzbekleidung für Ringer > Ringer-Kniepolster +Sportartikel > Athletik > Ringen > Schutzbekleidung für Ringer > Ringer-Kopfschutz +Sportartikel > Athletik > Rounders +Sportartikel > Athletik > Rounders > Rounders-Handschuhe +Sportartikel > Athletik > Rounders > Rounders-Schläger +Sportartikel > Athletik > Rugby +Sportartikel > Athletik > Rugby > Rugby-Bälle +Sportartikel > Athletik > Rugby > Rugby-Handschuhe +Sportartikel > Athletik > Rugby > Rugby-Schutzkleidung +Sportartikel > Athletik > Rugby > Rugby-Schutzkleidung > Rugby-Helme +Sportartikel > Athletik > Rugby > Rugby-Tore +Sportartikel > Athletik > Rugby > Rugby-Trainingsausrüstung +Sportartikel > Athletik > Tanzen +Sportartikel > Athletik > Tanzen > Ballettstangen +Sportartikel > Athletik > Tennis +Sportartikel > Athletik > Tennis > Tennisballdosen +Sportartikel > Athletik > Tennis > Tennisballkörbe & -wagen +Sportartikel > Athletik > Tennis > Tennisballwurfmaschinen +Sportartikel > Athletik > Tennis > Tennisbälle +Sportartikel > Athletik > Tennis > Tennisnetze +Sportartikel > Athletik > Tennis > Tennisschläger +Sportartikel > Athletik > Tennis > Tennisschlägerzubehör +Sportartikel > Athletik > Tennis > Tennisschlägerzubehör > Griffbänder +Sportartikel > Athletik > Tennis > Tennisschlägerzubehör > Schlägerdämpfer +Sportartikel > Athletik > Tennis > Tennisschlägerzubehör > Tennissaiten +Sportartikel > Athletik > Tennis > Tennisschlägerzubehör > Tennistaschen +Sportartikel > Athletik > Tennis > Tennisschlägerzubehör > Ösenbänder für Tennisschläger +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung > Anzeigetafeln +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung > Begrenzungsmarkierungen für Spielfelder +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung > Handzähler +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung > Kapitänsbinden +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung > Linienrichterfahnen +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung > Pitch Counter +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung > Schiedsrichter- & Trainerpfeifen +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung > Schiedsrichter-Münzen +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung > Schiedsrichtermappen +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung > Schiedsrichterstände & -stühle +Sportartikel > Athletik > Trainer- & Schiedsrichterausrüstung > Strafkarten & -flaggen +Sportartikel > Athletik > Turnen +Sportartikel > Athletik > Turnen > Pauschenpferde +Sportartikel > Athletik > Turnen > Schutzausrüstung +Sportartikel > Athletik > Turnen > Schutzausrüstung > Turner-Handbandagen +Sportartikel > Athletik > Turnen > Sprungpferde +Sportartikel > Athletik > Turnen > Turnringe +Sportartikel > Athletik > Turnen > Turnsprungbretter +Sportartikel > Athletik > Turnen > Turnstangen & Schwebebalken +Sportartikel > Athletik > Volleyball +Sportartikel > Athletik > Volleyball > Volleyball-Schutzkleidung +Sportartikel > Athletik > Volleyball > Volleyball-Schutzkleidung > Volleyball-Knieschoner +Sportartikel > Athletik > Volleyball > Volleyball-Trainingshilfen +Sportartikel > Athletik > Volleyball > Volleyballnetze +Sportartikel > Athletik > Volleyball > Volleybälle +Sportartikel > Athletik > Wallyball-Ausrüstung +Sportartikel > Athletik > Wasserball +Sportartikel > Athletik > Wasserball > Wasserballkappen +Sportartikel > Athletik > Wasserball > Wasserballtore +Sportartikel > Athletik > Wasserball > Wasserbälle +Sportartikel > Fitness +Sportartikel > Fitness > Balancetrainer +Sportartikel > Fitness > Bauchtrainer +Sportartikel > Fitness > Cardio +Sportartikel > Fitness > Cardio > Cardiogeräte +Sportartikel > Fitness > Cardio > Cardiogeräte > Crosstrainer +Sportartikel > Fitness > Cardio > Cardiogeräte > Heimtrainer +Sportartikel > Fitness > Cardio > Cardiogeräte > Laufbänder +Sportartikel > Fitness > Cardio > Cardiogeräte > Rudergeräte +Sportartikel > Fitness > Cardio > Cardiogeräte > Stepper +Sportartikel > Fitness > Cardio > Springseile +Sportartikel > Fitness > Cardio > Zubehör für Cardiogeräte +Sportartikel > Fitness > Cardio > Zubehör für Cardiogeräte > Crosstrainer-Zubehör +Sportartikel > Fitness > Cardio > Zubehör für Cardiogeräte > Heimtrainer-Zubehör +Sportartikel > Fitness > Cardio > Zubehör für Cardiogeräte > Laufbandzubehör +Sportartikel > Fitness > Cardio > Zubehör für Cardiogeräte > Rudergerät-Zubehör +Sportartikel > Fitness > Cardio > Zubehör für Cardiogeräte > Stepper-Zubehör +Sportartikel > Fitness > Fitnessbänder +Sportartikel > Fitness > Fitnessgeräte-Sets +Sportartikel > Fitness > Gewichtheben +Sportartikel > Fitness > Gewichtheben > Gewichthebergürtel +Sportartikel > Fitness > Gewichtheben > Gewichtheberhandschuhe & Handgelenkschoner +Sportartikel > Fitness > Gewichtheben > Hanteln +Sportartikel > Fitness > Gewichtheben > Hantelzubehör +Sportartikel > Fitness > Gewichtheben > Hantelzubehör > Hantelstangen +Sportartikel > Fitness > Gewichtheben > Hantelzubehör > Hantelstangen-Feststellringe +Sportartikel > Fitness > Gewichtheben > Hantelzubehör > Hantelständer +Sportartikel > Fitness > Gewichtheben > Krafttrainingsgeräte +Sportartikel > Fitness > Gewichtheben > Zubehör für Krafttrainingsgeräte +Sportartikel > Fitness > Gewichtswesten & -manschetten +Sportartikel > Fitness > Gymnastikbälle +Sportartikel > Fitness > Handtrainer +Sportartikel > Fitness > Inversionstische +Sportartikel > Fitness > Keilmatten +Sportartikel > Fitness > Klimmzugstangen +Sportartikel > Fitness > Matten für Heimtrainer +Sportartikel > Fitness > Medizinbälle +Sportartikel > Fitness > Power Tower +Sportartikel > Fitness > Reaktionsbälle +Sportartikel > Fitness > Schaumroller +Sportartikel > Fitness > Schaumroller-Zubehör +Sportartikel > Fitness > Schaumroller-Zubehör > Aufbewahrungstaschen für Schaumroller +Sportartikel > Fitness > Sicherheitslichter & -reflektoren für Sport +Sportartikel > Fitness > Steppbretter +Sportartikel > Fitness > Stoppuhren +Sportartikel > Fitness > Suspension Trainer +Sportartikel > Fitness > Trainings- & Bremsschirme +Sportartikel > Fitness > Trainingsbänke +Sportartikel > Fitness > Vibrationstrainer +Sportartikel > Fitness > Yoga & Pilates +Sportartikel > Fitness > Yoga & Pilates > Beutel für Yogamatten +Sportartikel > Fitness > Yoga & Pilates > Pilates-Geräte +Sportartikel > Fitness > Yoga & Pilates > Yoga- & Pilatesblöcke +Sportartikel > Fitness > Yoga & Pilates > Yoga- & Pilatesmatten +Sportartikel > Fitness > Yoga & Pilates > Yoga- & Pilatestücher +Sportartikel > Indoor - Aktivitäten +Sportartikel > Indoor - Aktivitäten > Air-Hockey +Sportartikel > Indoor - Aktivitäten > Air-Hockey > Air-Hockey-Ausrüstung +Sportartikel > Indoor - Aktivitäten > Air-Hockey > Air-Hockey-Tische +Sportartikel > Indoor - Aktivitäten > Air-Hockey > Air-Hockey-Tischzubehör +Sportartikel > Indoor - Aktivitäten > Billard +Sportartikel > Indoor - Aktivitäten > Billard > Billarddreiecke +Sportartikel > Indoor - Aktivitäten > Billard > Billardhandschuhe +Sportartikel > Indoor - Aktivitäten > Billard > Billardkugeln +Sportartikel > Indoor - Aktivitäten > Billard > Billardqueues & Queuebrücken +Sportartikel > Indoor - Aktivitäten > Billard > Billardtisch-Beleuchtung +Sportartikel > Indoor - Aktivitäten > Billard > Billardtisch-Teile & Zubehör +Sportartikel > Indoor - Aktivitäten > Billard > Billardtisch-Teile & Zubehör > Ballfangtaschen +Sportartikel > Indoor - Aktivitäten > Billard > Billardtisch-Teile & Zubehör > Billardtisch-Bürsten +Sportartikel > Indoor - Aktivitäten > Billard > Billardtisch-Teile & Zubehör > Billardtücher +Sportartikel > Indoor - Aktivitäten > Billard > Billardtisch-Teile & Zubehör > Billiardtischabdeckungen +Sportartikel > Indoor - Aktivitäten > Billard > Billardtische +Sportartikel > Indoor - Aktivitäten > Billard > Queuezubehör +Sportartikel > Indoor - Aktivitäten > Billard > Queuezubehör > Billiardkoffer +Sportartikel > Indoor - Aktivitäten > Billard > Queuezubehör > Billiardkreide +Sportartikel > Indoor - Aktivitäten > Billard > Queuezubehör > Queueständer +Sportartikel > Indoor - Aktivitäten > Bowling +Sportartikel > Indoor - Aktivitäten > Bowling > Bowling-Handgelenkstützen +Sportartikel > Indoor - Aktivitäten > Bowling > Bowlinghandschuhe +Sportartikel > Indoor - Aktivitäten > Bowling > Bowlingkegel +Sportartikel > Indoor - Aktivitäten > Bowling > Bowlingkugeln +Sportartikel > Indoor - Aktivitäten > Bowling > Bowlingtaschen +Sportartikel > Indoor - Aktivitäten > Dartspiele +Sportartikel > Indoor - Aktivitäten > Dartspiele > Dart-Rückwände +Sportartikel > Indoor - Aktivitäten > Dartspiele > Dartpfeile +Sportartikel > Indoor - Aktivitäten > Dartspiele > Dartscheiben +Sportartikel > Indoor - Aktivitäten > Dartspiele > Dartteile +Sportartikel > Indoor - Aktivitäten > Dartspiele > Dartteile > Dartflights +Sportartikel > Indoor - Aktivitäten > Dartspiele > Dartteile > Dartschäfte +Sportartikel > Indoor - Aktivitäten > Dartspiele > Dartteile > Dartspitzen +Sportartikel > Indoor - Aktivitäten > Jakkolo +Sportartikel > Indoor - Aktivitäten > Jakkolo > Jakkolo-Pulver +Sportartikel > Indoor - Aktivitäten > Jakkolo > Jakkolo-Spielsteine +Sportartikel > Indoor - Aktivitäten > Jakkolo > Shuffleboard-Tische +Sportartikel > Indoor - Aktivitäten > Multispieltische +Sportartikel > Indoor - Aktivitäten > Tischfußball +Sportartikel > Indoor - Aktivitäten > Tischfußball > Tischfußball-Bälle +Sportartikel > Indoor - Aktivitäten > Tischfußball > Tischfußball-Tische +Sportartikel > Indoor - Aktivitäten > Tischfußball > Zubehör für Tischfußballtische +Sportartikel > Indoor - Aktivitäten > Tischtennis +Sportartikel > Indoor - Aktivitäten > Tischtennis > Tischtennis-Roboter +Sportartikel > Indoor - Aktivitäten > Tischtennis > Tischtennisbälle +Sportartikel > Indoor - Aktivitäten > Tischtennis > Tischtennisnetze und -pfosten +Sportartikel > Indoor - Aktivitäten > Tischtennis > Tischtennisplatten +Sportartikel > Indoor - Aktivitäten > Tischtennis > Tischtennisschläger & -sets +Sportartikel > Indoor - Aktivitäten > Tischtennis > Zubehör für Tischtennis-Roboter +Sportartikel > Indoor - Aktivitäten > Tischtennis > Zubehör für Tischtennisschläger Sportartikel > Outdoor-Aktivitäten Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen -Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelblei -Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelhaken +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelgeräte +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelgeräte > Angelblei +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelgeräte > Angelhaken +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelgeräte > Karabiner & Wirbel +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelgeräte > Kunstköder +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelgeräte > Schwimmer Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelkästen & Angeltaschen Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelleinen & -wirbel Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelrollen +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelrollen-Zubehör +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelrollen-Zubehör > Ersatzspulen +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelrollen-Zubehör > Schmierstoffe +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelrollen-Zubehör > Taschen & Behälter Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelruten +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelruten-Zubehör +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelruten-Zubehör > Angelrutenhalter und -ständer +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Angelruten-Zubehör > Taschen & Behälter für Angelruten +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Bissanzeiger Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Fallen für den Angelsport Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Fischernetze Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Gaffs Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Harpunen für den Angelsport -Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Karabiner und Drehringe für den Angelsport -Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Kunstköder -Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Kunstköder > Kunstfliegen Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Köderbefestigung Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Köderbefestigung > Angelschnur Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Köderbefestigung > Pop-Up-Kugeln Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Köderbehälter Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Lebendköder -Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Schwimmer Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Wathosen für Angler & Jäger -Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Werkzeug zum Entfernen des Angelhakens -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Camping-Töpfe & -Geschirr -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Campinghilfsmittel -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Campinghilfsmittel > Messer & Klingen -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Campinghilfsmittel > Messer & Klingen > Jagdmesser -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Campinghilfsmittel > Multifunktionswerkzeuge & -messer -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Campingleuchten & -laternen -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Campingmöbel -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Campingmöbel > Feldbetten -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Campingmöbel > Luftmatratzen -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Campingmöbel > Luftmatratzenzubehör -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Gefriergetrocknete Lebensmittel -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Isomatten -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Jugendherbergs-Schlafsack -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Kompressionssäcke -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Mobile Toiletten -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Moskitonetze -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Navigationskompasse -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Notfalldecken -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Schlafsäcke -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Steigeisen -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Taschenwärmer -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Tragbare Wasserfilter & Wasserreiniger -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Trekkingstöcke -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Urinellas & Tragbare Urinale -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Zelte -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Zeltzubehör -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Zeltzubehör > Vorzelte -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Zeltzubehör > Zeltstangen & -Heringe -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Zeltzubehör > Zeltunterlagen -Sportartikel > Outdoor-Aktivitäten > Camping & Wandern & Rucksacktouren > Zubehör für Trekkingstöcke -Sportartikel > Outdoor-Aktivitäten > Discgolf -Sportartikel > Outdoor-Aktivitäten > Discgolf > Discgolf-Fangkörbe -Sportartikel > Outdoor-Aktivitäten > Discgolf > Discgolf-Scheiben -Sportartikel > Outdoor-Aktivitäten > Discgolf > Discgolf-Taschen -Sportartikel > Outdoor-Aktivitäten > Geocaching -Sportartikel > Outdoor-Aktivitäten > Geocaching > Cachebehälter -Sportartikel > Outdoor-Aktivitäten > Geocaching > Geocaching-Logbücher -Sportartikel > Outdoor-Aktivitäten > Geocaching > Geocaching-Trackables +Sportartikel > Outdoor-Aktivitäten > Angeln & Fischen > Werkzeuge zum Entfernen des Angelhakens +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Ablagen für Wasserfahrzeuge +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Ablagen für Wasserfahrzeuge > Halterungen für Boards +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Ablagen für Wasserfahrzeuge > Halterungen für Boote +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Boots- & Wassersportkleidung +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Boots- & Wassersportkleidung > Neoprenanzugteile +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Boots- & Wassersportkleidung > Neoprenanzugteile > Kappen, Handschuhe & Stiefel für Neoprenanzüge +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Boots- & Wassersportkleidung > Neoprenanzugteile > Neopren-Oberteile +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Boots- & Wassersportkleidung > Neoprenanzugteile > Neopren-Unterteile +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Boots- & Wassersportkleidung > Neoprenanzüge +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Boots- & Wassersportkleidung > Rashguards & Schwimmshirts +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Boots- & Wassersportkleidung > Rettungswesten +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Boots- & Wassersportkleidung > Schwimmwestenzubehör +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Boots- & Wassersportkleidung > Trockenanzüge +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Boots- & Wassersportkleidung > Wassersporthelme +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Bootssport +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Bootssport > Handschuhe für den Bootssport +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Bootssport > Kajaks +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Bootssport > Kajakzubehör +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Bootssport > Kanus +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Bootssport > Kanuzubehör +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Bootssport > Paddel & Ruder +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Bootssport > Paddelriemen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Bootssport > Rafts +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Bootssport > Ruderboote +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Bootssport > Tretboote +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Kitesurfen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Kitesurfen > Kiteboard-Taschen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Kitesurfen > Kiteboards +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Kitesurfen > Kiteboardteile +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Kitesurfen > Kitesurfing-Kites +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Kitesurfen > Trapeze für Kite- & Windsurfen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Badekappen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Gegenstromanlagen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Handpaddel +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Nasenklammern +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Pull Buoys +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Schwimmbretter +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Schwimmbrillen & -masken +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Schwimmflossen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Schwimmflossen > Monoflossen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Schwimmflossen > Trainingsflossen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Schwimmflügel +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Schwimmgewichte +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Schwimmgürtel +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Schwimmhandschuhe +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Schwimmen > Zubehör für Schwimmbrillen & -masken +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Handschuhe für seilgebundene Wassersportarten +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Kneeboarding +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Kneeboarding > Kneeboards +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Tubes & Towables +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Wakeboarding +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Wakeboarding > Kiteboard- & Wakeboard-Bindungen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Wakeboarding > Wakeboard-Ersatzteile +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Wakeboarding > Wakeboards +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Wasserski +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Wasserski > Hydrofoils +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Wasserski > Wasserski-Bindungen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Wasserski > Wasserskier +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Wasserski > Wasserskikoffer & -taschen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Seilgebundene Wassersportarten > Zugseile für Wassersport +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Surfen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Surfen > Bodyboards +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Surfen > Fußleinen für Surfbretter +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Surfen > Handschuhe für Surfer +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Surfen > Paddelboards +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Surfen > Skimboards +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Surfen > Surfboard-Taschen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Surfen > Surfboard-Wachs +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Surfen > Surfbretter +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Surfen > Surfbrettfinnen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Surfen > Surfpads +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Tauchen & Schnorcheln +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Tauchen & Schnorcheln > Atemregler +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Tauchen & Schnorcheln > Bleigürtel +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Tauchen & Schnorcheln > Schnorchel +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Tauchen & Schnorcheln > Tarierwesten +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Tauchen & Schnorcheln > Tauch- & Schnorchelausrüstung +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Tauchen & Schnorcheln > Tauch- & Schnorchelflossen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Tauchen & Schnorcheln > Tauch- & Schnorchelmasken +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Tauchen & Schnorcheln > Tauchcomputer +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Tauchen & Schnorcheln > Tauchermesser & -scheren +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Windsurfen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Windsurfen > Windsurfboard-Ausrüstung +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Windsurfen > Windsurfboard-Ausrüstung > Windsurf-Finnen +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Windsurfen > Windsurfboard-Ausrüstung > Windsurfmasten +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Windsurfen > Windsurfbretter +Sportartikel > Outdoor-Aktivitäten > Boot- & Wassersport > Windsurfen > Windsurfsegel +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Camping-Töpfe & -Geschirr +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Campinghilfsmittel +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Campinghilfsmittel > Jagdmesser +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Campinghilfsmittel > Multifunktionswerkzeuge & -messer +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Campingleuchten & -laternen +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Campingmöbel +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Campingmöbel > Feldbetten +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Campingmöbel > Luftbetten +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Campingmöbel > Zubehör für Luftmatratzen & Schlafunterlagen +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Innenschlafsack +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Isomatten +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Kompressionssäcke +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Mobile Toiletten & Duschen +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Mobile Toiletten & Duschen > Mobile Duschen & Sichtschutz +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Mobile Toiletten & Duschen > Mobile Toiletten & Urinale +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Moskitonetze & Fliegengitter +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Navigationskompasse +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Schlafsäcke +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Taschenwärmer +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Tragbare Wasserfilter & Wasserreiniger +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Trekkingstöcke +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Windschutz +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Zelte +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Zeltzubehör +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Zeltzubehör > Innenzelte +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Zeltzubehör > Vorzelte +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Zeltzubehör > Zeltstangen & -Heringe +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Zeltzubehör > Zeltunterlagen +Sportartikel > Outdoor-Aktivitäten > Camping & Wandern > Zubehör für Trekkingstöcke +Sportartikel > Outdoor-Aktivitäten > Drachenfliegen & Fallschirmspringen +Sportartikel > Outdoor-Aktivitäten > Drachenfliegen & Fallschirmspringen > Fallschirme +Sportartikel > Outdoor-Aktivitäten > Drachenfliegen & Fallschirmspringen > Fluganzüge +Sportartikel > Outdoor-Aktivitäten > Drachenfliegen & Fallschirmspringen > Hängegleiter Sportartikel > Outdoor-Aktivitäten > Golf -Sportartikel > Outdoor-Aktivitäten > Golf > Golf-Sets Sportartikel > Outdoor-Aktivitäten > Golf > Golf-Tees Sportartikel > Outdoor-Aktivitäten > Golf > Golf-Trainingshilfen +Sportartikel > Outdoor-Aktivitäten > Golf > Golf-Zubehörsets Sportartikel > Outdoor-Aktivitäten > Golf > Golfballmarkierungen Sportartikel > Outdoor-Aktivitäten > Golf > Golfbälle Sportartikel > Outdoor-Aktivitäten > Golf > Golffahnen @@ -5321,97 +4777,127 @@ Sportartikel > Outdoor-Aktivitäten > Golf > Golfschläger Sportartikel > Outdoor-Aktivitäten > Golf > Golfschläger-Teile & Zubehör Sportartikel > Outdoor-Aktivitäten > Golf > Golfschläger-Teile & Zubehör > Golfschläger-Schutzhüllen Sportartikel > Outdoor-Aktivitäten > Golf > Golfschläger-Teile & Zubehör > Golfschlägergriffe +Sportartikel > Outdoor-Aktivitäten > Golf > Golfschläger-Teile & Zubehör > Golfschlägerschäfte Sportartikel > Outdoor-Aktivitäten > Golf > Golftaschen Sportartikel > Outdoor-Aktivitäten > Golf > Golftaschenzubehör Sportartikel > Outdoor-Aktivitäten > Golf > Golftaschenzubehör > Golftaschenhüllen Sportartikel > Outdoor-Aktivitäten > Golf > Golftaschenzubehör > Golftaschentrolleys Sportartikel > Outdoor-Aktivitäten > Golf > Pitchgabeln -Sportartikel > Outdoor-Aktivitäten > Jagen -Sportartikel > Outdoor-Aktivitäten > Jagen > Hochsitze -Sportartikel > Outdoor-Aktivitäten > Jagen > Jagdansitze & Tarnung -Sportartikel > Outdoor-Aktivitäten > Jagen > Jagdhund-Ausrüstung -Sportartikel > Outdoor-Aktivitäten > Jagen > Jagdköder -Sportartikel > Outdoor-Aktivitäten > Jagen > Jagdzelte -Sportartikel > Outdoor-Aktivitäten > Jagen > Lockinstrumente -Sportartikel > Outdoor-Aktivitäten > Jagen > Lockmittel für Wild -Sportartikel > Outdoor-Aktivitäten > Jagen > Tierfallen -Sportartikel > Outdoor-Aktivitäten > Jagen > Wildfutterautomaten +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Armschutz für Bogenschützen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Bogen- & Armbrustteile & -zubehör +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Bögen & Armbrüste +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Bögen & Armbrüste > Armbrüste +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Bögen & Armbrüste > Compoundbögen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Bögen & Armbrüste > Langbögen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Handschuhe & Auslöser +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Köcher +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Pfeile +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Pfeilelemente & Zubehör +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Pfeilelemente & Zubehör > Jagdspitzen & Feldspitzen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Pfeilelemente & Zubehör > Nocken +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Pfeilelemente & Zubehör > Steuerfedern +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Bogenschießen > Zielscheiben für Bogenschießen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen > Hochsitze +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen > Jagdansitze & Tarnung +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen > Jagdhund-Ausrüstung +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen > Jagdverstärker +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen > Lockmittel für Wild +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen > Lockmittel für Wild > Geruchstarnung & Lockdüfte +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen > Lockmittel für Wild > Jagdköder +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen > Lockmittel für Wild > Köder, Futter & Mineralien +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen > Lockmittel für Wild > Lockinstrumente +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen > Tierfallen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Jagen > Wildfutterautomaten +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball > Ersatzteile & Zubehör für Paintball-Gewehre +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball > Ersatzteile & Zubehör für Paintball-Gewehre > Paintball-Drop-Forwards +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball > Ersatzteile & Zubehör für Paintball-Gewehre > Paintball-Gewehrläufe +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball > Ersatzteile & Zubehör für Paintball-Gewehre > Paintball-Hopper +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball > Ersatzteile & Zubehör für Paintball-Gewehre > Paintball-Pressluftflaschen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball > Paintball-Battlepacks +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball > Paintball-Granaten +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball > Paintball-Granatwerfer +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball > Paintball-Markierer +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball > Paintballs +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball- & Airsoft-Schutzkleidung +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball- & Airsoft-Schutzkleidung > Paintball- & Airsoft-Handschuhe +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball- & Airsoft-Schutzkleidung > Paintball- & Airsoft-Schutzbrillen & Masken +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball- & Airsoft-Schutzkleidung > Paintball- & Airsoft-Schutzpolster +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Paintball- & Airsoft-Schutzkleidung > Paintball- & Airsoft-Westen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Softair +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Softair > Softairkugeln +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Softair > Softairwaffen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Softair > Softairwaffen-Teile & -Zubehör +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Paintball & Airsoft > Softair > Softairwaffen-Teile & -Zubehör > Batterien für Softairwaffen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Schießsportzubehör +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Schießsportzubehör > Stative & Zweibeinständer +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Schießsportzubehör > Stützen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Schießsportzubehör > Zielscheiben +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Schutzkleidung für Jagd- und Schießsport +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Schutzkleidung für Jagd- und Schießsport > Handschuhe für Jagd- und Schießsport +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Schutzkleidung für Jagd- und Schießsport > Jacken für Jagd- und Schießsport +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Tontaubenschießen +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Tontaubenschießen > Tontauben +Sportartikel > Outdoor-Aktivitäten > Jagen und Schießen > Tontaubenschießen > Tontauben-Wurfmaschinen Sportartikel > Outdoor-Aktivitäten > Kitebuggying +Sportartikel > Outdoor-Aktivitäten > Kitebuggying > Kitebuggy-Zubehör Sportartikel > Outdoor-Aktivitäten > Kitebuggying > Kitebuggys -Sportartikel > Outdoor-Aktivitäten > Kitebuggying-Zubehör Sportartikel > Outdoor-Aktivitäten > Klettern -Sportartikel > Outdoor-Aktivitäten > Klettern > Eisklettern -Sportartikel > Outdoor-Aktivitäten > Klettern > Eisklettern > Eiskletterausrüstung -Sportartikel > Outdoor-Aktivitäten > Klettern > Eisklettern > Eisschrauben -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Bouldermatten -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Chalk -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Chalkbags -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Expresssets -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Gurtbänder -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Karabiner -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Klettergurte -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Kletterhelme -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Kletterschutzausrüstung -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Kletterseile -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Kletterwandgriffe -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Seilsäcke -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Sicherungsgeräte -Sportartikel > Outdoor-Aktivitäten > Klettern > Felsklettern & Indoor-Klettern > Steigklemmen & Abseilausrüstung -Sportartikel > Outdoor-Aktivitäten > Laufsport -Sportartikel > Outdoor-Aktivitäten > Leichtathletik -Sportartikel > Outdoor-Aktivitäten > Leichtathletik > Diskusscheiben -Sportartikel > Outdoor-Aktivitäten > Leichtathletik > Hochsprunglatten -Sportartikel > Outdoor-Aktivitäten > Leichtathletik > Hochsprungmatten -Sportartikel > Outdoor-Aktivitäten > Leichtathletik > Hürden -Sportartikel > Outdoor-Aktivitäten > Leichtathletik > Kugelstoß-Kugeln -Sportartikel > Outdoor-Aktivitäten > Leichtathletik > Speere -Sportartikel > Outdoor-Aktivitäten > Leichtathletik > Stabhochsprungmatten -Sportartikel > Outdoor-Aktivitäten > Leichtathletik > Stabhochsprungstäbe -Sportartikel > Outdoor-Aktivitäten > Leichtathletik > Staffelstäbe -Sportartikel > Outdoor-Aktivitäten > Leichtathletik > Startpistolen -Sportartikel > Outdoor-Aktivitäten > Leichtathletik > Wurfhämmer -Sportartikel > Outdoor-Aktivitäten > Motorsport -Sportartikel > Outdoor-Aktivitäten > Motorsport > Rennsport +Sportartikel > Outdoor-Aktivitäten > Klettern > Bouldermatten +Sportartikel > Outdoor-Aktivitäten > Klettern > Chalkbags +Sportartikel > Outdoor-Aktivitäten > Klettern > Eiskletterausrüstung +Sportartikel > Outdoor-Aktivitäten > Klettern > Eisschrauben +Sportartikel > Outdoor-Aktivitäten > Klettern > Expresssets +Sportartikel > Outdoor-Aktivitäten > Klettern > Gurtbänder +Sportartikel > Outdoor-Aktivitäten > Klettern > Karabiner +Sportartikel > Outdoor-Aktivitäten > Klettern > Kletterbekleidung & -zubehör +Sportartikel > Outdoor-Aktivitäten > Klettern > Kletterbekleidung & -zubehör > Kletterhandschuhe +Sportartikel > Outdoor-Aktivitäten > Klettern > Kletterbekleidung & -zubehör > Kletterhelme +Sportartikel > Outdoor-Aktivitäten > Klettern > Kletterbekleidung & -zubehör > Steigeisen +Sportartikel > Outdoor-Aktivitäten > Klettern > Klettergurte +Sportartikel > Outdoor-Aktivitäten > Klettern > Kletterschutzausrüstung +Sportartikel > Outdoor-Aktivitäten > Klettern > Kletterseile +Sportartikel > Outdoor-Aktivitäten > Klettern > Kletterwandgriffe +Sportartikel > Outdoor-Aktivitäten > Klettern > Seilsäcke +Sportartikel > Outdoor-Aktivitäten > Klettern > Sicherungsgeräte +Sportartikel > Outdoor-Aktivitäten > Klettern > Steigklemmen & Abseilausrüstung Sportartikel > Outdoor-Aktivitäten > Radsport Sportartikel > Outdoor-Aktivitäten > Radsport > Dreiradzubehör Sportartikel > Outdoor-Aktivitäten > Radsport > Dreiräder -Sportartikel > Outdoor-Aktivitäten > Radsport > Dreiräder > Liegedreiräder Sportartikel > Outdoor-Aktivitäten > Radsport > Einradzubehör Sportartikel > Outdoor-Aktivitäten > Radsport > Einräder -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradausrüstung -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradausrüstung > Cleat-Zubehör -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradausrüstung > Cleat-Zubehör > Cleat-Platten -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradausrüstung > Cleat-Zubehör > Cleat-Schrauben -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradausrüstung > Cleat-Zubehör > Cleat-Schutzhüllen -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradausrüstung > Fahrradhandschuhe -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradausrüstung > Fahrradhelme -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradausrüstung > Fahrradhelmschutz -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradausrüstung > Fahrradüberschuhe -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradausrüstung > Schuhplatten Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrrad-Groupsets Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradbremsenteile Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradbremsenteile > Bremsen-Sets Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradbremsenteile > Bremshebel Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradbremsenteile > Bremsrotoren Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradbremsenteile > Bremssättel Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradgabeln -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradgriffe & Lenkergriffbänder +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradkabel +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradkabelgehäuse Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradlenkerverlängerungen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradlenkervorbau -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradlenkkopfteile -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradlenkkopfteile > Fahrradlenkkopf-Abstandsringe -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradlenkkopfteile > Fahrradlenkkopf-Halterungen +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradlenkkopf-Teile +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradlenkkopf-Teile > Fahrradlenkkopf-Abstandsringe +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradlenkkopf-Teile > Fahrradlenkkopf-Halterungen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradlenkköpfe Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradlenkstangen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradrahmen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradreifen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradräder Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradräderzubehör +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradräderzubehör > Fahrradachsen & -spanner Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradräderzubehör > Fahrradfelgen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradräderzubehör > Fahrradfußrasten Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradräderzubehör > Fahrradnaben +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradräderzubehör > Fahrradnabenteile Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradräderzubehör > Fahrradspeichen +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradräderzubehör > Felgenbänder Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradräderzubehör > Speichennippel Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradsattelstützen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradsattelstützenklemmen @@ -5425,10 +4911,23 @@ Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradschalt Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradschaltungsteile > Fahrradschalthebel Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradschaltungsteile > Tretlagergehäuse Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradschläuche +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradständer Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Fahrradsättel Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Kleinere Fahrradzubehörteile -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Kleinere Fahrradzubehörteile > Fahrradhalterungen -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Kleinere Fahrradzubehörteile > Fahrradklemmstücke +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Reifenventiladapter +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Reifenventile +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbauteile > Reifenventilkappen +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbekleidung & Zubehör +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbekleidung & Zubehör > Fahrrad-Schutzpolster +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbekleidung & Zubehör > Fahrradhandschuhe +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbekleidung & Zubehör > Fahrradhelme +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbekleidung & Zubehör > Fahrradhelmteile und -zubehör +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbekleidung & Zubehör > Fahrradüberschuhe +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbekleidung & Zubehör > Schuhplatten +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbekleidung & Zubehör > Schuhplatten-Zubehör +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbekleidung & Zubehör > Schuhplatten-Zubehör > Schuhplatten-Klemmen & -Keile +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbekleidung & Zubehör > Schuhplatten-Zubehör > Schuhplatten-Schrauben +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradbekleidung & Zubehör > Schuhplatten-Zubehör > Schuhplatten-Schutzhüllen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Dämpferpumpen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrrad-Fußriemen @@ -5438,493 +4937,222 @@ Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradcomput Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradgepäckträger Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradklingeln & -hupen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradkörbe -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradlichter & -reflektoren Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradpumpen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradsattelbezüge & -polster Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradschlösser Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradschutzbleche -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradspeichenreflektoren +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradspeichen-Reflektoren Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradspiegel Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradständer & Fahrradparksysteme Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradtaschen Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradtaschen & -koffer -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradwerkzeug, Reinigungsmittel & Schmiermittel -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradwerkzeug, Reinigungsmittel & Schmiermittel > Fahrradschmiermittel -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradwerkzeug, Reinigungsmittel & Schmiermittel > Fahrradspeichen-Spannschlüssel +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Fahrradwerkzeug Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Flaschenhalter für Fahrradrahmen +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Flicken & Reparatur-Sets +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Griffe und Dekor für Fahrradlenker Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Kinderfahrradsitze Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Stützräder Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Surfbretthalterungen für Fahrräder Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Trainingsrollen +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Umrüstsatz für E-Bikes Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Zubehör für Fahrradcomputer +Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrradzubehör > Zubehör für Fahrradkindersitze Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrräder -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrräder > Cruiser -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrräder > Elektrofahrräder -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrräder > Liegeräder -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrräder > Mountainbikes -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrräder > Rennräder -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrräder > Trekkingräder -Sportartikel > Outdoor-Aktivitäten > Radsport > Fahrräder > Triathlonräder -Sportartikel > Outdoor-Aktivitäten > Rasenspiele -Sportartikel > Outdoor-Aktivitäten > Rasenspiele > Bean Bag -Sportartikel > Outdoor-Aktivitäten > Rasenspiele > Boccia -Sportartikel > Outdoor-Aktivitäten > Rasenspiele > Hufeisenwerfen -Sportartikel > Outdoor-Aktivitäten > Rasenspiele > Krocket -Sportartikel > Outdoor-Aktivitäten > Rasenspiele > Leitergolf -Sportartikel > Outdoor-Aktivitäten > Rasenspiele > Rasen-Dart -Sportartikel > Outdoor-Aktivitäten > Rasenspiele > Rasenbowling -Sportartikel > Outdoor-Aktivitäten > Rasenspiele > Rasenbowling > Rasenbowling-Kugeln -Sportartikel > Outdoor-Aktivitäten > Rasenspiele > Rasenbowling > Rasenbowling-Zielkugeln -Sportartikel > Outdoor-Aktivitäten > Rasenspiele > Ringwurfspiel -Sportartikel > Outdoor-Aktivitäten > Rasenspiele > Washer Pitching -Sportartikel > Outdoor-Aktivitäten > Reflektierende Fitnessbekleidung -Sportartikel > Outdoor-Aktivitäten > Reflektierende Fitnessbekleidung > Reflektierende Gürtel Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör -Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Hufglocken & Gamaschen -Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Hufglocken & Gamaschen > Hufglocken -Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Hufglocken & Gamaschen > Pferdegamaschen Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Pferdebedarf Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Pferdebedarf > Abreitdecken & Weidedecken +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Pferdebedarf > Fliegenmasken +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Pferdebedarf > Hufglocken & Gamaschen Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Pferdebedarf > Leckerli für Pferde Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Pferdebedarf > Pferdefutter Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Pferdebedarf > Pferdehaarpflege +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Pferdebedarf > Pferdehaarpflege > Kämme, Bürsten & Striegel Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Pferdebedarf > Pferdehaarpflege > Pferdeschermaschinen Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Pferdebedarf > Vitamine & Futterergänzungsmittel für Pferde +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Pferdebedarf > Wurmkur für Pferde Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Reitausrüstung -Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Reitausrüstung > Gerten +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Reitausrüstung > Gerten & Peitschen Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Reitausrüstung > Reithandschuhe Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Reitausrüstung > Reithelme Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Reitausrüstung > Reithosen Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Sattel- & Zaumzeug +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Sattel- & Zaumzeug > Halfter Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Sattel- & Zaumzeug > Pferdegeschirr +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Sattel- & Zaumzeug > Pferdeleinen Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Sattel- & Zaumzeug > Sattelgurte +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Sattel- & Zaumzeug > Steigbügel Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Sattel- & Zaumzeug > Sättel Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Sattel- & Zaumzeug > Trensen Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Sattel- & Zaumzeug > Zaumzeug Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Sattel- & Zaumzeug > Zügel -Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Zubehör für Pferdesattel & -zaumzeug -Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Zubehör für Pferdesattel & -zaumzeug > Sattelauflagen +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Zubehör für Sattel & Zaumzeug +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Zubehör für Sattel & Zaumzeug > Sattel- & Zaumzeugbehälter +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Zubehör für Sattel & Zaumzeug > Sattelzubehör +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Zubehör für Sattel & Zaumzeug > Sattelzubehör > Sattelauflagen +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Zubehör für Sattel & Zaumzeug > Sattelzubehör > Sattelbezüge und -hüllen +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Zubehör für Sattel & Zaumzeug > Sattelzubehör > Sattelpolster & -decken +Sportartikel > Outdoor-Aktivitäten > Reitsportzubehör > Zubehör für Sattel & Zaumzeug > Sattelzubehör > Satteltaschen Sportartikel > Outdoor-Aktivitäten > Roller -Sportartikel > Outdoor-Aktivitäten > Roller > Motorroller -Sportartikel > Outdoor-Aktivitäten > Roller > Tretroller -Sportartikel > Outdoor-Aktivitäten > Schießspiele -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Battlepacks -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Gewehrteile -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Gewehrteile > Paintball-Gewehrläufe -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Gewehrteile > Paintball-Hopper -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Granaten -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Granatwerfer -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Markierer -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Pressluftflaschen -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Schutzausrüstung -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Schutzausrüstung > Paintball-Handschuhe -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Schutzausrüstung > Paintball-Protektoren -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Schutzausrüstung > Paintball-Schutzbrillen & -Masken -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintball-Schutzausrüstung > Paintball-Westen -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Paintball > Paintballs -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Softair -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Softair > Softair-Schutzausrüstung -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Softair > Softairkugeln -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Softair > Softairwaffen -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Softair > Softairwaffen-Teile & -Zubehör -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Softair > Softairwaffen-Teile & -Zubehör > Batterien für Softairwaffen -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Tontaubenschießen -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Tontaubenschießen > Tontauben -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Tontaubenschießen > Tontauben-Wurfmaschinen -Sportartikel > Outdoor-Aktivitäten > Schießspiele > Zielscheiben -Sportartikel > Outdoor-Aktivitäten > Schutzkleidung für Jagd- und Schießsport -Sportartikel > Outdoor-Aktivitäten > Schutzkleidung für Jagd- und Schießsport > Handschuhe für Jagd- und Schießsport -Sportartikel > Outdoor-Aktivitäten > Schutzkleidung für Jagd- und Schießsport > Jacken für Jagd- und Schießsport -Sportartikel > Outdoor-Aktivitäten > Shuffleboard -Sportartikel > Outdoor-Aktivitäten > Shuffleboard > Shuffleboard-Cues -Sportartikel > Outdoor-Aktivitäten > Shuffleboard > Shuffleboard-Disks Sportartikel > Outdoor-Aktivitäten > Skateboarding Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skate-Rails Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skate-Rampen -Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboard-Schutzausrüstung -Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboard-Schutzausrüstung > Handschuhe für Skateboarder -Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboard-Schutzausrüstung > Skateboard-Helme -Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboard-Schutzausrüstung > Skateboard-Protektoren Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboard-Teile Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboard-Teile > Skateboard-Achsen Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboard-Teile > Skateboard-Decks -Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboard-Teile > Skateboard-Kugellager +Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboard-Teile > Skateboard-Kleinteile Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboard-Teile > Skateboard-Rollen +Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboarding-Schutzausrüstung +Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboarding-Schutzausrüstung > Skate-Helme +Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboarding-Schutzausrüstung > Skateboarding-Handschuhe +Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboarding-Schutzausrüstung > Skateboarding-Protektoren Sportartikel > Outdoor-Aktivitäten > Skateboarding > Skateboards Sportartikel > Outdoor-Aktivitäten > Skaten -Sportartikel > Outdoor-Aktivitäten > Skaten > Eiskunstlaufstiefel Sportartikel > Outdoor-Aktivitäten > Skaten > Inline-Skates -Sportartikel > Outdoor-Aktivitäten > Skaten > Kufenschoner +Sportartikel > Outdoor-Aktivitäten > Skaten > Inlineskate-Teile Sportartikel > Outdoor-Aktivitäten > Skaten > Rollerskates -Sportartikel > Outdoor-Aktivitäten > Skaten > Schlittschuhe -Sportartikel > Outdoor-Aktivitäten > Skaten > Schlittschuhe > Eishockeyschlittschuhe -Sportartikel > Outdoor-Aktivitäten > Skaten > Schlittschuhe > Eiskunstlaufschuhe -Sportartikel > Outdoor-Aktivitäten > Skaten > Schlittschuhkufen -Sportartikel > Outdoor-Aktivitäten > Skaten > Schlittschuhschleifer -Sportartikel > Outdoor-Aktivitäten > Skaten > Schnürhilfen -Sportartikel > Outdoor-Aktivitäten > Skaten > Skate-Helme & -Protektoren -Sportartikel > Outdoor-Aktivitäten > Skaten > Skate-Zubehörteile -Sportartikel > Outdoor-Aktivitäten > Stadionsitze und Polster -Sportartikel > Outdoor-Aktivitäten > Tetherball +Sportartikel > Outdoor-Aktivitäten > Skaten > Rollschuh-Teile +Sportartikel > Outdoor-Aktivitäten > Skaten > Rollskis +Sportartikel > Outdoor-Aktivitäten > Skaten > Schutzausrüstung für Skater +Sportartikel > Outdoor-Aktivitäten > Skaten > Schutzausrüstung für Skater > Rollschuh-Polster +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Badminton +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Badminton > Badmintonnetze +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Badminton > Badmintonschläger & -sets +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Badminton > Federball +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Bälle für Paddle-Tennis +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Discgolf +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Discgolf > Discgolf-Fangkörbe +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Discgolf > Discgolf-Taschen +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Paddletennis +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Paddletennis > Paddletennis-Bälle +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Paddletennis > Paddletennis-Schläger +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Pickleball +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Pickleball > Pickleball-Bälle +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Pickleball > Pickleball-Schläger +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Rasenspiele +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Shuffleboard +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Shuffleboard > Shuffleboard-Cues +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Shuffleboard > Shuffleboard-Disks +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Tetherball +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Tetherball > Tetherball-Bälle +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Tetherball > Tetherball-Sets +Sportartikel > Outdoor-Aktivitäten > Spiele im Freien > Tetherball > Tetherball-Stangen Sportartikel > Outdoor-Aktivitäten > Trinksystem-Zubehör Sportartikel > Outdoor-Aktivitäten > Trinksystemrucksäcke & -taschen -Sportartikel > Sportarten & Spiele für drinnen -Sportartikel > Sportarten & Spiele für drinnen > Air-Hockey -Sportartikel > Sportarten & Spiele für drinnen > Air-Hockey > Air-Hockey-Pucks -Sportartikel > Sportarten & Spiele für drinnen > Air-Hockey > Air-Hockey-Schläger -Sportartikel > Sportarten & Spiele für drinnen > Air-Hockey > Air-Hockey-Tische -Sportartikel > Sportarten & Spiele für drinnen > Air-Hockey > Air-Hockey-Tischzubehör -Sportartikel > Sportarten & Spiele für drinnen > Billard -Sportartikel > Sportarten & Spiele für drinnen > Billard > Billarddreiecke -Sportartikel > Sportarten & Spiele für drinnen > Billard > Billardhandschuhe -Sportartikel > Sportarten & Spiele für drinnen > Billard > Billardkugeln -Sportartikel > Sportarten & Spiele für drinnen > Billard > Billardqueues & Queuebrücken -Sportartikel > Sportarten & Spiele für drinnen > Billard > Billardtisch-Beleuchtung -Sportartikel > Sportarten & Spiele für drinnen > Billard > Billardtisch-Teile & Zubehör -Sportartikel > Sportarten & Spiele für drinnen > Billard > Billardtisch-Teile & Zubehör > Ballfangtaschen -Sportartikel > Sportarten & Spiele für drinnen > Billard > Billardtisch-Teile & Zubehör > Billardtisch-Bürsten -Sportartikel > Sportarten & Spiele für drinnen > Billard > Billardtisch-Teile & Zubehör > Billardtücher -Sportartikel > Sportarten & Spiele für drinnen > Billard > Billardtische -Sportartikel > Sportarten & Spiele für drinnen > Billard > Queuezubehör -Sportartikel > Sportarten & Spiele für drinnen > Billard > Queuezubehör > Queueständer -Sportartikel > Sportarten & Spiele für drinnen > Bowling -Sportartikel > Sportarten & Spiele für drinnen > Bowling > Bowling-Handgelenkstützen -Sportartikel > Sportarten & Spiele für drinnen > Bowling > Bowlinghandschuhe -Sportartikel > Sportarten & Spiele für drinnen > Bowling > Bowlingkegel -Sportartikel > Sportarten & Spiele für drinnen > Bowling > Bowlingkugeln -Sportartikel > Sportarten & Spiele für drinnen > Bowling > Bowlingtaschen -Sportartikel > Sportarten & Spiele für drinnen > Darts -Sportartikel > Sportarten & Spiele für drinnen > Darts > Dart-Rückwände -Sportartikel > Sportarten & Spiele für drinnen > Darts > Dartpfeile & -Sets -Sportartikel > Sportarten & Spiele für drinnen > Darts > Dartscheiben -Sportartikel > Sportarten & Spiele für drinnen > Darts > Dartteile -Sportartikel > Sportarten & Spiele für drinnen > Darts > Dartteile > Dartflights -Sportartikel > Sportarten & Spiele für drinnen > Darts > Dartteile > Dartschäfte -Sportartikel > Sportarten & Spiele für drinnen > Darts > Dartteile > Dartspitzen -Sportartikel > Sportarten & Spiele für drinnen > Jakkolo -Sportartikel > Sportarten & Spiele für drinnen > Jakkolo > Jakkolo-Pulver -Sportartikel > Sportarten & Spiele für drinnen > Jakkolo > Jakkolo-Spielsteine -Sportartikel > Sportarten & Spiele für drinnen > Jakkolo > Shuffleboard-Tische -Sportartikel > Sportarten & Spiele für drinnen > Multispieltische -Sportartikel > Sportarten & Spiele für drinnen > Tischfußball -Sportartikel > Sportarten & Spiele für drinnen > Tischfußball > Tischfußball-Bälle -Sportartikel > Sportarten & Spiele für drinnen > Tischfußball > Tischfußball-Tische -Sportartikel > Sportarten & Spiele für drinnen > Tischfußball > Zubehör für Tischfußballtische -Sportartikel > Sportarten & Spiele für drinnen > Tischtennis -Sportartikel > Sportarten & Spiele für drinnen > Tischtennis > Tischtennis-Roboter -Sportartikel > Sportarten & Spiele für drinnen > Tischtennis > Tischtennisbälle -Sportartikel > Sportarten & Spiele für drinnen > Tischtennis > Tischtennisnetze und -pfosten -Sportartikel > Sportarten & Spiele für drinnen > Tischtennis > Tischtennisplatten -Sportartikel > Sportarten & Spiele für drinnen > Tischtennis > Tischtennisschläger -Sportartikel > Sportarten & Spiele für drinnen > Tischtennis > Zubehör für Tischtennis-Roboter -Sportartikel > Sportarten & Spiele für drinnen > Tischtennis > Zubehör für Tischtennisschläger -Sportartikel > Sportarten mit Schläger -Sportartikel > Sportarten mit Schläger > Badminton -Sportartikel > Sportarten mit Schläger > Badminton > Badmintonnetze -Sportartikel > Sportarten mit Schläger > Badminton > Badmintonschläger -Sportartikel > Sportarten mit Schläger > Badminton > Federbälle -Sportartikel > Sportarten mit Schläger > Bälle für Paddle-Tennis -Sportartikel > Sportarten mit Schläger > Paddle-Tennis -Sportartikel > Sportarten mit Schläger > Pickleball -Sportartikel > Sportarten mit Schläger > Platform-Tennis -Sportartikel > Sportarten mit Schläger > Racquetball -Sportartikel > Sportarten mit Schläger > Racquetball > Racquetball-Bälle -Sportartikel > Sportarten mit Schläger > Racquetball > Racquetball-Schläger -Sportartikel > Sportarten mit Schläger > Racquetball > Racquetball-Schutzausrüstung -Sportartikel > Sportarten mit Schläger > Racquetball > Racquetball-Schutzausrüstung > Squash- & Racquetball-Schutzbrillen -Sportartikel > Sportarten mit Schläger > Squash -Sportartikel > Sportarten mit Schläger > Squash > Squash-Bälle -Sportartikel > Sportarten mit Schläger > Squash > Squash-Schläger -Sportartikel > Sportarten mit Schläger > Squash > Squash-Schutzausrüstung -Sportartikel > Sportarten mit Schläger > Tennis -Sportartikel > Sportarten mit Schläger > Tennis > Tennisballdosen -Sportartikel > Sportarten mit Schläger > Tennis > Tennisballkörbe -Sportartikel > Sportarten mit Schläger > Tennis > Tennisballwurfmaschinen -Sportartikel > Sportarten mit Schläger > Tennis > Tennisbälle -Sportartikel > Sportarten mit Schläger > Tennis > Tennisnetze -Sportartikel > Sportarten mit Schläger > Tennis > Tennisschläger -Sportartikel > Sportarten mit Schläger > Tennis > Tennisschlägerzubehör -Sportartikel > Sportarten mit Schläger > Tennis > Tennisschlägerzubehör > Griffbänder -Sportartikel > Sportarten mit Schläger > Tennis > Tennisschlägerzubehör > Tennissaiten -Sportartikel > Sportarten mit Schläger > Tennis > Tennisschlägerzubehör > Tennistaschen -Sportartikel > Sportarten mit Schläger > Tennis > Tennisschlägerzubehör > Vibrationsdämpfer -Sportartikel > Sportarten mit Schläger > Tennis > Tennisschlägerzubehör > Ösenbänder für Tennisschläger -Sportartikel > Sportübungen & Fitness -Sportartikel > Sportübungen & Fitness > Bauchtrainer -Sportartikel > Sportübungen & Fitness > Fitnessbänder -Sportartikel > Sportübungen & Fitness > Gewichthebergürtel -Sportartikel > Sportübungen & Fitness > Gewichtheberhandschuhe & Handgelenkschoner -Sportartikel > Sportübungen & Fitness > Gewichtswesten & -manschetten -Sportartikel > Sportübungen & Fitness > Gymnastikbälle -Sportartikel > Sportübungen & Fitness > Gymnastikmatten -Sportartikel > Sportübungen & Fitness > Gymnastikmatten > Matten für Heimtrainer -Sportartikel > Sportübungen & Fitness > Hantelbänke -Sportartikel > Sportübungen & Fitness > Hanteln -Sportartikel > Sportübungen & Fitness > Hantelstangen -Sportartikel > Sportübungen & Fitness > Hantelzubehör -Sportartikel > Sportübungen & Fitness > Kardiogeräte -Sportartikel > Sportübungen & Fitness > Kardiogeräte > Crosstrainer -Sportartikel > Sportübungen & Fitness > Kardiogeräte > Heimtrainer -Sportartikel > Sportübungen & Fitness > Kardiogeräte > Laufbänder -Sportartikel > Sportübungen & Fitness > Kardiogeräte > Rudergeräte -Sportartikel > Sportübungen & Fitness > Kardiogeräte > Stepper -Sportartikel > Sportübungen & Fitness > Krafttrainingsgeräte -Sportartikel > Sportübungen & Fitness > Medizinbälle -Sportartikel > Sportübungen & Fitness > Reaction Balls -Sportartikel > Sportübungen & Fitness > Schaumroller -Sportartikel > Sportübungen & Fitness > Schaumroller-Zubehör -Sportartikel > Sportübungen & Fitness > Schaumroller-Zubehör > Aufbewahrungstaschen für Schaumroller -Sportartikel > Sportübungen & Fitness > Springseile -Sportartikel > Sportübungen & Fitness > Stoppuhren -Sportartikel > Sportübungen & Fitness > Yoga & Pilates -Sportartikel > Sportübungen & Fitness > Yoga & Pilates > Beutel für Yogamatten -Sportartikel > Sportübungen & Fitness > Yoga & Pilates > Pilates-Geräte -Sportartikel > Sportübungen & Fitness > Yoga & Pilates > Yoga- & Pilatesblöcke -Sportartikel > Sportübungen & Fitness > Yoga & Pilates > Yoga- & Pilatesmatten -Sportartikel > Sportübungen & Fitness > Yoga- & Pilateszubehör -Sportartikel > Sportübungen & Fitness > Yoga- & Pilateszubehör > Yoga- & Pilatestücher -Sportartikel > Sportübungen & Fitness > Zubehör für Kardiogeräte -Sportartikel > Sportübungen & Fitness > Zubehör für Krafttrainingsgeräte -Sportartikel > Springen -Sportartikel > Springen > Bungee-Jumping -Sportartikel > Springen > Springstöcke -Sportartikel > Springen > Sprungstelzen -Sportartikel > Springen > Trampoline -Sportartikel > Springen > Trampolinzubehör -Sportartikel > Springen > Trampolinzubehör > Trampolin-Sicherheitspolster -Sportartikel > Springen > Trampolinzubehör > Trampolin-Verankerungssets -Sportartikel > Springen > Trampolinzubehör > Trampolinfedern -Sportartikel > Springen > Trampolinzubehör > Trampolinleitern -Sportartikel > Springen > Trampolinzubehör > Trampolinnetze -Sportartikel > Springen > Trampolinzubehör > Trampolinsprungtücher -Sportartikel > Tanzen -Sportartikel > Tanzen > Ballettstangen -Sportartikel > Turnen -Sportartikel > Turnen > Schutzausrüstung -Sportartikel > Turnen > Schwebebalken -Sportartikel > Turnen > Sprungpferde -Sportartikel > Turnen > Turnmatten -Sportartikel > Turnen > Turnpferde -Sportartikel > Turnen > Turnringe -Sportartikel > Turnen > Turnsprungbretter -Sportartikel > Turnen > Turnstangen -Sportartikel > Wassersport -Sportartikel > Wassersport > Ablagen für Wasserfahrzeuge -Sportartikel > Wassersport > Bootssport -Sportartikel > Wassersport > Bootssport > Handschuhe für den Bootssport -Sportartikel > Wassersport > Bootssport > Kajaksport -Sportartikel > Wassersport > Bootssport > Kajaksport > Kajak-Zubehör -Sportartikel > Wassersport > Bootssport > Kajaksport > Kajak-Zubehör > Spritzdecken -Sportartikel > Wassersport > Bootssport > Kajaksport > Kajaks -Sportartikel > Wassersport > Bootssport > Kanusport -Sportartikel > Wassersport > Bootssport > Kanusport > Kanu-Zubehör -Sportartikel > Wassersport > Bootssport > Kanusport > Kanus -Sportartikel > Wassersport > Bootssport > Raftingboote -Sportartikel > Wassersport > Bootssport > Rudern -Sportartikel > Wassersport > Bootssport > Rudern > Ruderboote -Sportartikel > Wassersport > Bootssport > Rudern > Sitzauflagen -Sportartikel > Wassersport > Bootssport > Segel- & Yachtsport -Sportartikel > Wassersport > Bootssport > Tretboote -Sportartikel > Wassersport > Kitesurfen -Sportartikel > Wassersport > Kitesurfen > Kiteboard-Taschen -Sportartikel > Wassersport > Kitesurfen > Kiteboards -Sportartikel > Wassersport > Kitesurfen > Kiteboardteile -Sportartikel > Wassersport > Kitesurfen > Kitesurfing-Kites -Sportartikel > Wassersport > Kitesurfen > Kitesurfing-Trapeze -Sportartikel > Wassersport > Neoprenanzüge -Sportartikel > Wassersport > Neoprenanzüge > Neopren-Oberteile -Sportartikel > Wassersport > Neoprenanzüge > Neopren-Unterteile -Sportartikel > Wassersport > Neoprenanzüge > Zubehör für Neoprenanzüge -Sportartikel > Wassersport > Paddel & Ruder -Sportartikel > Wassersport > Rafting -Sportartikel > Wassersport > Rafting > Raftinghelme -Sportartikel > Wassersport > Rafting > Rafts -Sportartikel > Wassersport > Rettungswesten -Sportartikel > Wassersport > Schwimmen -Sportartikel > Wassersport > Schwimmen > Badekappen -Sportartikel > Wassersport > Schwimmen > Gegenstromanlagen -Sportartikel > Wassersport > Schwimmen > Handpaddel -Sportartikel > Wassersport > Schwimmen > Nasenklammern -Sportartikel > Wassersport > Schwimmen > Pull Buoys -Sportartikel > Wassersport > Schwimmen > Schwimmbretter -Sportartikel > Wassersport > Schwimmen > Schwimmbrillen & -masken -Sportartikel > Wassersport > Schwimmen > Schwimmflossen -Sportartikel > Wassersport > Schwimmen > Schwimmflossen > Monoflossen -Sportartikel > Wassersport > Schwimmen > Schwimmflossen > Trainingsflossen -Sportartikel > Wassersport > Schwimmen > Schwimmgewichte -Sportartikel > Wassersport > Schwimmen > Schwimmgürtel -Sportartikel > Wassersport > Schwimmen > Schwimmhandschuhe -Sportartikel > Wassersport > Schwimmen > Zubehör für Schwimmbrillen & -masken -Sportartikel > Wassersport > Schwimmwestenzubehör -Sportartikel > Wassersport > Seilgebundene Wassersportarten -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Handschuhe für Wassersport mit Zugseil -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Kneeboarding -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Kneeboarding > Kneeboards -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Parasailing -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Tubes & Towables -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Wakeboarding -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Wakeboarding > Wakeboard-Bindungen -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Wakeboarding > Wakeboards -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Wasserski -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Wasserski > Hydrofoils -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Wasserski > Wasserski-Bindungen -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Wasserski > Wasserski-Koffer & -Taschen -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Wasserski > Wasserskier -Sportartikel > Wassersport > Seilgebundene Wassersportarten > Zugseile -Sportartikel > Wassersport > Surfen -Sportartikel > Wassersport > Surfen > Bodyboards -Sportartikel > Wassersport > Surfen > Fußschlaufen & Trapeztampen -Sportartikel > Wassersport > Surfen > Handschuhe für Surfer -Sportartikel > Wassersport > Surfen > Paddelboards -Sportartikel > Wassersport > Surfen > Skimboards -Sportartikel > Wassersport > Surfen > Surfboard-Taschen -Sportartikel > Wassersport > Surfen > Surfboard-Wachs -Sportartikel > Wassersport > Surfen > Surfbretter -Sportartikel > Wassersport > Surfen > Surfbrettfinnen -Sportartikel > Wassersport > Surfen > Surfpads -Sportartikel > Wassersport > Tauchen & Schnorcheln -Sportartikel > Wassersport > Tauchen & Schnorcheln > Atemregler -Sportartikel > Wassersport > Tauchen & Schnorcheln > Bleigürtel -Sportartikel > Wassersport > Tauchen & Schnorcheln > Handschuhe für Taucher & Schnorchler -Sportartikel > Wassersport > Tauchen & Schnorcheln > Schnorchel -Sportartikel > Wassersport > Tauchen & Schnorcheln > Tarierwesten -Sportartikel > Wassersport > Tauchen & Schnorcheln > Tauch- & Schnorchelflossen -Sportartikel > Wassersport > Tauchen & Schnorcheln > Tauch- & Schnorchelmasken -Sportartikel > Wassersport > Tauchen & Schnorcheln > Tauchcomputer -Sportartikel > Wassersport > Tauchen & Schnorcheln > Tauchermesser & -scheren -Sportartikel > Wassersport > Tauchen & Schnorcheln > Trockenanzüge -Sportartikel > Wassersport > Tubing -Sportartikel > Wassersport > UV-Schutz-Shirts -Sportartikel > Wassersport > Wasserball -Sportartikel > Wassersport > Wasserball > Wasserballkappen -Sportartikel > Wassersport > Wasserball > Wasserballtore -Sportartikel > Wassersport > Wasserball > Wasserbälle -Sportartikel > Wassersport > Windsurfen -Sportartikel > Wassersport > Windsurfen > Windsurf-Ausrüstung -Sportartikel > Wassersport > Windsurfen > Windsurf-Ausrüstung > Windsurf-Finnen -Sportartikel > Wassersport > Windsurfen > Windsurf-Ausrüstung > Windsurfmasten -Sportartikel > Wassersport > Windsurfen > Windsurf-Ausrüstung > Windsurfsegel -Sportartikel > Wassersport > Windsurfen > Windsurfbretter -Sportartikel > Wintersport -Sportartikel > Wintersport > Bobs -Sportartikel > Wintersport > Rodeln -Sportartikel > Wintersport > Schlitten -Sportartikel > Wintersport > Schlitten > Rodelschlitten -Sportartikel > Wintersport > Schlitten > Rodelschlitten > Rennschlitten -Sportartikel > Wintersport > Schlitten > Rodelschlitten > Schneerutscher -Sportartikel > Wintersport > Schlitten > Rodelschlitten > Toboggans -Sportartikel > Wintersport > Schlitten > Rodelschläuche -Sportartikel > Wintersport > Schneemobilsport -Sportartikel > Wintersport > Schneeschuhe & Zubehör -Sportartikel > Wintersport > Schneeschuhe & Zubehör > Schneeschuhbinder -Sportartikel > Wintersport > Schneeschuhe & Zubehör > Schneeschuhe -Sportartikel > Wintersport > Skisport -Sportartikel > Wintersport > Skisport > Ski- & Snowboard-Brillen -Sportartikel > Wintersport > Skisport > Ski- & Snowboard-Brillengläser -Sportartikel > Wintersport > Skisport > Ski- & Snowboardhelme -Sportartikel > Wintersport > Skisport > Skibindungen -Sportartikel > Wintersport > Skisport > Skibindungen > Skibindungen Alpin -Sportartikel > Wintersport > Skisport > Skibindungen > Skibindungen Langlauf -Sportartikel > Wintersport > Skisport > Skier -Sportartikel > Wintersport > Skisport > Skier > Alpinskier -Sportartikel > Wintersport > Skisport > Skier > Langlaufskier -Sportartikel > Wintersport > Skisport > Skier- & Snowboard-Ablagen -Sportartikel > Wintersport > Skisport > Skier- & Snowboard-Taschen -Sportartikel > Wintersport > Skisport > Skier- & Snowboard-Taschen > Skitaschen -Sportartikel > Wintersport > Skisport > Skier- & Snowboard-Taschen > Snowboardtaschen -Sportartikel > Wintersport > Skisport > Skischuhe -Sportartikel > Wintersport > Skisport > Skischuhe > Skischuhe Alpin -Sportartikel > Wintersport > Skisport > Skischuhe > Skischuhe Langlauf -Sportartikel > Wintersport > Skisport > Skistöcke -Sportartikel > Wintersport > Snowboarden -Sportartikel > Wintersport > Snowboarden > Snowboard-Bindungen -Sportartikel > Wintersport > Snowboarden > Snowboards -Sportartikel > Wintersport > Snowboarden > Snowboardstiefel -Sportartikel > Wintersport > Snowboarden > Zubehör Snowboard-Bindungen +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Lawinenschutz +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Lawinenschutz > Lawinenairbags +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Lawinenschutz > Lawinensonden +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Rodelschlitten +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Schneeschuhe & Zubehör +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Schneeschuhe & Zubehör > Schneeschuh-Bindungen +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Schneeschuhe & Zubehör > Schneeschuhe +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Ersatzteile für Skibindungen +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Ski- & Snowboard-Ablagen +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Ski- & Snowboard-Brillen +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Ski- & Snowboard-Brillenzubehör +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Ski- & Snowboard-Brillenzubehör > Ski- & Snowboard-Brillengläser +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Ski- & Snowboard-Leinen +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Ski- & Snowboard-Taschen +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Ski- & Snowboard-Wachs +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Ski- & Snowboardhelme +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Skibindungen +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Skier +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Skier > Alpinskier +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Skier > Langlaufskier +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Skischuhe +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Skistöcke +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Snowboard-Bindungen +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Snowboards +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Snowboardstiefel +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Tuningwerkzeuge für Skis & Snowboards +Sportartikel > Outdoor-Aktivitäten > Wintersport & -Aktivitäten > Skisport & Snowboarden > Zubehör Snowboard-Bindungen Taschen & Gepäck +Taschen & Gepäck > Aktentaschen Taschen & Gepäck > Bauchtaschen -Taschen & Gepäck > Business-Taschen -Taschen & Gepäck > Business-Taschen > Aktentaschen -Taschen & Gepäck > Business-Taschen > Kleidertaschen -Taschen & Gepäck > Business-Taschen > Taschen & Etuis für elektronische Geräte -Taschen & Gepäck > Business-Taschen > Taschen & Etuis für elektronische Geräte > Computerschutzhüllen -Taschen & Gepäck > Business-Taschen > Taschen & Etuis für elektronische Geräte > Laptoptaschen & -Etuis -Taschen & Gepäck > Business-Taschen > Taschen & Etuis für elektronische Geräte > Taschen & Etuis für Tablets Taschen & Gepäck > Einkaufstaschen Taschen & Gepäck > Gepäckzubehör +Taschen & Gepäck > Gepäckzubehör > Folien & Einsätze für Trockenboxen Taschen & Gepäck > Gepäckzubehör > Gepäck-Organizer Taschen & Gepäck > Gepäckzubehör > Gepäckanhänger +Taschen & Gepäck > Gepäckzubehör > Kofferablagen Taschen & Gepäck > Gepäckzubehör > Koffergurte Taschen & Gepäck > Gepäckzubehör > Reisebehälter Taschen & Gepäck > Gepäckzubehör > Reisedokumententaschen +Taschen & Gepäck > Gepäckzubehör > Rucksack-Schutzhüllen +Taschen & Gepäck > Kleidertaschen Taschen & Gepäck > Koffer -Taschen & Gepäck > Koffer > Handgepäck Taschen & Gepäck > Kosmetikkoffer Taschen & Gepäck > Kulturtaschen Taschen & Gepäck > Kurier- & Schultertaschen -Taschen & Gepäck > Rucksackzubehör -Taschen & Gepäck > Rucksackzubehör > Rucksack-Schutzhüllen Taschen & Gepäck > Rucksäcke Taschen & Gepäck > Seesäcke +Taschen & Gepäck > Trockenboxen Taschen & Gepäck > Wickeltaschen Tiere & Tierbedarf Tiere & Tierbedarf > Haustierbedarf +Tiere & Tierbedarf > Haustierbedarf > Agility-Ausrüstung Tiere & Tierbedarf > Haustierbedarf > Aquaristik Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Aquarien Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Aquariumbeleuchtung Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Aquariumdekoration -Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Aquariumdekoration > Künstliche Aquariumpflanzen Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Aquariumfilter -Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Aquariumpumpen Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Aquariumständer +Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Ausströmersteine & Diffusoren für Aquarien Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Bodengrund für Aquarien Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Fischfutter Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Fischfutterautomaten -Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Heizvorrichtungen für Aquarien +Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Fischnetze für Aquarien +Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Schläuche & Rohre für Aquarien & Teiche +Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Temperaturregler für Aquarien Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Wasseraufbereitungsgeräte Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Wasserpflanzendünger -Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Wasserpumpen für Aquarien +Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Zubehör für Aquariumreinigung Tiere & Tierbedarf > Haustierbedarf > Aquaristik > Überlaufkästen Tiere & Tierbedarf > Haustierbedarf > Augentropfen für Haustiere Tiere & Tierbedarf > Haustierbedarf > Begrenzungssysteme -Tiere & Tierbedarf > Haustierbedarf > Bodenschutz & Unterlagen Tiere & Tierbedarf > Haustierbedarf > Bügel für Haustierbekleidung +Tiere & Tierbedarf > Haustierbedarf > Entsorgungssysteme & -werkzeug für Tierkot Tiere & Tierbedarf > Haustierbedarf > Erkennungsmarken +Tiere & Tierbedarf > Haustierbedarf > Erste-Hilfe-Koffer für Haustiere Tiere & Tierbedarf > Haustierbedarf > Fitnessmonitor für Haustiere Tiere & Tierbedarf > Haustierbedarf > Fitnessmonitor für Haustiere > Glukosemessgeräte für Haustiere Tiere & Tierbedarf > Haustierbedarf > Fitnessmonitor für Haustiere > Schrittzähler für Haustiere Tiere & Tierbedarf > Haustierbedarf > Fitnessmonitor für Haustiere > Thermometer für Haustiere -Tiere & Tierbedarf > Haustierbedarf > Fitnessmonitor für Haustiere > Tierwaagen Tiere & Tierbedarf > Haustierbedarf > Floh- & Zeckenschutzmittel -Tiere & Tierbedarf > Haustierbedarf > Floh- & Zeckenschutzmittel > Mittel gegen Hundeflöhe/-zecken -Tiere & Tierbedarf > Haustierbedarf > Floh- & Zeckenschutzmittel > Mittel gegen Katzenflöhe/-zecken -Tiere & Tierbedarf > Haustierbedarf > Futternäpfe, Futter- & Wasserspender -Tiere & Tierbedarf > Haustierbedarf > Futternäpfe, Futter- & Wasserspender > Futternäpfe, Futter- & Wasserspender für Kleintiere -Tiere & Tierbedarf > Haustierbedarf > Futternäpfe, Futter- & Wasserspender > Hundeschüsseln, Futter- & Wasserspender -Tiere & Tierbedarf > Haustierbedarf > Futternäpfe, Futter- & Wasserspender > Katzenschüsseln, Futter- & Wasserspender +Tiere & Tierbedarf > Haustierbedarf > Futter- & Wasserspender Tiere & Tierbedarf > Haustierbedarf > Glocken & Anhänger Tiere & Tierbedarf > Haustierbedarf > Halsbänder & Geschirre -Tiere & Tierbedarf > Haustierbedarf > Halsbänder & Geschirre > Hundehalsbänder & Hundegeschirre -Tiere & Tierbedarf > Haustierbedarf > Halsbänder & Geschirre > Katzenhalsbänder & Katzengeschirre Tiere & Tierbedarf > Haustierbedarf > Haustierpflegeartikel +Tiere & Tierbedarf > Haustierbedarf > Haustierpflegeartikel > Düfte & Deosprays für Haustiere +Tiere & Tierbedarf > Haustierbedarf > Haustierpflegeartikel > Feuchttücher für Haustiere +Tiere & Tierbedarf > Haustierbedarf > Haustierpflegeartikel > Haartrockner für Haustiere Tiere & Tierbedarf > Haustierbedarf > Haustierpflegeartikel > Kämme & Bürsten zur Fellpflege Tiere & Tierbedarf > Haustierbedarf > Haustierpflegeartikel > Nagellack für Haustiere Tiere & Tierbedarf > Haustierbedarf > Haustierpflegeartikel > Nagelpflege für Haustiere Tiere & Tierbedarf > Haustierbedarf > Haustierpflegeartikel > Shampoos & Spülungen zur Fellpflege Tiere & Tierbedarf > Haustierbedarf > Haustierpflegeartikel > Tierhaarschneider -Tiere & Tierbedarf > Haustierbedarf > Haustiersportbedarf +Tiere & Tierbedarf > Haustierbedarf > Haustiertüren +Tiere & Tierbedarf > Haustierbedarf > Hilfsmittel für Tierausbildung +Tiere & Tierbedarf > Haustierbedarf > Hilfsmittel für Tierausbildung > Bodenschutz & Unterlagen +Tiere & Tierbedarf > Haustierbedarf > Hilfsmittel für Tierausbildung > Clicker & Belohnungsspielzeug für Tierausbildung +Tiere & Tierbedarf > Haustierbedarf > Hilfsmittel für Tierausbildung > Pad-Unterlagen für Tierausbildung +Tiere & Tierbedarf > Haustierbedarf > Hilfsmittel für Tierausbildung > Sprays & Lösungen für Tierausbildung Tiere & Tierbedarf > Haustierbedarf > Hundebedarf +Tiere & Tierbedarf > Haustierbedarf > Hundebedarf > Einlagen für Hundewindeln Tiere & Tierbedarf > Haustierbedarf > Hundebedarf > Hundebekleidung Tiere & Tierbedarf > Haustierbedarf > Hundebedarf > Hundebetten Tiere & Tierbedarf > Haustierbedarf > Hundebedarf > Hundefutter Tiere & Tierbedarf > Haustierbedarf > Hundebedarf > Hundehütten +Tiere & Tierbedarf > Haustierbedarf > Hundebedarf > Hundelaufbänder Tiere & Tierbedarf > Haustierbedarf > Hundebedarf > Hundespielzeug Tiere & Tierbedarf > Haustierbedarf > Hundebedarf > Hundewindeln Tiere & Tierbedarf > Haustierbedarf > Hundebedarf > Hundezwinger & -ausläufe Tiere & Tierbedarf > Haustierbedarf > Hundebedarf > Leckerbissen für Hunde Tiere & Tierbedarf > Haustierbedarf > Hundebedarf > Zubehör für Hundezwinger & -ausläufe Tiere & Tierbedarf > Haustierbedarf > Katzenbedarf +Tiere & Tierbedarf > Haustierbedarf > Katzenbedarf > Einlagen für Katzentoilette Tiere & Tierbedarf > Haustierbedarf > Katzenbedarf > Katzenbekleidung Tiere & Tierbedarf > Haustierbedarf > Katzenbedarf > Katzenbetten Tiere & Tierbedarf > Haustierbedarf > Katzenbedarf > Katzenfutter @@ -5934,23 +5162,20 @@ Tiere & Tierbedarf > Haustierbedarf > Katzenbedarf > Katzenstreu Tiere & Tierbedarf > Haustierbedarf > Katzenbedarf > Katzentoiletten Tiere & Tierbedarf > Haustierbedarf > Katzenbedarf > Leckerbissen für Katzen Tiere & Tierbedarf > Haustierbedarf > Katzenbedarf > Vorleger für Katzentoiletten +Tiere & Tierbedarf > Haustierbedarf > Katzenbedarf > Zubehör für Katzenmöbel Tiere & Tierbedarf > Haustierbedarf > Kleintierzubehör Tiere & Tierbedarf > Haustierbedarf > Kleintierzubehör > Decken & Streu für Kleintiere Tiere & Tierbedarf > Haustierbedarf > Kleintierzubehör > Futter für Kleintiere Tiere & Tierbedarf > Haustierbedarf > Kleintierzubehör > Lebensräume & Gehege für Kleintiere Tiere & Tierbedarf > Haustierbedarf > Kleintierzubehör > Leckerli für Kleintiere Tiere & Tierbedarf > Haustierbedarf > Kleintierzubehör > Zubehör für Lebensräume von Kleintieren +Tiere & Tierbedarf > Haustierbedarf > Kotbeutel Tiere & Tierbedarf > Haustierbedarf > Leinen -Tiere & Tierbedarf > Haustierbedarf > Leinen > Hundeleinen -Tiere & Tierbedarf > Haustierbedarf > Leinen > Katzenleinen -Tiere & Tierbedarf > Haustierbedarf > Lockmittel für Haustiere Tiere & Tierbedarf > Haustierbedarf > Maulkörbe Tiere & Tierbedarf > Haustierbedarf > Medizinische Halsbänder für Haustiere Tiere & Tierbedarf > Haustierbedarf > Medizinisches Klebeband & Verbandsmaterial für Haustiere Tiere & Tierbedarf > Haustierbedarf > Mundpflege für Haustiere -Tiere & Tierbedarf > Haustierbedarf > Mundpflege für Haustiere > Mundspray für Haustiere -Tiere & Tierbedarf > Haustierbedarf > Mundpflege für Haustiere > Zahnbürsten für Haustiere -Tiere & Tierbedarf > Haustierbedarf > Mundpflege für Haustiere > Zahnpasta für Haustiere +Tiere & Tierbedarf > Haustierbedarf > Napfständer Tiere & Tierbedarf > Haustierbedarf > Napfunterlagen Tiere & Tierbedarf > Haustierbedarf > Pet-Stroller Tiere & Tierbedarf > Haustierbedarf > Reptilien- & Amphibienbedarf @@ -5960,14 +5185,13 @@ Tiere & Tierbedarf > Haustierbedarf > Reptilien- & Amphibienbedarf > Reptilien- Tiere & Tierbedarf > Haustierbedarf > Reptilien- & Amphibienbedarf > Terrarien Tiere & Tierbedarf > Haustierbedarf > Reptilien- & Amphibienbedarf > Zubehör für Terrarien Tiere & Tierbedarf > Haustierbedarf > Sonnenschutz für Haustiere +Tiere & Tierbedarf > Haustierbedarf > Spender & Halterungen für Kotbeutel Tiere & Tierbedarf > Haustierbedarf > Spielgehege für Haustiere Tiere & Tierbedarf > Haustierbedarf > Tierfutterbehälter Tiere & Tierbedarf > Haustierbedarf > Tierfutterportionierer Tiere & Tierbedarf > Haustierbedarf > Tiermedikamente -Tiere & Tierbedarf > Haustierbedarf > Transportboxen und -taschen für Haustiere -Tiere & Tierbedarf > Haustierbedarf > Transportboxen und -taschen für Haustiere > Tragetaschen für Kleintiere -Tiere & Tierbedarf > Haustierbedarf > Transportboxen und -taschen für Haustiere > Transportboxen und -taschen für Hunde -Tiere & Tierbedarf > Haustierbedarf > Transportboxen und -taschen für Haustiere > Transportboxen und -taschen für Katzen +Tiere & Tierbedarf > Haustierbedarf > Transportboxen & -taschen +Tiere & Tierbedarf > Haustierbedarf > Trennvorrichtungen für den Haustiertransport Tiere & Tierbedarf > Haustierbedarf > Treppen & Rampen für Haustiere Tiere & Tierbedarf > Haustierbedarf > Verlängerungsleinen Tiere & Tierbedarf > Haustierbedarf > Vitamine & Nahrungsergänzungsmittel für Haustiere @@ -5977,29 +5201,44 @@ Tiere & Tierbedarf > Haustierbedarf > Vogelbedarf > Spielvorrichtungen für Vög Tiere & Tierbedarf > Haustierbedarf > Vogelbedarf > Vogelfutter Tiere & Tierbedarf > Haustierbedarf > Vogelbedarf > Vogelkäfige & -ständer Tiere & Tierbedarf > Haustierbedarf > Vogelbedarf > Vogelkäfigzubehör -Tiere & Tierbedarf > Haustierbedarf > Vogelbedarf > Vogelkäfigzubehör > Futter- und Wasserspender für Vogelkäfige +Tiere & Tierbedarf > Haustierbedarf > Vogelbedarf > Vogelkäfigzubehör > Futter- & Wasserspender für Vogelkäfige +Tiere & Tierbedarf > Haustierbedarf > Vogelbedarf > Vogelkäfigzubehör > Vogelbäder für Vogelkäfige Tiere & Tierbedarf > Haustierbedarf > Vogelbedarf > Vogelleitern & -sitzstangen Tiere & Tierbedarf > Haustierbedarf > Vogelbedarf > Vogelspielzeug +Tiere & Tierbedarf > Haustierbedarf > Wärmekissen für Haustiere +Tiere & Tierbedarf > Haustierbedarf > Zubehör für Haustier-Heizkissen +Tiere & Tierbedarf > Haustierbedarf > Zubehör für Haustierbetten +Tiere & Tierbedarf > Haustierbedarf > Zubehör für Haustierklappen +Tiere & Tierbedarf > Haustierbedarf > Zubehör für Haustiertragetaschen und -boxen Tiere & Tierbedarf > Lebende Tiere Wirtschaft & Industrie Wirtschaft & Industrie > Arbeitsschutzausrüstung Wirtschaft & Industrie > Arbeitsschutzausrüstung > Chemikalienschutzanzüge -Wirtschaft & Industrie > Arbeitsschutzausrüstung > Feuerschutzkleidung -Wirtschaft & Industrie > Arbeitsschutzausrüstung > Feuerwehr-Atemschutzmasken -Wirtschaft & Industrie > Arbeitsschutzausrüstung > Gasmasken +Wirtschaft & Industrie > Arbeitsschutzausrüstung > Gasmasken- & Atemschutzmaskenzubehör Wirtschaft & Industrie > Arbeitsschutzausrüstung > Halteseile +Wirtschaft & Industrie > Arbeitsschutzausrüstung > Knieschoner Wirtschaft & Industrie > Arbeitsschutzausrüstung > Kugelsichere Westen Wirtschaft & Industrie > Arbeitsschutzausrüstung > Schutzbrillen Wirtschaft & Industrie > Arbeitsschutzausrüstung > Schutzhandschuhe Wirtschaft & Industrie > Arbeitsschutzausrüstung > Schutzhelme +Wirtschaft & Industrie > Arbeitsschutzausrüstung > Schutzmasken +Wirtschaft & Industrie > Arbeitsschutzausrüstung > Schutzmasken > Feuerwehr-Atemschutzmasken +Wirtschaft & Industrie > Arbeitsschutzausrüstung > Schutzmasken > Gas- & Atemschutzmasken +Wirtschaft & Industrie > Arbeitsschutzausrüstung > Schutzmasken > Medizinische Masken +Wirtschaft & Industrie > Arbeitsschutzausrüstung > Schutzmasken > Staubmasken +Wirtschaft & Industrie > Arbeitsschutzausrüstung > Schweißhelme Wirtschaft & Industrie > Arbeitsschutzausrüstung > Sicherheitsschürzen -Wirtschaft & Industrie > Arbeitsschutzausrüstung > Signalkleidung -Wirtschaft & Industrie > Arbeitsschutzausrüstung > Staubmasken +Wirtschaft & Industrie > Arbeitsschutzausrüstung > Sicherungsgurte Wirtschaft & Industrie > Aufbewahrungszubehör für industrielle Zwecke +Wirtschaft & Industrie > Automatisierungskomponenten +Wirtschaft & Industrie > Automatisierungskomponenten > Antriebssysteme mit variabler Frequenz & einstellbarer Drehzahl +Wirtschaft & Industrie > Automatisierungskomponenten > Speicherprogrammierbare Steuerungen Wirtschaft & Industrie > Baugewerbe +Wirtschaft & Industrie > Baugewerbe > Verkehrskegel Wirtschaft & Industrie > Baugewerbe > Vermessung Wirtschaft & Industrie > Bergbau & Steinbruch Wirtschaft & Industrie > Beschilderung +Wirtschaft & Industrie > Beschilderung > Digitale Schilder Wirtschaft & Industrie > Beschilderung > Einzelhandelsschilder Wirtschaft & Industrie > Beschilderung > Elektronische Schilder Wirtschaft & Industrie > Beschilderung > Elektronische Schilder > LED-Schilder @@ -6015,19 +5254,21 @@ Wirtschaft & Industrie > Beschilderung > Warnschilder Wirtschaft & Industrie > Beschilderung > Wegweiser Wirtschaft & Industrie > Beschilderung > Öffnungszeiten-Schilder Wirtschaft & Industrie > Einzelhandel +Wirtschaft & Industrie > Einzelhandel > Einkaufstüten aus Papier & Plastik Wirtschaft & Industrie > Einzelhandel > Geldhandhabung Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Geldscheinprüfgeräte Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Geldtaschen Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Geldwechsler Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Geldzählmaschinen für Münzen und Scheine +Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Ladenkassen & POS-Terminals Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Münzrollenpapier & Geldscheinbanderolen -Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Registrierkassen & Kassenzubehör -Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Registrierkassen & Kassenzubehör > Kassenschubladen -Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Registrierkassen & Kassenzubehör > Kreditkartenleser -Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Registrierkassen & Kassenzubehör > Unterschriftenpads -Wirtschaft & Industrie > Einzelhandel > Plastiktüten +Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Zubehör für Ladenkassen & POS-Terminals +Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Zubehör für Ladenkassen & POS-Terminals > Kassenschubladen +Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Zubehör für Ladenkassen & POS-Terminals > Kreditkartenterminals +Wirtschaft & Industrie > Einzelhandel > Geldhandhabung > Zubehör für Ladenkassen & POS-Terminals > Unterschriftenpads Wirtschaft & Industrie > Einzelhandel > Preisauszeichnungsgeräte Wirtschaft & Industrie > Einzelhandel > Präsentationsständer für Bekleidung +Wirtschaft & Industrie > Einzelhandel > Schaufensterdeko & -puppen Wirtschaft & Industrie > Einzelhandel > Schaufensterpuppen Wirtschaft & Industrie > Einzelhandel > Schaufensterpuppenteile Wirtschaft & Industrie > Einzelhandel > Vitrinen @@ -6036,63 +5277,77 @@ Wirtschaft & Industrie > Finanzwesen & Versicherung Wirtschaft & Industrie > Finanzwesen & Versicherung > Bullion Wirtschaft & Industrie > Forst- & Holzwirtschaft Wirtschaft & Industrie > Friseur- & Kosmetikgewerbe -Wirtschaft & Industrie > Friseur- & Kosmetikgewerbe > Pediküresessel +Wirtschaft & Industrie > Friseur- & Kosmetikgewerbe > Friseurumhänge & Halskrausen +Wirtschaft & Industrie > Friseur- & Kosmetikgewerbe > Pedikürestuhl Wirtschaft & Industrie > Friseur- & Kosmetikgewerbe > Salonstühle Wirtschaft & Industrie > Gewerbliche Lagerung Wirtschaft & Industrie > Gewerbliche Lagerung > Frachtcontainer Wirtschaft & Industrie > Gewerbliche Lagerung > Industrieregale Wirtschaft & Industrie > Gewerbliche Lagerung > Industrieschränke +Wirtschaft & Industrie > Gewerbliche Lagerung > Zäune, Einfriedungen & Türen Wirtschaft & Industrie > Herstellung Wirtschaft & Industrie > Hotels & Gaststätten Wirtschaft & Industrie > Landwirtschaft Wirtschaft & Industrie > Landwirtschaft > Viehhaltung -Wirtschaft & Industrie > Landwirtschaft > Viehhaltung > Hühnerställe +Wirtschaft & Industrie > Landwirtschaft > Viehhaltung > Brutapparate +Wirtschaft & Industrie > Landwirtschaft > Viehhaltung > Viehfutter Wirtschaft & Industrie > Landwirtschaft > Viehhaltung > Viehfutterspender +Wirtschaft & Industrie > Landwirtschaft > Viehhaltung > Viehhalfter Wirtschaft & Industrie > Materialbeförderung +Wirtschaft & Industrie > Materialbeförderung > Aufzüge & Hebeeinrichtungen +Wirtschaft & Industrie > Materialbeförderung > Aufzüge & Hebeeinrichtungen > Flaschenzüge & Rollen +Wirtschaft & Industrie > Materialbeförderung > Aufzüge & Hebeeinrichtungen > Hebezüge, Kräne & Laufkatzen +Wirtschaft & Industrie > Materialbeförderung > Aufzüge & Hebeeinrichtungen > Hubwagen +Wirtschaft & Industrie > Materialbeförderung > Aufzüge & Hebeeinrichtungen > Personenaufzüge +Wirtschaft & Industrie > Materialbeförderung > Aufzüge & Hebeeinrichtungen > Seilwinden Wirtschaft & Industrie > Materialbeförderung > Förderanlagen -Wirtschaft & Industrie > Materialbeförderung > Gabelstapler +Wirtschaft & Industrie > Materialbeförderung > Paletten & Ladeflächen Wirtschaft & Industrie > Medizinischer Bereich Wirtschaft & Industrie > Medizinischer Bereich > Krankenhaus-Vorhänge Wirtschaft & Industrie > Medizinischer Bereich > Krankenhausbekleidung Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Bettwäsche Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte > Automatisierte externe Defibrillatoren -Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte > Medizinische Zangen +Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte > Medizinische Hebegürtel Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte > Otoskope & Ophthalmoskope Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte > Patientenlifter Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte > Reflexhämmer -Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte > Skalpelle Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte > Stethoskope +Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte > Tragen Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte > Vitaldatenmonitore Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Geräte > Zubehör für Vitaldatenmonitore +Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Instrumente +Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Instrumente > Chirurgische Nadeln und Fäden +Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Instrumente > Medizinische Zangen +Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Instrumente > Skalpelle +Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Instrumente > Skalpellklingen Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Chiropraktische Tische Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Krankenhaus- und Pflegebetten Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Medikamentenaufbewahrung -Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Medikamentenaufbewahrung > Betäubungsmittelschränke Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Medizinische Wagen Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Medizinische Wagen > Infusionsständer & -wagen Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Medizinische Wagen > Reanimationswagen Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > OP-Tische -Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Untersuchungsstühle -Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Untersuchungsstühle > Gynäkologische Stühle -Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Untersuchungsstühle > Optometrische Stühle -Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Untersuchungsstühle > Zahnarztstühle +Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Möbel > Untersuchungsstühle & -tische Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Schulungsgeräte Wirtschaft & Industrie > Medizinischer Bereich > Medizinische Schulungsgeräte > HLW-Puppen Wirtschaft & Industrie > Medizinischer Bereich > Medizinischer Bedarf Wirtschaft & Industrie > Medizinischer Bereich > Medizinischer Bedarf > Einweghandschuhe Wirtschaft & Industrie > Medizinischer Bereich > Medizinischer Bedarf > Fingerlinge -Wirtschaft & Industrie > Medizinischer Bereich > Medizinischer Bedarf > Medizinische Masken -Wirtschaft & Industrie > Medizinischer Bereich > Medizinischer Bedarf > Zungenspatel +Wirtschaft & Industrie > Medizinischer Bereich > Medizinischer Bedarf > Mundspatel +Wirtschaft & Industrie > Medizinischer Bereich > Medizinischer Bedarf > Nadeln & Spritzen +Wirtschaft & Industrie > Medizinischer Bereich > Medizinischer Bedarf > Stomazubehör Wirtschaft & Industrie > Medizinischer Bereich > OP-Bekleidung Wirtschaft & Industrie > Medizinischer Bereich > OP-Hauben Wirtschaft & Industrie > Medizinischer Bereich > OP-Kittel Wirtschaft & Industrie > Nahrungsmittelservice +Wirtschaft & Industrie > Nahrungsmittelservice > Auslagenschutz Wirtschaft & Industrie > Nahrungsmittelservice > Backwarenbehälter Wirtschaft & Industrie > Nahrungsmittelservice > Einweg-Serviergeschirr Wirtschaft & Industrie > Nahrungsmittelservice > Einweg-Serviergeschirr > Einwegtabletts Wirtschaft & Industrie > Nahrungsmittelservice > Einwegbehälter +Wirtschaft & Industrie > Nahrungsmittelservice > Einwegdeckel Wirtschaft & Industrie > Nahrungsmittelservice > Einweggeschirr Wirtschaft & Industrie > Nahrungsmittelservice > Einweggeschirr > Einwegbecher Wirtschaft & Industrie > Nahrungsmittelservice > Einweggeschirr > Einwegbesteck @@ -6101,10 +5356,10 @@ Wirtschaft & Industrie > Nahrungsmittelservice > Einweggeschirr > Einwegteller Wirtschaft & Industrie > Nahrungsmittelservice > Eisbehälter Wirtschaft & Industrie > Nahrungsmittelservice > Gemüseschleudern Wirtschaft & Industrie > Nahrungsmittelservice > Hot-Dog-Grills -Wirtschaft & Industrie > Nahrungsmittelservice > Kippbare Industrietöpfe +Wirtschaft & Industrie > Nahrungsmittelservice > Kippbare Industriepfannen Wirtschaft & Industrie > Nahrungsmittelservice > Lebensmittelkörbe +Wirtschaft & Industrie > Nahrungsmittelservice > Scheck- & Kartenetuis Wirtschaft & Industrie > Nahrungsmittelservice > Servierwagen -Wirtschaft & Industrie > Nahrungsmittelservice > Spender für Einmalbesteck Wirtschaft & Industrie > Nahrungsmittelservice > Spülwannen Wirtschaft & Industrie > Nahrungsmittelservice > Takeaway-Behälter Wirtschaft & Industrie > Nahrungsmittelservice > Teller- & Speisenwärmer @@ -6117,15 +5372,12 @@ Wirtschaft & Industrie > Piercing & Tätowieren > Tätowierbedarf > Tattoo-Abdec Wirtschaft & Industrie > Piercing & Tätowieren > Tätowierbedarf > Tätowierfarben Wirtschaft & Industrie > Piercing & Tätowieren > Tätowierbedarf > Tätowiermaschinen Wirtschaft & Industrie > Piercing & Tätowieren > Tätowierbedarf > Tätowiernadeln +Wirtschaft & Industrie > Reinigungswagen Wirtschaft & Industrie > Schwermaschinen Wirtschaft & Industrie > Schwermaschinen > Häcksler -Wirtschaft & Industrie > Sicherheitssektor -Wirtschaft & Industrie > Sicherheitssektor > Metalldetektoren -Wirtschaft & Industrie > Sicherheitssektor > Schellen -Wirtschaft & Industrie > Sicherheitssektor > Schellen > Fußgelenkschellen -Wirtschaft & Industrie > Sicherheitssektor > Schellen > Fußschellen -Wirtschaft & Industrie > Sicherheitssektor > Schellen > Handschellen -Wirtschaft & Industrie > Sicherheitssektor > Schlagstöcke +Wirtschaft & Industrie > Strafverfolgung +Wirtschaft & Industrie > Strafverfolgung > Metalldetektoren +Wirtschaft & Industrie > Strafverfolgung > Schellen Wirtschaft & Industrie > Werbung & Marketing Wirtschaft & Industrie > Werbung & Marketing > Broschüren Wirtschaft & Industrie > Werbung & Marketing > Messestand-Displays @@ -6134,8 +5386,11 @@ Wirtschaft & Industrie > Wissenschaft & Labor Wirtschaft & Industrie > Wissenschaft & Labor > Biochemikalien Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Autoklaven +Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Gefriertrockenmaschinen +Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Labor-Gefrierschränke Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Labor-Heizplatten Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Labormixer +Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Labortrichter Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Laboröfen Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Mikroskope Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Mikroskopzubehör @@ -6146,7 +5401,6 @@ Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Mikroskopzube Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Mikroskopzubehör > Objektträger Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Mikrotome Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Spektrometer -Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Spektrometer > Spektralfotometer Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Trockeneismaschinen Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Zentrifugen Wirtschaft & Industrie > Wissenschaft & Labor > Laborausstattung > Zubehör für Spektrometer @@ -6160,9 +5414,14 @@ Wirtschaft & Industrie > Wissenschaft & Labor > Laborbedarf > Reagenzglasstände Wirtschaft & Industrie > Wissenschaft & Labor > Laborbedarf > Reagenzgläser Wirtschaft & Industrie > Wissenschaft & Labor > Laborbedarf > Spritzflaschen Wirtschaft & Industrie > Wissenschaft & Labor > Laborchemikalien +Wirtschaft & Industrie > Wissenschaft & Labor > Laborproben Wirtschaft & Industrie > Wissenschaft & Labor > Sezierinstrumente Wirtschaft & Industrie > Zahnmedizin +Wirtschaft & Industrie > Zahnmedizin > Prophy-Paste Wirtschaft & Industrie > Zahnmedizin > Zahnmedizinische Instrumente -Wirtschaft & Industrie > Zahnmedizin > Zahnmedizinische Instrumente > Zahnmedizinische Ausstattung -Wirtschaft & Industrie > Zahnmedizin > Zahnmedizinische Instrumente > Zahnmedizinische Spiegel -Wirtschaft & Industrie > Zahnmedizin > Zahnzement +Wirtschaft & Industrie > Zahnmedizin > Zahnmedizinische Instrumente > Dappengläser +Wirtschaft & Industrie > Zahnmedizin > Zahnmedizinische Instrumente > Dentalspiegel +Wirtschaft & Industrie > Zahnmedizin > Zahnmedizinische Instrumente > Prophy-Kelche +Wirtschaft & Industrie > Zahnmedizin > Zahnmedizinische Instrumente > Prophy-Köpfe +Wirtschaft & Industrie > Zahnmedizin > Zahnmedizinische Instrumente > Zahnmedizinische Instrumentensets +Wirtschaft & Industrie > Zahnmedizin > Zahnzement \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.en-US.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.en-US.txt index f7004f5fca..671672c564 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.en-US.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.en-US.txt @@ -1,9 +1,9 @@ - -Animals & Pet Supplies +Animals & Pet Supplies Animals & Pet Supplies > Live Animals Animals & Pet Supplies > Pet Supplies Animals & Pet Supplies > Pet Supplies > Bird Supplies Animals & Pet Supplies > Pet Supplies > Bird Supplies > Bird Cage Accessories +Animals & Pet Supplies > Pet Supplies > Bird Supplies > Bird Cage Accessories > Bird Cage Bird Baths Animals & Pet Supplies > Pet Supplies > Bird Supplies > Bird Cage Accessories > Bird Cage Food & Water Dishes Animals & Pet Supplies > Pet Supplies > Bird Supplies > Bird Cages & Stands Animals & Pet Supplies > Pet Supplies > Bird Supplies > Bird Food @@ -16,7 +16,9 @@ Animals & Pet Supplies > Pet Supplies > Cat Supplies > Cat Apparel Animals & Pet Supplies > Pet Supplies > Cat Supplies > Cat Beds Animals & Pet Supplies > Pet Supplies > Cat Supplies > Cat Food Animals & Pet Supplies > Pet Supplies > Cat Supplies > Cat Furniture +Animals & Pet Supplies > Pet Supplies > Cat Supplies > Cat Furniture Accessories Animals & Pet Supplies > Pet Supplies > Cat Supplies > Cat Litter +Animals & Pet Supplies > Pet Supplies > Cat Supplies > Cat Litter Box Liners Animals & Pet Supplies > Pet Supplies > Cat Supplies > Cat Litter Box Mats Animals & Pet Supplies > Pet Supplies > Cat Supplies > Cat Litter Boxes Animals & Pet Supplies > Pet Supplies > Cat Supplies > Cat Toys @@ -25,23 +27,26 @@ Animals & Pet Supplies > Pet Supplies > Dog Supplies Animals & Pet Supplies > Pet Supplies > Dog Supplies > Dog Apparel Animals & Pet Supplies > Pet Supplies > Dog Supplies > Dog Beds Animals & Pet Supplies > Pet Supplies > Dog Supplies > Dog Diaper Pads & Liners +Animals & Pet Supplies > Pet Supplies > Dog Supplies > Dog Diapers Animals & Pet Supplies > Pet Supplies > Dog Supplies > Dog Food Animals & Pet Supplies > Pet Supplies > Dog Supplies > Dog Houses Animals & Pet Supplies > Pet Supplies > Dog Supplies > Dog Kennel & Run Accessories Animals & Pet Supplies > Pet Supplies > Dog Supplies > Dog Kennels & Runs Animals & Pet Supplies > Pet Supplies > Dog Supplies > Dog Toys +Animals & Pet Supplies > Pet Supplies > Dog Supplies > Dog Treadmills Animals & Pet Supplies > Pet Supplies > Dog Supplies > Dog Treats Animals & Pet Supplies > Pet Supplies > Fish Supplies -Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Air Pumps +Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium & Pond Tubing +Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Air Stones & Diffusers +Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Cleaning Supplies Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Decor -Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Decor > Artificial Aquarium Plants Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Filters +Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Fish Nets Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Gravel & Substrates -Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Heating Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Lighting Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Overflow Boxes Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Stands -Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Water Pumps +Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Temperature Controllers Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquarium Water Treatments Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquariums Animals & Pet Supplies > Pet Supplies > Fish Supplies > Aquatic Plant Fertilizers @@ -49,57 +54,58 @@ Animals & Pet Supplies > Pet Supplies > Fish Supplies > Fish Feeders Animals & Pet Supplies > Pet Supplies > Fish Supplies > Fish Food Animals & Pet Supplies > Pet Supplies > Pet Agility Equipment Animals & Pet Supplies > Pet Supplies > Pet Apparel Hangers -Animals & Pet Supplies > Pet Supplies > Pet Attractant +Animals & Pet Supplies > Pet Supplies > Pet Bed Accessories Animals & Pet Supplies > Pet Supplies > Pet Bells & Charms Animals & Pet Supplies > Pet Supplies > Pet Biometric Monitors Animals & Pet Supplies > Pet Supplies > Pet Biometric Monitors > Pet Glucose Meters Animals & Pet Supplies > Pet Supplies > Pet Biometric Monitors > Pet Pedometers -Animals & Pet Supplies > Pet Supplies > Pet Biometric Monitors > Pet Scales Animals & Pet Supplies > Pet Supplies > Pet Biometric Monitors > Pet Thermometers Animals & Pet Supplies > Pet Supplies > Pet Bowl Mats +Animals & Pet Supplies > Pet Supplies > Pet Bowl Stands Animals & Pet Supplies > Pet Supplies > Pet Bowls, Feeders & Waterers -Animals & Pet Supplies > Pet Supplies > Pet Bowls, Feeders & Waterers > Cat Bowls, Feeders & Waterers -Animals & Pet Supplies > Pet Supplies > Pet Bowls, Feeders & Waterers > Dog Bowls, Feeders & Waterers -Animals & Pet Supplies > Pet Supplies > Pet Bowls, Feeders & Waterers > Small Animal Bowls, Feeders & Waterers +Animals & Pet Supplies > Pet Supplies > Pet Carrier & Crate Accessories Animals & Pet Supplies > Pet Supplies > Pet Carriers & Crates -Animals & Pet Supplies > Pet Supplies > Pet Carriers & Crates > Cat Carriers & Crates -Animals & Pet Supplies > Pet Supplies > Pet Carriers & Crates > Dog Carriers & Crates -Animals & Pet Supplies > Pet Supplies > Pet Carriers & Crates > Small Animal Carriers Animals & Pet Supplies > Pet Supplies > Pet Collars & Harnesses -Animals & Pet Supplies > Pet Supplies > Pet Collars & Harnesses > Cat Collars & Harnesses -Animals & Pet Supplies > Pet Supplies > Pet Collars & Harnesses > Dog Collars & Harnesses Animals & Pet Supplies > Pet Supplies > Pet Containment Systems +Animals & Pet Supplies > Pet Supplies > Pet Door Accessories +Animals & Pet Supplies > Pet Supplies > Pet Doors Animals & Pet Supplies > Pet Supplies > Pet Eye Drops & Lubricants +Animals & Pet Supplies > Pet Supplies > Pet First Aid & Emergency Kits Animals & Pet Supplies > Pet Supplies > Pet Flea & Tick Control -Animals & Pet Supplies > Pet Supplies > Pet Flea & Tick Control > Cat Flea & Tick Control -Animals & Pet Supplies > Pet Supplies > Pet Flea & Tick Control > Dog Flea & Tick Control Animals & Pet Supplies > Pet Supplies > Pet Food Containers Animals & Pet Supplies > Pet Supplies > Pet Food Scoops Animals & Pet Supplies > Pet Supplies > Pet Grooming Supplies Animals & Pet Supplies > Pet Supplies > Pet Grooming Supplies > Pet Combs & Brushes +Animals & Pet Supplies > Pet Supplies > Pet Grooming Supplies > Pet Fragrances & Deodorizing Sprays Animals & Pet Supplies > Pet Supplies > Pet Grooming Supplies > Pet Hair Clippers & Trimmers +Animals & Pet Supplies > Pet Supplies > Pet Grooming Supplies > Pet Hair Dryers Animals & Pet Supplies > Pet Supplies > Pet Grooming Supplies > Pet Nail Polish Animals & Pet Supplies > Pet Supplies > Pet Grooming Supplies > Pet Nail Tools Animals & Pet Supplies > Pet Supplies > Pet Grooming Supplies > Pet Shampoo & Conditioner +Animals & Pet Supplies > Pet Supplies > Pet Grooming Supplies > Pet Wipes +Animals & Pet Supplies > Pet Supplies > Pet Heating Pad Accessories +Animals & Pet Supplies > Pet Supplies > Pet Heating Pads Animals & Pet Supplies > Pet Supplies > Pet ID Tags Animals & Pet Supplies > Pet Supplies > Pet Leash Extensions Animals & Pet Supplies > Pet Supplies > Pet Leashes -Animals & Pet Supplies > Pet Supplies > Pet Leashes > Cat Leashes -Animals & Pet Supplies > Pet Supplies > Pet Leashes > Dog Leashes Animals & Pet Supplies > Pet Supplies > Pet Medical Collars Animals & Pet Supplies > Pet Supplies > Pet Medical Tape & Bandages Animals & Pet Supplies > Pet Supplies > Pet Medicine Animals & Pet Supplies > Pet Supplies > Pet Muzzles -Animals & Pet Supplies > Pet Supplies > Pet Oral Care -Animals & Pet Supplies > Pet Supplies > Pet Oral Care > Pet Breath Spray -Animals & Pet Supplies > Pet Supplies > Pet Oral Care > Pet Toothbrushes -Animals & Pet Supplies > Pet Supplies > Pet Oral Care > Pet Toothpaste +Animals & Pet Supplies > Pet Supplies > Pet Oral Care Supplies Animals & Pet Supplies > Pet Supplies > Pet Playpens Animals & Pet Supplies > Pet Supplies > Pet Steps & Ramps Animals & Pet Supplies > Pet Supplies > Pet Strollers Animals & Pet Supplies > Pet Supplies > Pet Sunscreen -Animals & Pet Supplies > Pet Supplies > Pet Training Pads +Animals & Pet Supplies > Pet Supplies > Pet Training Aids +Animals & Pet Supplies > Pet Supplies > Pet Training Aids > Pet Training Clickers & Treat Dispensers +Animals & Pet Supplies > Pet Supplies > Pet Training Aids > Pet Training Pad Holders +Animals & Pet Supplies > Pet Supplies > Pet Training Aids > Pet Training Pads +Animals & Pet Supplies > Pet Supplies > Pet Training Aids > Pet Training Sprays & Solutions Animals & Pet Supplies > Pet Supplies > Pet Vitamins & Supplements +Animals & Pet Supplies > Pet Supplies > Pet Waste Bag Dispensers & Holders +Animals & Pet Supplies > Pet Supplies > Pet Waste Bags +Animals & Pet Supplies > Pet Supplies > Pet Waste Disposal Systems & Tools Animals & Pet Supplies > Pet Supplies > Reptile & Amphibian Supplies Animals & Pet Supplies > Pet Supplies > Reptile & Amphibian Supplies > Reptile & Amphibian Food Animals & Pet Supplies > Pet Supplies > Reptile & Amphibian Supplies > Reptile & Amphibian Habitat Accessories @@ -112,68 +118,42 @@ Animals & Pet Supplies > Pet Supplies > Small Animal Supplies > Small Animal Foo Animals & Pet Supplies > Pet Supplies > Small Animal Supplies > Small Animal Habitat Accessories Animals & Pet Supplies > Pet Supplies > Small Animal Supplies > Small Animal Habitats & Cages Animals & Pet Supplies > Pet Supplies > Small Animal Supplies > Small Animal Treats +Animals & Pet Supplies > Pet Supplies > Vehicle Pet Barriers Apparel & Accessories Apparel & Accessories > Clothing Apparel & Accessories > Clothing > Activewear -Apparel & Accessories > Clothing > Activewear > Active Dresses -Apparel & Accessories > Clothing > Activewear > Active Jackets -Apparel & Accessories > Clothing > Activewear > Active Jackets > Windbreakers -Apparel & Accessories > Clothing > Activewear > Active Leotards -Apparel & Accessories > Clothing > Activewear > Active Pants -Apparel & Accessories > Clothing > Activewear > Active Shirts -Apparel & Accessories > Clothing > Activewear > Active Shorts -Apparel & Accessories > Clothing > Activewear > Active Skirts -Apparel & Accessories > Clothing > Activewear > Active Tanks -Apparel & Accessories > Clothing > Activewear > Active Undergarments -Apparel & Accessories > Clothing > Activewear > Active Undergarments > Active Underwear -Apparel & Accessories > Clothing > Activewear > Active Undergarments > Compression Undergarment Shorts -Apparel & Accessories > Clothing > Activewear > Active Undergarments > Jock Straps -Apparel & Accessories > Clothing > Activewear > Active Undergarments > Sports Bras -Apparel & Accessories > Clothing > Activewear > Activewear Sets Apparel & Accessories > Clothing > Activewear > Bicycle Activewear Apparel & Accessories > Clothing > Activewear > Bicycle Activewear > Bicycle Bibs Apparel & Accessories > Clothing > Activewear > Bicycle Activewear > Bicycle Jerseys -Apparel & Accessories > Clothing > Activewear > Bicycle Activewear > Bicycle Shorts +Apparel & Accessories > Clothing > Activewear > Bicycle Activewear > Bicycle Shorts & Briefs Apparel & Accessories > Clothing > Activewear > Bicycle Activewear > Bicycle Skinsuits Apparel & Accessories > Clothing > Activewear > Bicycle Activewear > Bicycle Tights Apparel & Accessories > Clothing > Activewear > Boxing Shorts -Apparel & Accessories > Clothing > Activewear > Dancewear -Apparel & Accessories > Clothing > Activewear > Dancewear > Dance Dresses -Apparel & Accessories > Clothing > Activewear > Dancewear > Dance Pants -Apparel & Accessories > Clothing > Activewear > Dancewear > Dance Skirts -Apparel & Accessories > Clothing > Activewear > Dancewear > Dance Tights -Apparel & Accessories > Clothing > Activewear > Dancewear > Dance Tops +Apparel & Accessories > Clothing > Activewear > Dance Dresses, Skirts & Costumes Apparel & Accessories > Clothing > Activewear > Football Pants Apparel & Accessories > Clothing > Activewear > Hunting Clothing Apparel & Accessories > Clothing > Activewear > Hunting Clothing > Ghillie Suits +Apparel & Accessories > Clothing > Activewear > Hunting Clothing > Hunting & Fishing Vests Apparel & Accessories > Clothing > Activewear > Hunting Clothing > Hunting & Tactical Pants -Apparel & Accessories > Clothing > Activewear > Hunting Clothing > Hunting & Tactical Vests -Apparel & Accessories > Clothing > Activewear > Long Johns Apparel & Accessories > Clothing > Activewear > Martial Arts Shorts Apparel & Accessories > Clothing > Activewear > Motorcycle Protective Clothing Apparel & Accessories > Clothing > Activewear > Motorcycle Protective Clothing > Motorcycle Jackets Apparel & Accessories > Clothing > Activewear > Motorcycle Protective Clothing > Motorcycle Pants Apparel & Accessories > Clothing > Activewear > Motorcycle Protective Clothing > Motorcycle Suits Apparel & Accessories > Clothing > Activewear > Paintball Clothing -Apparel & Accessories > Clothing > Activewear > Sauna Suits -Apparel & Accessories > Clothing > Activewear > Snow Pants & Suits Apparel & Accessories > Clothing > Baby & Toddler Clothing Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby & Toddler Bottoms Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby & Toddler Diaper Covers Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby & Toddler Dresses Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby & Toddler Outerwear Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby & Toddler Outfits +Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby & Toddler Sleepwear Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby & Toddler Socks & Tights Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby & Toddler Swimwear Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby & Toddler Tops -Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby Onesies -Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby Sleepwear & Swaddlers +Apparel & Accessories > Clothing > Baby & Toddler Clothing > Baby One-Pieces Apparel & Accessories > Clothing > Baby & Toddler Clothing > Toddler Underwear Apparel & Accessories > Clothing > Dresses -Apparel & Accessories > Clothing > Dresses > Cocktail Dresses -Apparel & Accessories > Clothing > Dresses > Day Dresses -Apparel & Accessories > Clothing > Dresses > Formal Gowns -Apparel & Accessories > Clothing > Dresses > Little Black Dresses Apparel & Accessories > Clothing > One-Pieces Apparel & Accessories > Clothing > One-Pieces > Jumpsuits & Rompers Apparel & Accessories > Clothing > One-Pieces > Leotards & Unitards @@ -181,46 +161,14 @@ Apparel & Accessories > Clothing > One-Pieces > Overalls Apparel & Accessories > Clothing > Outerwear Apparel & Accessories > Clothing > Outerwear > Chaps Apparel & Accessories > Clothing > Outerwear > Coats & Jackets -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Anorak Jackets -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Blazers -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Boleros -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Capes & Ponchos -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Denim Jackets -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Down & Snow Jackets -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Fleece Jackets -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Fur Coats -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Leather & Suede Jackets -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Overcoats -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Trench Coats -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Tweed Jackets -Apparel & Accessories > Clothing > Outerwear > Coats & Jackets > Varsity Jackets -Apparel & Accessories > Clothing > Outerwear > Rain Gear -Apparel & Accessories > Clothing > Outerwear > Rain Gear > Rain Pants -Apparel & Accessories > Clothing > Outerwear > Rain Gear > Rain Suits -Apparel & Accessories > Clothing > Outerwear > Rain Gear > Raincoats +Apparel & Accessories > Clothing > Outerwear > Rain Pants +Apparel & Accessories > Clothing > Outerwear > Rain Suits +Apparel & Accessories > Clothing > Outerwear > Snow Pants & Suits Apparel & Accessories > Clothing > Outerwear > Vests Apparel & Accessories > Clothing > Outfit Sets Apparel & Accessories > Clothing > Pants -Apparel & Accessories > Clothing > Pants > Cargo Pants -Apparel & Accessories > Clothing > Pants > Corduroy & Velvet Pants -Apparel & Accessories > Clothing > Pants > Dress Pants -Apparel & Accessories > Clothing > Pants > Jeans -Apparel & Accessories > Clothing > Pants > Khaki & Chino Pants -Apparel & Accessories > Clothing > Pants > Leather & Suede Pants -Apparel & Accessories > Clothing > Pants > Leggings -Apparel & Accessories > Clothing > Pants > Linen Pants Apparel & Accessories > Clothing > Shirts & Tops -Apparel & Accessories > Clothing > Shirts & Tops > Camisoles & Tank Tops -Apparel & Accessories > Clothing > Shirts & Tops > Polo Shirts -Apparel & Accessories > Clothing > Shirts & Tops > Shirts & Blouses -Apparel & Accessories > Clothing > Shirts & Tops > Sports Fan Jerseys -Apparel & Accessories > Clothing > Shirts & Tops > Sweaters & Cardigans -Apparel & Accessories > Clothing > Shirts & Tops > Sweatshirts -Apparel & Accessories > Clothing > Shirts & Tops > T-Shirts Apparel & Accessories > Clothing > Shorts -Apparel & Accessories > Clothing > Shorts > Bermuda Shorts -Apparel & Accessories > Clothing > Shorts > Cargo Shorts -Apparel & Accessories > Clothing > Shorts > Short Shorts Apparel & Accessories > Clothing > Skirts Apparel & Accessories > Clothing > Skorts Apparel & Accessories > Clothing > Sleepwear & Loungewear @@ -231,31 +179,17 @@ Apparel & Accessories > Clothing > Sleepwear & Loungewear > Robes Apparel & Accessories > Clothing > Suits Apparel & Accessories > Clothing > Suits > Pant Suits Apparel & Accessories > Clothing > Suits > Skirt Suits -Apparel & Accessories > Clothing > Suits > Suit Separates Apparel & Accessories > Clothing > Suits > Tuxedos Apparel & Accessories > Clothing > Swimwear -Apparel & Accessories > Clothing > Swimwear > Bikinis -Apparel & Accessories > Clothing > Swimwear > Cover-Ups -Apparel & Accessories > Clothing > Swimwear > One-Piece Swimsuits -Apparel & Accessories > Clothing > Swimwear > Swim Briefs -Apparel & Accessories > Clothing > Swimwear > Swim Shorts -Apparel & Accessories > Clothing > Swimwear > Swimsuit Bottoms -Apparel & Accessories > Clothing > Swimwear > Swimsuit Tops -Apparel & Accessories > Clothing > Swimwear > Tankinis & Extra Coverage Swimwear Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Dirndls Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Hakama Trousers +Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Japanese Black Formal Wear Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Kimono Outerwear -Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Kimono Outerwear > Haori Jackets -Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Kimono Outerwear > Kimono Coats Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Kimonos -Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Kimonos > Bridal Kimonos -Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Kimonos > Casual Kimonos -Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Kimonos > Furisode Kimonos -Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Kimonos > Iromuji Kimonos -Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Kimonos > Komon Kimonos -Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Kimonos > Tomesode & Houmongi Kimonos Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Religious Ceremonial Clothing +Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Religious Ceremonial Clothing > Baptism & Communion Dresses +Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Saris & Lehengas Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Traditional Leather Pants Apparel & Accessories > Clothing > Traditional & Ceremonial Clothing > Yukata Apparel & Accessories > Clothing > Underwear & Socks @@ -265,13 +199,13 @@ Apparel & Accessories > Clothing > Underwear & Socks > Bra Accessories > Bra Str Apparel & Accessories > Clothing > Underwear & Socks > Bra Accessories > Breast Enhancing Inserts Apparel & Accessories > Clothing > Underwear & Socks > Bra Accessories > Breast Petals & Concealers Apparel & Accessories > Clothing > Underwear & Socks > Bras -Apparel & Accessories > Clothing > Underwear & Socks > Bras > Nursing Bras -Apparel & Accessories > Clothing > Underwear & Socks > Bridal Intimates Apparel & Accessories > Clothing > Underwear & Socks > Hosiery +Apparel & Accessories > Clothing > Underwear & Socks > Jock Straps Apparel & Accessories > Clothing > Underwear & Socks > Lingerie Apparel & Accessories > Clothing > Underwear & Socks > Lingerie Accessories Apparel & Accessories > Clothing > Underwear & Socks > Lingerie Accessories > Garter Belts Apparel & Accessories > Clothing > Underwear & Socks > Lingerie Accessories > Garters +Apparel & Accessories > Clothing > Underwear & Socks > Long Johns Apparel & Accessories > Clothing > Underwear & Socks > Petticoats & Pettipants Apparel & Accessories > Clothing > Underwear & Socks > Shapewear Apparel & Accessories > Clothing > Underwear & Socks > Socks @@ -305,9 +239,7 @@ Apparel & Accessories > Clothing > Wedding & Bridal Party Dresses Apparel & Accessories > Clothing > Wedding & Bridal Party Dresses > Bridal Party Dresses Apparel & Accessories > Clothing > Wedding & Bridal Party Dresses > Wedding Dresses Apparel & Accessories > Clothing Accessories -Apparel & Accessories > Clothing Accessories > Arm & Leg Warmers -Apparel & Accessories > Clothing Accessories > Arm & Leg Warmers > Arm Warmers -Apparel & Accessories > Clothing Accessories > Arm & Leg Warmers > Leg Warmers +Apparel & Accessories > Clothing Accessories > Arm Warmers & Sleeves Apparel & Accessories > Clothing Accessories > Baby & Toddler Clothing Accessories Apparel & Accessories > Clothing Accessories > Baby & Toddler Clothing Accessories > Baby & Toddler Belts Apparel & Accessories > Clothing Accessories > Baby & Toddler Clothing Accessories > Baby & Toddler Gloves & Mittens @@ -322,75 +254,59 @@ Apparel & Accessories > Clothing Accessories > Bridal Accessories > Bridal Veils Apparel & Accessories > Clothing Accessories > Button Studs Apparel & Accessories > Clothing Accessories > Collar Stays Apparel & Accessories > Clothing Accessories > Cufflinks +Apparel & Accessories > Clothing Accessories > Decorative Fans Apparel & Accessories > Clothing Accessories > Earmuffs -Apparel & Accessories > Clothing Accessories > Folding Fans Apparel & Accessories > Clothing Accessories > Gloves & Mittens -Apparel & Accessories > Clothing Accessories > Gloves & Mittens > Formal Gloves -Apparel & Accessories > Clothing Accessories > Gloves & Mittens > Gloves -Apparel & Accessories > Clothing Accessories > Gloves & Mittens > Mittens -Apparel & Accessories > Clothing Accessories > Gloves & Mittens > Ski & Snowboard Gloves Apparel & Accessories > Clothing Accessories > Hair Accessories -Apparel & Accessories > Clothing Accessories > Hair Accessories > Hair Claws +Apparel & Accessories > Clothing Accessories > Hair Accessories > Hair Bun & Volume Shapers Apparel & Accessories > Clothing Accessories > Hair Accessories > Hair Combs Apparel & Accessories > Clothing Accessories > Hair Accessories > Hair Extensions Apparel & Accessories > Clothing Accessories > Hair Accessories > Hair Forks & Sticks Apparel & Accessories > Clothing Accessories > Hair Accessories > Hair Nets -Apparel & Accessories > Clothing Accessories > Hair Accessories > Hair Pins & Clips +Apparel & Accessories > Clothing Accessories > Hair Accessories > Hair Pins, Claws & Clips Apparel & Accessories > Clothing Accessories > Hair Accessories > Hair Wreaths Apparel & Accessories > Clothing Accessories > Hair Accessories > Headbands Apparel & Accessories > Clothing Accessories > Hair Accessories > Ponytail Holders Apparel & Accessories > Clothing Accessories > Hair Accessories > Tiaras +Apparel & Accessories > Clothing Accessories > Hair Accessories > Wig Accessories +Apparel & Accessories > Clothing Accessories > Hair Accessories > Wig Accessories > Wig Caps +Apparel & Accessories > Clothing Accessories > Hair Accessories > Wig Accessories > Wig Glue & Tape +Apparel & Accessories > Clothing Accessories > Hair Accessories > Wigs Apparel & Accessories > Clothing Accessories > Hand Muffs Apparel & Accessories > Clothing Accessories > Handkerchiefs Apparel & Accessories > Clothing Accessories > Hats -Apparel & Accessories > Clothing Accessories > Hats > Beanies -Apparel & Accessories > Clothing Accessories > Hats > Berets -Apparel & Accessories > Clothing Accessories > Hats > Bowler & Derby Hats -Apparel & Accessories > Clothing Accessories > Hats > Caps -Apparel & Accessories > Clothing Accessories > Hats > Caps > Baseball Caps -Apparel & Accessories > Clothing Accessories > Hats > Caps > Newsboy Caps -Apparel & Accessories > Clothing Accessories > Hats > Cowboys Hats -Apparel & Accessories > Clothing Accessories > Hats > Fedoras -Apparel & Accessories > Clothing Accessories > Hats > Sun Hats -Apparel & Accessories > Clothing Accessories > Hats > Umbrella Hats -Apparel & Accessories > Clothing Accessories > Hats > Visors Apparel & Accessories > Clothing Accessories > Headwear Apparel & Accessories > Clothing Accessories > Headwear > Fascinators Apparel & Accessories > Clothing Accessories > Headwear > Headdresses Apparel & Accessories > Clothing Accessories > Headwear > Turbans +Apparel & Accessories > Clothing Accessories > Leg Warmers Apparel & Accessories > Clothing Accessories > Leis +Apparel & Accessories > Clothing Accessories > Maternity Belts & Support Bands Apparel & Accessories > Clothing Accessories > Neck Gaiters Apparel & Accessories > Clothing Accessories > Neckties -Apparel & Accessories > Clothing Accessories > Neckties > Ascot Ties -Apparel & Accessories > Clothing Accessories > Neckties > Bolo Ties -Apparel & Accessories > Clothing Accessories > Neckties > Bow Ties -Apparel & Accessories > Clothing Accessories > Neckties > Ties Apparel & Accessories > Clothing Accessories > Pinback Buttons +Apparel & Accessories > Clothing Accessories > Sashes Apparel & Accessories > Clothing Accessories > Scarves & Shawls Apparel & Accessories > Clothing Accessories > Sunglasses Apparel & Accessories > Clothing Accessories > Suspenders Apparel & Accessories > Clothing Accessories > Tie Clips Apparel & Accessories > Clothing Accessories > Traditional Clothing Accessories -Apparel & Accessories > Clothing Accessories > Traditional Clothing Accessories > Kimono Underclothes -Apparel & Accessories > Clothing Accessories > Traditional Clothing Accessories > Obi Accessories Apparel & Accessories > Clothing Accessories > Traditional Clothing Accessories > Obis Apparel & Accessories > Clothing Accessories > Traditional Clothing Accessories > Tabi Socks -Apparel & Accessories > Clothing Accessories > Veils Apparel & Accessories > Clothing Accessories > Wristbands Apparel & Accessories > Costumes & Accessories Apparel & Accessories > Costumes & Accessories > Costume Accessories Apparel & Accessories > Costumes & Accessories > Costume Accessories > Bald Caps Apparel & Accessories > Costumes & Accessories > Costume Accessories > Costume Accessory Sets Apparel & Accessories > Costumes & Accessories > Costume Accessories > Costume Capes +Apparel & Accessories > Costumes & Accessories > Costume Accessories > Costume Gloves Apparel & Accessories > Costumes & Accessories > Costume Accessories > Costume Hats +Apparel & Accessories > Costumes & Accessories > Costume Accessories > Costume Special Effects +Apparel & Accessories > Costumes & Accessories > Costume Accessories > Costume Tobacco Products Apparel & Accessories > Costumes & Accessories > Costume Accessories > Pretend Jewelry Apparel & Accessories > Costumes & Accessories > Costume Shoes Apparel & Accessories > Costumes & Accessories > Costumes Apparel & Accessories > Costumes & Accessories > Masks -Apparel & Accessories > Costumes & Accessories > Wig Accessories -Apparel & Accessories > Costumes & Accessories > Wig Accessories > Wig Caps -Apparel & Accessories > Costumes & Accessories > Wig Accessories > Wig Glue -Apparel & Accessories > Costumes & Accessories > Wigs Apparel & Accessories > Handbag & Wallet Accessories Apparel & Accessories > Handbag & Wallet Accessories > Checkbook Covers Apparel & Accessories > Handbag & Wallet Accessories > Keychains @@ -400,13 +316,6 @@ Apparel & Accessories > Handbags, Wallets & Cases Apparel & Accessories > Handbags, Wallets & Cases > Badge & Pass Holders Apparel & Accessories > Handbags, Wallets & Cases > Business Card Cases Apparel & Accessories > Handbags, Wallets & Cases > Handbags -Apparel & Accessories > Handbags, Wallets & Cases > Handbags > Clutches & Special Occasion Bags -Apparel & Accessories > Handbags, Wallets & Cases > Handbags > Cross-Body Handbags -Apparel & Accessories > Handbags, Wallets & Cases > Handbags > Hobo Handbags -Apparel & Accessories > Handbags, Wallets & Cases > Handbags > Satchels -Apparel & Accessories > Handbags, Wallets & Cases > Handbags > Shoulder Bags -Apparel & Accessories > Handbags, Wallets & Cases > Handbags > Tote Handbags -Apparel & Accessories > Handbags, Wallets & Cases > Handbags > Wristlets Apparel & Accessories > Handbags, Wallets & Cases > Wallets & Money Clips Apparel & Accessories > Jewelry Apparel & Accessories > Jewelry > Anklets @@ -416,8 +325,6 @@ Apparel & Accessories > Jewelry > Brooches & Lapel Pins Apparel & Accessories > Jewelry > Charms & Pendants Apparel & Accessories > Jewelry > Earrings Apparel & Accessories > Jewelry > Jewelry Sets -Apparel & Accessories > Jewelry > Loose Stones -Apparel & Accessories > Jewelry > Loose Stones > Diamonds Apparel & Accessories > Jewelry > Necklaces Apparel & Accessories > Jewelry > Rings Apparel & Accessories > Jewelry > Watch Accessories @@ -425,8 +332,6 @@ Apparel & Accessories > Jewelry > Watch Accessories > Watch Bands Apparel & Accessories > Jewelry > Watch Accessories > Watch Stickers & Decals Apparel & Accessories > Jewelry > Watch Accessories > Watch Winders Apparel & Accessories > Jewelry > Watches -Apparel & Accessories > Jewelry > Watches > Analog Watches -Apparel & Accessories > Jewelry > Watches > Digital Watches Apparel & Accessories > Shoe Accessories Apparel & Accessories > Shoe Accessories > Boot Liners Apparel & Accessories > Shoe Accessories > Gaiters @@ -434,87 +339,180 @@ Apparel & Accessories > Shoe Accessories > Shoe Covers Apparel & Accessories > Shoe Accessories > Shoelaces Apparel & Accessories > Shoe Accessories > Spurs Apparel & Accessories > Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Ballet & Dance Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Basketball Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Bowling Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Boxing Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Cheerleading Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Cycling Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Golf Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Indoor Court Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Indoor Court Shoes > Badminton Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Indoor Court Shoes > Handball Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Indoor Court Shoes > Volleyball Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Indoor Soccer Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Skateboarding Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Table Tennis Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Tennis Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Weightlifting Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Athletic Shoes > Wrestling Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Cleats -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Cleats > Baseball & Softball Cleats -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Cleats > Football Cleats -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Cleats > Lacrosse Cleats -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Cleats > Rugby Boots -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Cleats > Soccer Cleats -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Cleats > Track Spikes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Sneakers -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Sneakers > Cross Training Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Sneakers > Fashion Sneakers -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Sneakers > Running Shoes -Apparel & Accessories > Shoes > Athletic Shoes & Sneakers > Sneakers > Walking Shoes -Apparel & Accessories > Shoes > Baby & Toddler Shoes -Apparel & Accessories > Shoes > Boots -Apparel & Accessories > Shoes > Boots > Cowboy Boots -Apparel & Accessories > Shoes > Boots > Desert & Chukka Boots -Apparel & Accessories > Shoes > Boots > Dress Boots -Apparel & Accessories > Shoes > Boots > Motorcycle Boots -Apparel & Accessories > Shoes > Boots > Rain Boots -Apparel & Accessories > Shoes > Boots > Riding Boots -Apparel & Accessories > Shoes > Boots > Shearling & Fur Trimmed Boots -Apparel & Accessories > Shoes > Boots > Snow Boots -Apparel & Accessories > Shoes > Boots > Work & Safety Boots -Apparel & Accessories > Shoes > Clogs & Mules -Apparel & Accessories > Shoes > Dress Shoes -Apparel & Accessories > Shoes > Dress Shoes > Formal Shoes -Apparel & Accessories > Shoes > Dress Shoes > Oxford Shoes -Apparel & Accessories > Shoes > Dress Shoes > Pumps -Apparel & Accessories > Shoes > Dress Shoes > Pumps > Basic Pumps -Apparel & Accessories > Shoes > Dress Shoes > Pumps > D'orsay & Two-Piece Pumps -Apparel & Accessories > Shoes > Dress Shoes > Pumps > Spectator Pumps -Apparel & Accessories > Shoes > Espadrilles -Apparel & Accessories > Shoes > Flats -Apparel & Accessories > Shoes > Loafers & Slip-Ons -Apparel & Accessories > Shoes > Moccasins -Apparel & Accessories > Shoes > Outdoor Shoes -Apparel & Accessories > Shoes > Outdoor Shoes > Climbing Shoes -Apparel & Accessories > Shoes > Outdoor Shoes > Fishing & Hunting Shoes -Apparel & Accessories > Shoes > Outdoor Shoes > Gardening Shoes -Apparel & Accessories > Shoes > Outdoor Shoes > Hiking Boots & Shoes -Apparel & Accessories > Shoes > Outdoor Shoes > Performance Boating Shoes -Apparel & Accessories > Shoes > Outdoor Shoes > Water Shoes -Apparel & Accessories > Shoes > Sandals -Apparel & Accessories > Shoes > Sandals > Fisherman Sandals -Apparel & Accessories > Shoes > Sandals > Slide Sandals -Apparel & Accessories > Shoes > Sandals > Thongs & Flip-Flops -Apparel & Accessories > Shoes > Slippers Arts & Entertainment +Arts & Entertainment > Event Tickets Arts & Entertainment > Hobbies & Creative Arts -Arts & Entertainment > Hobbies & Creative Arts > Artwork -Arts & Entertainment > Hobbies & Creative Arts > Artwork > Cardboard Cutouts -Arts & Entertainment > Hobbies & Creative Arts > Artwork > Drawings & Illustrations -Arts & Entertainment > Hobbies & Creative Arts > Artwork > Mixed Media Art -Arts & Entertainment > Hobbies & Creative Arts > Artwork > Paintings -Arts & Entertainment > Hobbies & Creative Arts > Artwork > Photographs -Arts & Entertainment > Hobbies & Creative Arts > Artwork > Photographs > Stock Photographs -Arts & Entertainment > Hobbies & Creative Arts > Artwork > Posters -Arts & Entertainment > Hobbies & Creative Arts > Artwork > Sculptures & Statues +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Craft Kits +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Craft Kits > Candle Making Kits +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Craft Kits > Drawing & Painting Kits +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Craft Kits > Fabric Repair Kits +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Craft Kits > Incense Making Kits +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Craft Kits > Jewelry Making Kits +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Craft Kits > Mosaic Kits +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Craft Kits > Needlecraft Kits +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Craft Kits > Scrapbooking & Stamping Kits +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Craft Kits > Toy Craft Kits +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Art & Craft Paper +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Art & Craft Paper > Cardstock & Scrapbooking Paper +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Art & Craft Paper > Construction Paper +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Art & Craft Paper > Craft Foil +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Art & Craft Paper > Drawing & Painting Paper +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Art & Craft Paper > Origami Paper +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Art & Craft Paper > Transfer Paper +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Art & Craft Paper > Vellum Paper +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Fasteners & Closures +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Fasteners & Closures > Buttons & Snaps +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Fasteners & Closures > Clasps & Hooks +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Fasteners & Closures > Eyelets & Grommets +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Fasteners & Closures > Hook and Loop Fasteners +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Fasteners & Closures > Zipper Pulls +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Fasteners & Closures > Zippers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Paint, Ink & Glaze +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Paint, Ink & Glaze > Art & Craft Paint +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Paint, Ink & Glaze > Art Fixatives +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Paint, Ink & Glaze > Art Ink +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Paint, Ink & Glaze > Ceramic & Pottery Glazes +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Paint, Ink & Glaze > Craft Dyes +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Paint, Ink & Glaze > Ink Pads +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Paint, Ink & Glaze > Paint Mediums +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Shapes & Bases +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Shapes & Bases > Craft Foam & Styrofoam +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Shapes & Bases > Craft Wood & Shapes +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Shapes & Bases > Papier Mache Shapes +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Craft Shapes & Bases > Wreath & Floral Frames +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Adhesives & Magnets +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Adhesives & Magnets > Craft & Office Glue +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Adhesives & Magnets > Craft Magnets +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Adhesives & Magnets > Decorative Tape +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Adhesives & Magnets > Floral Tape +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Adhesives & Magnets > Fusible Tape +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Fibers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Fibers > Jewelry & Beading Cord +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Fibers > Thread & Floss +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Fibers > Unspun Fiber +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Fibers > Yarn +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Wire +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Wire > Craft Pipe Cleaners +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Wire > Floral Wire +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Crafting Wire > Jewelry & Beading Wire +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Appliques & Patches +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Beads +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Bows & Yo-Yos +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Decorative Stickers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Elastic +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Feathers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Jewelry Findings +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Loose Stones +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Rhinestones & Flatbacks +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Ribbons & Trim +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Sequins & Glitter +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embellishments & Trims > Sew-in Labels +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Embossing Powder +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Filling & Padding Material +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Filling & Padding Material > Batting & Stuffing +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Filling & Padding Material > Filling Pellets +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Filling & Padding Material > Pillow Forms +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Leather & Vinyl +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Pottery & Sculpting Materials +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Pottery & Sculpting Materials > Clay & Modeling Dough +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Pottery & Sculpting Materials > Papier Mache Mixes +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Pottery & Sculpting Materials > Plaster Gauze +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Pottery & Sculpting Materials > Pottery Slips +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Raw Candle Wax +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Textiles +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Textiles > Crafting Canvas +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Textiles > Crafting Canvas > Needlecraft Canvas +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Textiles > Crafting Canvas > Painting Canvas +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Textiles > Crafting Canvas > Plastic Canvas +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Textiles > Fabric +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Textiles > Interfacing +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Textiles > Printable Fabric +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Wick Tabs +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Materials > Wicks +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tool Accessories +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tool Accessories > Craft Knife Blades +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tool Accessories > Craft Machine Cases & Covers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tool Accessories > Sewing Machine Extension Tables +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tool Accessories > Sewing Machine Feet +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tool Accessories > Sewing Machine Replacement Parts +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tool Accessories > Spinning Wheel Accessories +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tool Accessories > Stamp Blocks +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Blocking Mats +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Blocking Wires +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Color Mixing Tools +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Color Mixing Tools > Palette Knives +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Color Mixing Tools > Palettes +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Cutting & Embossing Tools +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Cutting & Embossing Tools > Craft & Office Scissors +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Cutting & Embossing Tools > Craft Cutters & Embossers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Cutting & Embossing Tools > Craft Knives +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Cutting & Embossing Tools > Craft Scoring Tools +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Cutting & Embossing Tools > Embossing Heat Tools +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Cutting & Embossing Tools > Embossing Pens & Styluses +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Cutting & Embossing Tools > Seam Rippers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Cutting & Embossing Tools > Thread & Yarn Cutters +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Decoration Makers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Measuring & Marking Tools +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Measuring & Marking Tools > Art Brushes +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Measuring & Marking Tools > Brayer Rollers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Measuring & Marking Tools > Decorative Stamps +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Measuring & Marking Tools > Drafting Compasses +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Measuring & Marking Tools > Screen Printing Squeegees +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Measuring & Marking Tools > Stencil Machines +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Measuring & Marking Tools > Stencils & Die Cuts +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Measuring & Marking Tools > Stitch Markers & Counters +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Measuring & Marking Tools > Textile Art Gauges & Rulers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Craft Measuring & Marking Tools > Wood Burning Tools +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Cutting Mats +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Dress Forms +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Felting Pads & Mats +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Frames, Hoops & Stretchers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Glue Guns +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Light Boxes +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Needles & Hooks +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Needles & Hooks > Crochet Hooks +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Needles & Hooks > Hand-Sewing Needles +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Needles & Hooks > Knitting Needles +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Needles & Hooks > Latch & Locker Hooks +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Needles & Hooks > Sewing Machine Needles +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Safety Pins +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Straight Pins +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Textile Craft Machines +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Textile Craft Machines > Felting Needles & Machines +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Textile Craft Machines > Hand Looms +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Textile Craft Machines > Mechanical Looms +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Textile Craft Machines > Sewing Machines +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Textile Craft Machines > Spinning Wheels +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Thimbles & Sewing Palms +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Thread & Yarn Tools +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Thread & Yarn Tools > Fiber Cards & Brushes +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Thread & Yarn Tools > Hand Spindles +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Thread & Yarn Tools > Needle Threaders +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Thread & Yarn Tools > Thread & Yarn Guides +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Thread & Yarn Tools > Thread & Yarn Spools +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Thread & Yarn Tools > Thread, Yarn & Bobbin Winders +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Thread & Yarn Tools > Weaving Beaters +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Art & Crafting Tools > Thread & Yarn Tools > Weaving Shuttles +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Craft Organization +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Craft Organization > Needle, Pin & Hook Organizers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Craft Organization > Sewing Baskets & Kits +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Craft Organization > Thread & Yarn Organizers +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Crafting Patterns & Molds +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Crafting Patterns & Molds > Beading Patterns +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Crafting Patterns & Molds > Craft Molds +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Crafting Patterns & Molds > Felting Molds +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Crafting Patterns & Molds > Needlecraft Patterns +Arts & Entertainment > Hobbies & Creative Arts > Arts & Crafts > Crafting Patterns & Molds > Sewing Patterns Arts & Entertainment > Hobbies & Creative Arts > Collectibles Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Autographs Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Collectible Coins & Currency +Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Collectible Trading Cards Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Collectible Weapons +Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Collectible Weapons > Collectible Guns Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Collectible Weapons > Collectible Knives Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Collectible Weapons > Collectible Swords Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Collectible Weapons > Sword Stands & Displays @@ -528,6 +526,7 @@ Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectib Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectibles > Autographed Sports Paraphernalia > Auto Racing Autographed Paraphernalia Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectibles > Autographed Sports Paraphernalia > Baseball & Softball Autographed Paraphernalia Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectibles > Autographed Sports Paraphernalia > Basketball Autographed Paraphernalia +Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectibles > Autographed Sports Paraphernalia > Boxing Autographed Paraphernalia Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectibles > Autographed Sports Paraphernalia > Football Autographed Paraphernalia Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectibles > Autographed Sports Paraphernalia > Hockey Autographed Paraphernalia Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectibles > Autographed Sports Paraphernalia > Soccer Autographed Paraphernalia @@ -540,381 +539,182 @@ Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectib Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectibles > Sports Fan Accessories > Hockey Fan Accessories Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectibles > Sports Fan Accessories > Soccer Fan Accessories Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Sports Collectibles > Sports Fan Accessories > Tennis Fan Accessories -Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Trading Cards -Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Trading Cards > Sports Trading Cards -Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Trading Cards > Sports Trading Cards > Auto Racing Trading Cards -Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Trading Cards > Sports Trading Cards > Baseball Trading Cards -Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Trading Cards > Sports Trading Cards > Basketball Trading Cards -Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Trading Cards > Sports Trading Cards > Football Trading Cards -Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Trading Cards > Sports Trading Cards > Hockey Trading Cards -Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Trading Cards > Sports Trading Cards > Soccer Trading Cards Arts & Entertainment > Hobbies & Creative Arts > Collectibles > Vintage Advertisements -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Art Paper -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Art Paper > Construction Paper -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Art Paper > Origami Paper -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Art Paper > Scratchboard Paper -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Art Paper > Sketch Pads -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Art Paper > Tracing Paper -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Art Paper > Transfer Paper -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Art Paper > Vellum -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Art Paper > Watercolor Paper -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Beading Patterns -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Candle & Soap Making -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Candle & Soap Making > Wick Tabs -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Candle & Soap Making > Wicks -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Canvas & Boards -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Craft & Hobby Storage -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Craft & Hobby Storage > Knitting Needle & Crochet Hook Cases -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Craft & Hobby Storage > Sewing Organizers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Craft & Hobby Storage > Yarn Organizers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Craft Blanks -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Craft Foam & Styrofoam -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Craft Magnets -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Craft Molds -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Craft Pipe Cleaners -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Decorative Stickers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Drawing & Painting -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Drawing & Painting > Art Brushes -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Drawing & Painting > Drafting -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Drawing & Painting > Drafting > Drafting Compasses -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Drawing & Painting > Drawing & Painting Kits -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Drawing & Painting > Painting -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Drawing & Painting > Painting > Art Paint -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Drawing & Painting > Painting > Paint Mediums -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Drawing & Painting > Painting > Palette Knives -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Drawing & Painting > Painting > Palettes -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fabric Yo-Yos -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Crocheting -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Crocheting > Crochet Hooks -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Crocheting > Crochet Thread -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Cross-Stitch & Embroidery -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Felting -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Felting > Felting Molds -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Felting > Felting Needles -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Felting > Felting Pads & Mats -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Fibercraft Patterns -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Fibercraft Patterns > Cross-Stitch & Embroidery Patterns -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Fibercraft Patterns > Knitting & Crocheting Patterns -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Fibercraft Patterns > Sewing Patterns -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Hand Spinning -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Hand Spinning > Hand Spindles -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Hand Spinning > Spinning Wheel Accessories -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Hand Spinning > Spinning Wheels -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Knitting -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Knitting > Knitting Needles -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Lace, Trims & Trimmings -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Needlepoint -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Needlepoint > Needlepoint Canvas -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Needlepoint > Needlepoint Kits -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Quilting -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Elastic -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Fabric -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Fabric Patches -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Hook & Loop Fasteners -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Interfacing -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Plastic Canvas -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Sewing Buttons -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Sewing Labels -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Sewing Snaps -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Stuffing & Filling Material -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Textile Dyes -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Textile Paints -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Thread -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Unspun Fiber -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Yarn -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Materials > Zippers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Blocking Mats -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Blocking Wires -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Bow & Pom-Pom Makers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Dressforms -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Fabric Adhesives -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Fabric Adhesives > Fabric Glue -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Fabric Adhesives > Fusible Fabric Tape -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Fabric Cutter Dies -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Fabric Cutters -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Fabric Marking Tools -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Fabric Repair Kits -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Fabric Scissors & Shears -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Fiber Cards -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Looms -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Needle Threaders -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Pin Cushions -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Safety Pins -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Seam Rippers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Sewing Needles -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Stitch Markers & Counters -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Straight Pins -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Textile Art Frames, Hoops & Stretchers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Textile Art Gauges & Rulers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Thimbles -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Thread & Yarn Cutters -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Thread & Yarn Guides -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Thread & Yarn Spools -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Thread & Yarn Winders -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Textile Art Tools & Accessories > Yo-Yo Makers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Weaving -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Weaving > Locker Hooks -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Weaving > Weaving Beaters -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Fibercraft & Textile Arts > Weaving > Weaving Shuttles -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Flower Arranging -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Flower Arranging > Floral Tape -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Flower Arranging > Vase Fillers & Table Scatters -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Glass Crafts -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Glitter -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Homebrewing -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Homebrewing > Beer Brewing -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Homebrewing > Wine Making -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Incense Making Kits -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Jewelry Making -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Jewelry Making > Beads -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Jewelry Making > Jewelry & Beading Cord -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Jewelry Making > Jewelry & Beading Wire -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Jewelry Making > Jewelry Findings -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Juggling -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Leather Crafts -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Light Boxes -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Magic & Novelties -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Magnetic Tape -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Model Making -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Model Making > Model Rocketry -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Model Making > Model Train Accessories -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Model Making > Model Trains & Train Sets -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Model Making > Scale Model Kits -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Mosaic Making -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Mosaic Making > Mosaic Kits -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Paper Crafts -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Paper Crafts > Card Making Kits -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Paper Crafts > Embossing -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Paper Crafts > Embossing > Embossers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Paper Crafts > Embossing > Embossing Dies -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Paper Crafts > Embossing > Embossing Folders -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Paper Crafts > Embossing > Embossing Heat Tools -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Paper Crafts > Embossing > Embossing Pens -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Paper Crafts > Embossing > Embossing Powder -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Paper Crafts > Paper Creasers & Scorers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Pottery & Sculpting -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Pottery & Sculpting > Modeling Clay & Dough -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Printmaking -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Printmaking > Screen Printing -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Printmaking > Screen Printing > Brayer Rollers -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Printmaking > Screen Printing > Screen Printing Squeegees -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Rhinestones -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Scrapbooking -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Sequins -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Stamping -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Stamping > Leather Stamps & Kits -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Stamping > Metal Stamps & Kits -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Stenciling -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Stenciling > Stencil Machines -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Stenciling > Stencils & Dies -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Watch Repair Kits -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Woodworking -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Wreath Making -Arts & Entertainment > Hobbies & Creative Arts > Crafts & Hobbies > Wreath Making > Wreath Frames -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Brushes, Snakes & Cleaning Rods -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Care Kits -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Cleaners & Sanitizers -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Guards -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Lubricants -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Polishing Cloths -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Brass Instrument Replacement Parts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Euphonium Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Euphonium Accessories > Euphonium Cases & Gigbags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Euphonium Accessories > Euphonium Mouthpieces -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Euphonium Accessories > Euphonium Mutes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Euphonium Accessories > Euphonium Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > French Horn Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > French Horn Accessories > French Horn Cases & Gigbags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > French Horn Accessories > French Horn Mouthpieces -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > French Horn Accessories > French Horn Mutes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > French Horn Accessories > French Horn Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trombone Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trombone Accessories > Trombone Cases & Gigbags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trombone Accessories > Trombone Lyres -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trombone Accessories > Trombone Mouthpieces -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trombone Accessories > Trombone Mutes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trombone Accessories > Trombone Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trumpet & Cornet Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trumpet & Cornet Accessories > Trumpet & Cornet Cases & Gigbags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trumpet & Cornet Accessories > Trumpet & Cornet Lyres -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trumpet & Cornet Accessories > Trumpet & Cornet Mouthpieces -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trumpet & Cornet Accessories > Trumpet & Cornet Mutes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Trumpet & Cornet Accessories > Trumpet & Cornet Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Tuba Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Tuba Accessories > Tuba Cases & Gigbags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Tuba Accessories > Tuba Mouthpieces -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Tuba Accessories > Tuba Mutes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Brass Instrument Accessories > Tuba Accessories > Tuba Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Electronic Tuners -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Music Stand Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Music Stand Accessories > Music Stand Bags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Music Stand Accessories > Music Stand Lights -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Music Stand Accessories > Sheet Music Clips -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Music Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Musical Instrument Amplifier Cabinets -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Musical Instrument Amplifier Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Musical Instrument Amplifiers -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Musical Keyboard Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Musical Keyboard Accessories > Musical Keyboard Bags & Cases -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Musical Keyboard Accessories > Musical Keyboard Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Musical Keyboard Accessories > Sustain Pedals -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Cymbal & Drum Cases -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Cymbal & Drum Mutes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Drum Heads -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Drum Keys -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Drum Kit Hardware -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Drum Kit Hardware > Bass Drum Beaters -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Drum Kit Hardware > Drum Kit Mounting Hardware -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Drum Kit Hardware > Drum Pedals -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Drum Stick Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Drum Stick Accessories > Drum Stick Bags & Holders -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Drum Sticks -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Drumming Thrones -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Electronic Drum Modules -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Hand Percussion Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Hand Percussion Accessories > Hand Percussion Bags & Cases -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Hand Percussion Accessories > Hand Percussion Stands & Mounts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Percussion Mallets -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Percussion Accessories > Percussion Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Piano Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Piano Accessories > Piano Benches -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Cello Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Cello Accessories > Cello Bows -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Cello Accessories > Cello Cases -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Cello Accessories > Cello Mutes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Cello Accessories > Cello Strings -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories > Acoustic Guitar Pickups -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories > Capos -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories > Electric Guitar Pickups -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories > Guitar Bridge Pins -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories > Guitar Cases & Gig Bags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories > Guitar Humidifiers -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories > Guitar Picks -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories > Guitar Slides -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories > Guitar Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories > Guitar Strings -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Guitar Accessories > Guitar Tuning Machines -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Harp Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Harp Accessories > Harp Cases -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Harp Accessories > Harp Strings -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > String Instrument Care & Cleaning -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > String Instrument Care & Cleaning > Bow Rosin -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > String Instrument Care & Cleaning > String Instrument Cleaning Cloths -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > String Instrument Care & Cleaning > String Instrument Polish -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Upright Bass Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Upright Bass Accessories > Upright Bass Bows -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Upright Bass Accessories > Upright Bass Cases -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Upright Bass Accessories > Upright Bass Mutes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Upright Bass Accessories > Upright Bass Strings -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Viola Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Viola Accessories > Viola Bows -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Viola Accessories > Viola Cases -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Viola Accessories > Viola Mutes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Viola Accessories > Viola Strings -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Violin Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Violin Accessories > Violin & Viola Chinrests -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Violin Accessories > Violin & Viola Shoulder Rests -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Violin Accessories > Violin Bows -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Violin Accessories > Violin Bridges -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Violin Accessories > Violin Cases -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Violin Accessories > Violin Mutes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Violin Accessories > Violin Pickups -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Violin Accessories > Violin Strings -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Violin Accessories > Violin Tailpieces -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > String Instrument Accessories > Violin Accessories > Violin Tuning Pegs -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Bassoon Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Care & Cleaning -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Care & Cleaning > Bassoon Swabs -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Cases & Gigbags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Parts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Parts > Bassoon Bocals -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Parts > Bassoon Small Parts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Reeds -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Straps & Supports -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Care & Cleaning -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Care & Cleaning > Clarinet Care Kits -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Care & Cleaning > Clarinet Pad Savers -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Care & Cleaning > Clarinet Swabs -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Cases & Gigbags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Ligatures & Caps -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Lyres -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Parts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Parts > Clarinet Barrels -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Parts > Clarinet Bells -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Parts > Clarinet Mouthpieces -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Parts > Clarinet Small Parts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Pegs & Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Reeds -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Straps & Supports -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Flute Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Care & Cleaning -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Care & Cleaning > Flute Care Kits -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Care & Cleaning > Flute Cleaning Rods -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Care & Cleaning > Flute Swabs -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Cases & Gigbags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Lyres -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Parts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Parts > Flute Headjoints -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Parts > Flute Small Parts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Pegs & Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Harmonica Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Harmonica Accessories > Harmonica Cases -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Harmonica Accessories > Harmonica Holders -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Care & Cleaning -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Care & Cleaning > Oboe Care Kits -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Care & Cleaning > Oboe Swabs -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Cases & Gigbags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Parts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Parts > Oboe Small Parts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Pegs & Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Reeds -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Straps & Supports -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Care & Cleaning -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Care & Cleaning > Saxophone Care Kits -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Care & Cleaning > Saxophone Pad Savers -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Care & Cleaning > Saxophone Swabs -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Cases & Gigbags -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Ligatures & Caps -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Lyres -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Parts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Parts > Saxophone Mouthpieces -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Parts > Saxophone Necks -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Parts > Saxophone Small Parts -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Pegs & Stands -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Reeds -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Straps & Supports -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Woodwind Cork Grease -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Woodwind Polishing Cloths -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Woodwind Reed Cases -Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument Accessories > Woodwind Instrument Accessories > Woodwind Reed Knives +Arts & Entertainment > Hobbies & Creative Arts > Homebrewing & Winemaking Supplies +Arts & Entertainment > Hobbies & Creative Arts > Homebrewing & Winemaking Supplies > Beer Brewing Grains & Malts +Arts & Entertainment > Hobbies & Creative Arts > Homebrewing & Winemaking Supplies > Bottling Bottles +Arts & Entertainment > Hobbies & Creative Arts > Homebrewing & Winemaking Supplies > Homebrewing & Winemaking Kits +Arts & Entertainment > Hobbies & Creative Arts > Homebrewing & Winemaking Supplies > Wine Making +Arts & Entertainment > Hobbies & Creative Arts > Juggling +Arts & Entertainment > Hobbies & Creative Arts > Magic & Novelties +Arts & Entertainment > Hobbies & Creative Arts > Model Making +Arts & Entertainment > Hobbies & Creative Arts > Model Making > Model Rocketry +Arts & Entertainment > Hobbies & Creative Arts > Model Making > Model Train Accessories +Arts & Entertainment > Hobbies & Creative Arts > Model Making > Model Trains & Train Sets +Arts & Entertainment > Hobbies & Creative Arts > Model Making > Scale Model Kits +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Care Kits +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Cleaners & Sanitizers +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Cleaning Tools +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Guards +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Lubricants +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Care & Cleaning > Brass Instrument Polishing Cloths +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Cases & Gigbags +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Mouthpieces +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Mutes +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Replacement Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Brass Instrument Accessories > Brass Instrument Straps & Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Conductor Batons +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Electronic Tuners +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Metronomes +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Music Benches & Stools +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Music Lyres & Flip Folders +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Music Stand Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Music Stand Accessories > Music Stand Bags +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Music Stand Accessories > Music Stand Lights +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Music Stand Accessories > Sheet Music Clips +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Music Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Instrument Amplifier Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Instrument Amplifier Accessories > Musical Instrument Amplifier Cabinets +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Instrument Amplifier Accessories > Musical Instrument Amplifier Covers & Cases +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Instrument Amplifier Accessories > Musical Instrument Amplifier Footswitches +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Instrument Amplifier Accessories > Musical Instrument Amplifier Knobs +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Instrument Amplifier Accessories > Musical Instrument Amplifier Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Instrument Amplifier Accessories > Musical Instrument Amplifier Tubes +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Instrument Amplifiers +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Keyboard Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Keyboard Accessories > Musical Keyboard Bags & Cases +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Keyboard Accessories > Musical Keyboard Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Musical Keyboard Accessories > Sustain Pedals +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Cymbal & Drum Cases +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Cymbal & Drum Mutes +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Drum Heads +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Drum Keys +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Drum Kit Hardware +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Drum Kit Hardware > Bass Drum Beaters +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Drum Kit Hardware > Drum Kit Mounting Hardware +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Drum Kit Hardware > Drum Pedals +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Drum Stick & Brush Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Drum Stick & Brush Accessories > Drum Stick & Brush Bags & Holders +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Drum Sticks & Brushes +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Electronic Drum Modules +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Hand Percussion Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Hand Percussion Accessories > Hand Percussion Bags & Cases +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Hand Percussion Accessories > Hand Percussion Stands & Mounts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Percussion Mallets +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Percussion Accessories > Percussion Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Acoustic Guitar Pickups +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Capos +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Electric Guitar Pickups +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Guitar Cases & Gig Bags +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Guitar Fittings & Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Guitar Humidifiers +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Guitar Picks +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Guitar Slides +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Guitar Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Guitar Straps +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Guitar String Winders +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Guitar Strings +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Guitar Accessories > Guitar Tuning Pegs +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Orchestral String Instrument Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Orchestral String Instrument Accessories > Orchestral String Instrument Bow Cases +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Orchestral String Instrument Accessories > Orchestral String Instrument Bows +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Orchestral String Instrument Accessories > Orchestral String Instrument Cases +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Orchestral String Instrument Accessories > Orchestral String Instrument Fittings & Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Orchestral String Instrument Accessories > Orchestral String Instrument Mutes +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Orchestral String Instrument Accessories > Orchestral String Instrument Pickups +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Orchestral String Instrument Accessories > Orchestral String Instrument Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > Orchestral String Instrument Accessories > Orchestral String Instrument Strings +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > String Instrument Care & Cleaning +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > String Instrument Care & Cleaning > Bow Rosin +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > String Instrument Care & Cleaning > String Instrument Cleaning Cloths +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > String Instrument Accessories > String Instrument Care & Cleaning > String Instrument Polish +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Bassoon Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Care & Cleaning +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Care & Cleaning > Bassoon Swabs +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Cases & Gigbags +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Parts > Bassoon Bocals +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Parts > Bassoon Small Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Reeds +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Bassoon Accessories > Bassoon Straps & Supports +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Care & Cleaning +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Care & Cleaning > Clarinet Care Kits +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Care & Cleaning > Clarinet Pad Savers +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Care & Cleaning > Clarinet Swabs +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Cases & Gigbags +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Ligatures & Caps +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Parts > Clarinet Barrels +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Parts > Clarinet Bells +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Parts > Clarinet Mouthpieces +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Parts > Clarinet Small Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Pegs & Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Reeds +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Clarinet Accessories > Clarinet Straps & Supports +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Flute Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Care & Cleaning +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Care & Cleaning > Flute Care Kits +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Care & Cleaning > Flute Cleaning Rods +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Care & Cleaning > Flute Swabs +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Cases & Gigbags +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Parts > Flute Headjoints +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Parts > Flute Small Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Flute Accessories > Flute Pegs & Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Harmonica Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Harmonica Accessories > Harmonica Cases +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Harmonica Accessories > Harmonica Holders +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Care & Cleaning +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Care & Cleaning > Oboe Care Kits +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Care & Cleaning > Oboe Swabs +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Cases & Gigbags +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Parts > Oboe Small Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Pegs & Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Reeds +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Oboe & English Horn Accessories > Oboe Straps & Supports +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Recorder Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Recorder Accessories > Recorder Care & Cleaning +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Recorder Accessories > Recorder Cases +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Recorder Accessories > Recorder Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Care & Cleaning +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Care & Cleaning > Saxophone Care Kits +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Care & Cleaning > Saxophone Pad Savers +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Care & Cleaning > Saxophone Swabs +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Cases & Gigbags +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Ligatures & Caps +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Parts > Saxophone Mouthpieces +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Parts > Saxophone Necks +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Parts > Saxophone Small Parts +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Pegs & Stands +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Reeds +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Saxophone Accessories > Saxophone Straps & Supports +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Woodwind Cork Grease +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Woodwind Polishing Cloths +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Woodwind Reed Cases +Arts & Entertainment > Hobbies & Creative Arts > Musical Instrument & Orchestra Accessories > Woodwind Instrument Accessories > Woodwind Reed Knives Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Accordions & Concertinas Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Bagpipes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass > Cornets -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass > Euphoniums -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass > Flugel Horns -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass > French Horns -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass > Mellophones -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass > Sousaphones -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass > Trombones -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass > Trumpets -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass > Tubas +Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass Instruments +Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass Instruments > Alto & Baritone Horns +Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass Instruments > Euphoniums +Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass Instruments > French Horns +Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass Instruments > Trombones +Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass Instruments > Trumpets & Cornets +Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Brass Instruments > Tubas Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Electronic Musical Instruments Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Electronic Musical Instruments > Audio Samplers Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Electronic Musical Instruments > MIDI Controllers @@ -930,7 +730,6 @@ Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Percussio Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Percussion > Hand Percussion Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Percussion > Hand Percussion > Claves & Castanets Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Percussion > Hand Percussion > Finger & Hand Cymbals -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Percussion > Hand Percussion > Hand Bells Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Percussion > Hand Percussion > Hand Bells & Chimes Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Percussion > Hand Percussion > Hand Drums Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Percussion > Hand Percussion > Hand Drums > Bongos @@ -955,9 +754,6 @@ Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Pianos Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > String Instruments Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > String Instruments > Cellos Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > String Instruments > Guitars -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > String Instruments > Guitars > Acoustic Guitars -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > String Instruments > Guitars > Bass Guitars -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > String Instruments > Guitars > Electric Guitars Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > String Instruments > Harps Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > String Instruments > Upright Basses Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > String Instruments > Violas @@ -966,7 +762,6 @@ Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Woodwinds Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Woodwinds > Bassoons Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Woodwinds > Clarinets Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Woodwinds > Flutes -Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Woodwinds > Flutes > Fifes Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Woodwinds > Flutophones Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Woodwinds > Harmonicas Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Woodwinds > Jew's Harps @@ -980,59 +775,62 @@ 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 -Arts & Entertainment > Party & Celebration > Gift Giving > Gift Wrapping > Bows & Ribbons Arts & Entertainment > Party & Celebration > Gift Giving > Gift Wrapping > Gift Bags Arts & Entertainment > Party & Celebration > Gift Giving > Gift Wrapping > Gift Boxes & Tins +Arts & Entertainment > Party & Celebration > Gift Giving > Gift Wrapping > Gift Tags & Labels Arts & Entertainment > Party & Celebration > Gift Giving > Gift Wrapping > Tissue Paper Arts & Entertainment > Party & Celebration > Gift Giving > Gift Wrapping > Wrapping Paper Arts & Entertainment > Party & Celebration > Gift Giving > Greeting & Note Cards Arts & Entertainment > Party & Celebration > Party Supplies +Arts & Entertainment > Party & Celebration > Party Supplies > Advice Cards Arts & Entertainment > Party & Celebration > Party Supplies > Balloon Kits Arts & Entertainment > Party & Celebration > Party Supplies > Balloons Arts & Entertainment > Party & Celebration > Party Supplies > Banners -Arts & Entertainment > Party & Celebration > Party Supplies > Beverage Tubs Arts & Entertainment > Party & Celebration > Party Supplies > Birthday Candles +Arts & Entertainment > Party & Celebration > Party Supplies > Chair Sashes Arts & Entertainment > Party & Celebration > Party Supplies > Cocktail Decorations Arts & Entertainment > Party & Celebration > Party Supplies > Confetti +Arts & Entertainment > Party & Celebration > Party Supplies > Decorative Pom-Poms Arts & Entertainment > Party & Celebration > Party Supplies > Drinking Games Arts & Entertainment > Party & Celebration > Party Supplies > Drinking Games > Beer Pong Arts & Entertainment > Party & Celebration > Party Supplies > Drinking Games > Beer Pong > Beer Pong Tables Arts & Entertainment > Party & Celebration > Party Supplies > Drinking Straws & Stirrers Arts & Entertainment > Party & Celebration > Party Supplies > Envelope Seals Arts & Entertainment > Party & Celebration > Party Supplies > Event Programs +Arts & Entertainment > Party & Celebration > Party Supplies > Fireworks & Firecrackers +Arts & Entertainment > Party & Celebration > Party Supplies > Inflatable Party Decorations Arts & Entertainment > Party & Celebration > Party Supplies > Invitations -Arts & Entertainment > Party & Celebration > Party Supplies > Invitations > Birthday Invitations -Arts & Entertainment > Party & Celebration > Party Supplies > Invitations > Wedding Invitations Arts & Entertainment > Party & Celebration > Party Supplies > Noisemakers & Party Blowers Arts & Entertainment > Party & Celebration > Party Supplies > Party Favors Arts & Entertainment > Party & Celebration > Party Supplies > Party Favors > Wedding Favors Arts & Entertainment > Party & Celebration > Party Supplies > Party Games Arts & Entertainment > Party & Celebration > Party Supplies > Party Hats -Arts & Entertainment > Party & Celebration > Party Supplies > Piñatas +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 > Place Card Holders Arts & Entertainment > Party & Celebration > Party Supplies > Place Cards Arts & Entertainment > Party & Celebration > Party Supplies > Response Cards -Arts & Entertainment > Party & Celebration > Party Supplies > Response Cards > Wedding Response Cards Arts & Entertainment > Party & Celebration > Party Supplies > Sparklers +Arts & Entertainment > Party & Celebration > Party Supplies > Special Occasion Card Boxes & Holders Arts & Entertainment > Party & Celebration > Party Supplies > Spray String -Arts & Entertainment > Party & Celebration > Party Supplies > Streamers -Arts & Entertainment > Party & Celebration > Party Supplies > Wedding Card Boxes & Holders Arts & Entertainment > Party & Celebration > Special Effects Arts & Entertainment > Party & Celebration > Special Effects > Disco Balls Arts & Entertainment > Party & Celebration > Special Effects > Fog Machines Arts & Entertainment > Party & Celebration > Special Effects > Special Effects Controllers +Arts & Entertainment > Party & Celebration > Special Effects > Special Effects Light Stands Arts & Entertainment > Party & Celebration > Special Effects > Special Effects Lighting Arts & Entertainment > Party & Celebration > Trophies & Awards Baby & Toddler -Baby & Toddler > Baby & Toddler Gift Sets Baby & Toddler > Baby Bathing Baby & Toddler > Baby Bathing > Baby Bathtubs & Bath Seats Baby & Toddler > Baby Bathing > Shower Visors +Baby & Toddler > Baby Gift Sets Baby & Toddler > Baby Health Baby & Toddler > Baby Health > Baby Health & Grooming Kits Baby & Toddler > Baby Health > Nasal Aspirators @@ -1046,20 +844,20 @@ Baby & Toddler > Baby Safety > Baby Monitors Baby & Toddler > Baby Safety > Baby Safety Harnesses & Leashes Baby & Toddler > Baby Safety > Baby Safety Locks & Guards Baby & Toddler > Baby Safety > Baby Safety Rails -Baby & Toddler > Baby Toys -Baby & Toddler > Baby Toys > Alphabet Toys -Baby & Toddler > Baby Toys > Baby Activity Toys -Baby & Toddler > Baby Toys > Baby Bouncers & Rockers -Baby & Toddler > Baby Toys > Baby Jumpers & Swings -Baby & Toddler > Baby Toys > Baby Mobile Accessories -Baby & Toddler > Baby Toys > Baby Mobiles -Baby & Toddler > Baby Toys > Baby Soothers -Baby & Toddler > Baby Toys > Baby Walkers & Entertainers -Baby & Toddler > Baby Toys > Play Mats & Gyms -Baby & Toddler > Baby Toys > Play Yards -Baby & Toddler > Baby Toys > Push & Pull Toys -Baby & Toddler > Baby Toys > Rattles -Baby & Toddler > Baby Toys > Sorting & Stacking Toys +Baby & Toddler > Baby Toys & Activity Equipment +Baby & Toddler > Baby Toys & Activity Equipment > Alphabet Toys +Baby & Toddler > Baby Toys & Activity Equipment > Baby Activity Toys +Baby & Toddler > Baby Toys & Activity Equipment > Baby Bouncers & Rockers +Baby & Toddler > Baby Toys & Activity Equipment > Baby Jumpers & Swings +Baby & Toddler > Baby Toys & Activity Equipment > Baby Mobile Accessories +Baby & Toddler > Baby Toys & Activity Equipment > Baby Mobiles +Baby & Toddler > Baby Toys & Activity Equipment > Baby Soothers +Baby & Toddler > Baby Toys & Activity Equipment > Baby Walkers & Entertainers +Baby & Toddler > Baby Toys & Activity Equipment > Play Mats & Gyms +Baby & Toddler > Baby Toys & Activity Equipment > Play Yards +Baby & Toddler > Baby Toys & Activity Equipment > Push & Pull Toys +Baby & Toddler > Baby Toys & Activity Equipment > Rattles +Baby & Toddler > Baby Toys & Activity Equipment > Sorting & Stacking Toys Baby & Toddler > Baby Transport Baby & Toddler > Baby Transport > Baby & Toddler Car Seats Baby & Toddler > Baby Transport > Baby Carriers @@ -1068,10 +866,12 @@ Baby & Toddler > Baby Transport Accessories Baby & Toddler > Baby Transport Accessories > Baby & Toddler Car Seat Accessories Baby & Toddler > Baby Transport Accessories > Baby Carrier Accessories Baby & Toddler > Baby Transport Accessories > Baby Stroller Accessories +Baby & Toddler > Baby Transport Accessories > Baby Transport Liners & Sacks Baby & Toddler > Baby Transport Accessories > Shopping Cart & High Chair Covers Baby & Toddler > Diapering Baby & Toddler > Diapering > Baby Wipe Dispensers & Warmers Baby & Toddler > Diapering > Baby Wipes +Baby & Toddler > Diapering > Changing Mat & Tray Covers Baby & Toddler > Diapering > Changing Mats & Trays Baby & Toddler > Diapering > Diaper Kits Baby & Toddler > Diapering > Diaper Liners @@ -1084,27 +884,29 @@ Baby & Toddler > Diapering > Diapers Baby & Toddler > Nursing & Feeding Baby & Toddler > Nursing & Feeding > Baby & Toddler Food Baby & Toddler > Nursing & Feeding > Baby & Toddler Food > Baby Cereal +Baby & Toddler > Nursing & Feeding > Baby & Toddler Food > Baby Drinks Baby & Toddler > Nursing & Feeding > Baby & Toddler Food > Baby Food Baby & Toddler > Nursing & Feeding > Baby & Toddler Food > Baby Formula -Baby & Toddler > Nursing & Feeding > Baby & Toddler Food > Baby Juice Baby & Toddler > Nursing & Feeding > Baby & Toddler Food > Baby Snacks -Baby & Toddler > Nursing & Feeding > Baby Bottle Nipples +Baby & Toddler > Nursing & Feeding > Baby & Toddler Food > Toddler Nutrition Drinks & Shakes +Baby & Toddler > Nursing & Feeding > Baby Bottle Nipples & Liners Baby & Toddler > Nursing & Feeding > Baby Bottles Baby & Toddler > Nursing & Feeding > Baby Care Timers Baby & Toddler > Nursing & Feeding > Bibs Baby & Toddler > Nursing & Feeding > Bottle Warmers & Sterilizers Baby & Toddler > Nursing & Feeding > Breast Milk Storage Containers +Baby & Toddler > Nursing & Feeding > Breast Pump Accessories Baby & Toddler > Nursing & Feeding > Breast Pumps Baby & Toddler > Nursing & Feeding > Burp Cloths -Baby & Toddler > Nursing & Feeding > Nipple Shields Baby & Toddler > Nursing & Feeding > Nursing Covers -Baby & Toddler > Nursing & Feeding > Nursing Pads +Baby & Toddler > Nursing & Feeding > Nursing Pads & Shields +Baby & Toddler > Nursing & Feeding > Nursing Pillow Covers Baby & Toddler > Nursing & Feeding > Nursing Pillows Baby & Toddler > Nursing & Feeding > Sippy Cups Baby & Toddler > Potty Training Baby & Toddler > Potty Training > Potty Seats Baby & Toddler > Potty Training > Potty Training Kits -Baby & Toddler > Swaddling Blankets +Baby & Toddler > Swaddling & Receiving Blankets Business & Industrial Business & Industrial > Advertising & Marketing Business & Industrial > Advertising & Marketing > Brochures @@ -1112,23 +914,34 @@ Business & Industrial > Advertising & Marketing > Trade Show Counters Business & Industrial > Advertising & Marketing > Trade Show Displays Business & Industrial > Agriculture Business & Industrial > Agriculture > Animal Husbandry -Business & Industrial > Agriculture > Animal Husbandry > Chicken Coops -Business & Industrial > Agriculture > Animal Husbandry > Livestock Feeders +Business & Industrial > Agriculture > Animal Husbandry > Egg Incubators +Business & Industrial > Agriculture > Animal Husbandry > Livestock Feed +Business & Industrial > Agriculture > Animal Husbandry > Livestock Feeders & Waterers +Business & Industrial > Agriculture > Animal Husbandry > Livestock Halters +Business & Industrial > Automation Control Components +Business & Industrial > Automation Control Components > Programmable Logic Controllers +Business & Industrial > Automation Control Components > Variable Frequency & Adjustable Speed Drives Business & Industrial > Construction Business & Industrial > Construction > Surveying +Business & Industrial > Construction > Traffic Cones & Barrels Business & Industrial > Dentistry Business & Industrial > Dentistry > Dental Cement Business & Industrial > Dentistry > Dental Tools +Business & Industrial > Dentistry > Dental Tools > Dappen Dishes Business & Industrial > Dentistry > Dental Tools > Dental Mirrors Business & Industrial > Dentistry > Dental Tools > Dental Tool Sets +Business & Industrial > Dentistry > Dental Tools > Prophy Cups +Business & Industrial > Dentistry > Dental Tools > Prophy Heads +Business & Industrial > Dentistry > Prophy Paste Business & Industrial > Film & Television Business & Industrial > Finance & Insurance Business & Industrial > Finance & Insurance > Bullion Business & Industrial > Food Service Business & Industrial > Food Service > Bakery Boxes Business & Industrial > Food Service > Bus Tubs +Business & Industrial > Food Service > Check Presenters Business & Industrial > Food Service > Concession Food Containers -Business & Industrial > Food Service > Disposable Cutlery Dispensers +Business & Industrial > Food Service > Disposable Lids Business & Industrial > Food Service > Disposable Serveware Business & Industrial > Food Service > Disposable Serveware > Disposable Serving Trays Business & Industrial > Food Service > Disposable Tableware @@ -1142,11 +955,13 @@ Business & Industrial > Food Service > Food Washers & Dryers Business & Industrial > Food Service > Hot Dog Rollers Business & Industrial > Food Service > Ice Bins Business & Industrial > Food Service > Plate & Dish Warmers +Business & Industrial > Food Service > Sneeze Guards Business & Industrial > Food Service > Take-Out Containers Business & Industrial > Food Service > Tilt Skillets Business & Industrial > Food Service > Vending Machines Business & Industrial > Forestry & Logging Business & Industrial > Hairdressing & Cosmetology +Business & Industrial > Hairdressing & Cosmetology > Hairdressing Capes & Neck Covers Business & Industrial > Hairdressing & Cosmetology > Pedicure Chairs Business & Industrial > Hairdressing & Cosmetology > Salon Chairs Business & Industrial > Heavy Machinery @@ -1156,52 +971,58 @@ Business & Industrial > Industrial Storage Business & Industrial > Industrial Storage > Industrial Cabinets Business & Industrial > Industrial Storage > Industrial Shelving Business & Industrial > Industrial Storage > Shipping Containers +Business & Industrial > Industrial Storage > Wire Partitions, Enclosures & Doors Business & Industrial > Industrial Storage Accessories +Business & Industrial > Janitorial Carts & Caddies Business & Industrial > Law Enforcement -Business & Industrial > Law Enforcement > Batons Business & Industrial > Law Enforcement > Cuffs -Business & Industrial > Law Enforcement > Cuffs > Ankle Cuffs -Business & Industrial > Law Enforcement > Cuffs > Handcuffs -Business & Industrial > Law Enforcement > Cuffs > Leg Cuffs Business & Industrial > Law Enforcement > Metal Detectors Business & Industrial > Manufacturing Business & Industrial > Material Handling Business & Industrial > Material Handling > Conveyors -Business & Industrial > Material Handling > Fork Lifts +Business & Industrial > Material Handling > Lifts & Hoists +Business & Industrial > Material Handling > Lifts & Hoists > Hoists, Cranes & Trolleys +Business & Industrial > Material Handling > Lifts & Hoists > Jacks & Lift Trucks +Business & Industrial > Material Handling > Lifts & Hoists > Personnel Lifts +Business & Industrial > Material Handling > Lifts & Hoists > Pulleys, Blocks & Sheaves +Business & Industrial > Material Handling > Lifts & Hoists > Winches +Business & Industrial > Material Handling > Pallets & Loading Platforms Business & Industrial > Medical Business & Industrial > Medical > Hospital Curtains Business & Industrial > Medical > Hospital Gowns Business & Industrial > Medical > Medical Bedding Business & Industrial > Medical > Medical Equipment Business & Industrial > Medical > Medical Equipment > Automated External Defibrillators -Business & Industrial > Medical > Medical Equipment > Medical Forceps +Business & Industrial > Medical > Medical Equipment > Gait Belts +Business & Industrial > Medical > Medical Equipment > Medical Reflex Hammers & Tuning Forks +Business & Industrial > Medical > Medical Equipment > Medical Stretchers & Gurneys Business & Industrial > Medical > Medical Equipment > Otoscopes & Ophthalmoscopes Business & Industrial > Medical > Medical Equipment > Patient Lifts -Business & Industrial > Medical > Medical Equipment > Reflex Hammers -Business & Industrial > Medical > Medical Equipment > Scalpels Business & Industrial > Medical > Medical Equipment > Stethoscopes Business & Industrial > Medical > Medical Equipment > Vital Signs Monitor Accessories Business & Industrial > Medical > Medical Equipment > Vital Signs Monitors Business & Industrial > Medical > Medical Furniture Business & Industrial > Medical > Medical Furniture > Chiropractic Tables -Business & Industrial > Medical > Medical Furniture > Examination Chairs -Business & Industrial > Medical > Medical Furniture > Examination Chairs > Dental Chairs -Business & Industrial > Medical > Medical Furniture > Examination Chairs > Gynecological Chairs -Business & Industrial > Medical > Medical Furniture > Examination Chairs > Optometry Chairs +Business & Industrial > Medical > Medical Furniture > Examination Chairs & Tables Business & Industrial > Medical > Medical Furniture > Homecare & Hospital Beds Business & Industrial > Medical > Medical Furniture > Medical Cabinets -Business & Industrial > Medical > Medical Furniture > Medical Cabinets > Narcotics Boxes Business & Industrial > Medical > Medical Furniture > Medical Carts Business & Industrial > Medical > Medical Furniture > Medical Carts > Crash Carts Business & Industrial > Medical > Medical Furniture > Medical Carts > IV Poles & Carts Business & Industrial > Medical > Medical Furniture > Surgical Tables +Business & Industrial > Medical > Medical Instruments +Business & Industrial > Medical > Medical Instruments > Medical Forceps +Business & Industrial > Medical > Medical Instruments > Scalpel Blades +Business & Industrial > Medical > Medical Instruments > Scalpels +Business & Industrial > Medical > Medical Instruments > Surgical Needles & Sutures Business & Industrial > Medical > Medical Supplies Business & Industrial > Medical > Medical Supplies > Disposable Gloves Business & Industrial > Medical > Medical Supplies > Finger Cots -Business & Industrial > Medical > Medical Supplies > Medical Masks +Business & Industrial > Medical > Medical Supplies > Medical Needles & Syringes +Business & Industrial > Medical > Medical Supplies > Ostomy Supplies Business & Industrial > Medical > Medical Supplies > Tongue Depressors Business & Industrial > Medical > Medical Teaching Equipment -Business & Industrial > Medical > Medical Teaching Equipment > CPR Mannequins +Business & Industrial > Medical > Medical Teaching Equipment > Medical & Emergency Response Training Mannequins Business & Industrial > Medical > Scrub Caps Business & Industrial > Medical > Scrubs Business & Industrial > Medical > Surgical Gowns @@ -1216,21 +1037,23 @@ Business & Industrial > Piercing & Tattooing > Tattooing Supplies > Tattooing Ma Business & Industrial > Piercing & Tattooing > Tattooing Supplies > Tattooing Needles Business & Industrial > Retail Business & Industrial > Retail > Clothing Display Racks +Business & Industrial > Retail > Display Mannequins Business & Industrial > Retail > Mannequin Parts -Business & Industrial > Retail > Mannequins Business & Industrial > Retail > Money Handling Business & Industrial > Retail > Money Handling > Banknote Verifiers -Business & Industrial > Retail > Money Handling > Cash Registers & POS Equipment -Business & Industrial > Retail > Money Handling > Cash Registers & POS Equipment > Cash Drawers -Business & Industrial > Retail > Money Handling > Cash Registers & POS Equipment > Credit Card Readers -Business & Industrial > Retail > Money Handling > Cash Registers & POS Equipment > Signature Capture Pads +Business & Industrial > Retail > Money Handling > Cash Register & POS Terminal Accessories +Business & Industrial > Retail > Money Handling > Cash Register & POS Terminal Accessories > Cash Drawers & Trays +Business & Industrial > Retail > Money Handling > Cash Register & POS Terminal Accessories > Credit Card Terminals +Business & Industrial > Retail > Money Handling > Cash Register & POS Terminal Accessories > Signature Capture Pads +Business & Industrial > Retail > Money Handling > Cash Registers & POS Terminals Business & Industrial > Retail > Money Handling > Coin & Bill Counters Business & Industrial > Retail > Money Handling > Money Changers Business & Industrial > Retail > Money Handling > Money Deposit Bags Business & Industrial > Retail > Money Handling > Paper Coin Wrappers & Bill Straps -Business & Industrial > Retail > Plastic Bags +Business & Industrial > Retail > Paper & Plastic Shopping Bags Business & Industrial > Retail > Pricing Guns Business & Industrial > Retail > Retail Display Cases +Business & Industrial > Retail > Retail Display Props & Models Business & Industrial > Science & Laboratory Business & Industrial > Science & Laboratory > Biochemicals Business & Industrial > Science & Laboratory > Dissection Kits @@ -1239,7 +1062,10 @@ Business & Industrial > Science & Laboratory > Laboratory Equipment Business & Industrial > Science & Laboratory > Laboratory Equipment > Autoclaves Business & Industrial > Science & Laboratory > Laboratory Equipment > Centrifuges Business & Industrial > Science & Laboratory > Laboratory Equipment > Dry Ice Makers +Business & Industrial > Science & Laboratory > Laboratory Equipment > Freeze-Drying Machines Business & Industrial > Science & Laboratory > Laboratory Equipment > Laboratory Blenders +Business & Industrial > Science & Laboratory > Laboratory Equipment > Laboratory Freezers +Business & Industrial > Science & Laboratory > Laboratory Equipment > Laboratory Funnels Business & Industrial > Science & Laboratory > Laboratory Equipment > Laboratory Hot Plates Business & Industrial > Science & Laboratory > Laboratory Equipment > Laboratory Ovens Business & Industrial > Science & Laboratory > Laboratory Equipment > Microscope Accessories @@ -1252,7 +1078,7 @@ Business & Industrial > Science & Laboratory > Laboratory Equipment > Microscope Business & Industrial > Science & Laboratory > Laboratory Equipment > Microtomes Business & Industrial > Science & Laboratory > Laboratory Equipment > Spectrometer Accessories Business & Industrial > Science & Laboratory > Laboratory Equipment > Spectrometers -Business & Industrial > Science & Laboratory > Laboratory Equipment > Spectrometers > Spectrophotometers +Business & Industrial > Science & Laboratory > Laboratory Specimens Business & Industrial > Science & Laboratory > Laboratory Supplies Business & Industrial > Science & Laboratory > Laboratory Supplies > Beakers Business & Industrial > Science & Laboratory > Laboratory Supplies > Graduated Cylinders @@ -1264,9 +1090,10 @@ Business & Industrial > Science & Laboratory > Laboratory Supplies > Test Tubes Business & Industrial > Science & Laboratory > Laboratory Supplies > Wash Bottles Business & Industrial > Signage Business & Industrial > Signage > Business Hour Signs -Business & Industrial > Signage > Electronic Signs -Business & Industrial > Signage > Electronic Signs > LED Signs -Business & Industrial > Signage > Electronic Signs > Neon Signs +Business & Industrial > Signage > Digital Signs +Business & Industrial > Signage > Electric Signs +Business & Industrial > Signage > Electric Signs > LED Signs +Business & Industrial > Signage > Electric Signs > Neon Signs Business & Industrial > Signage > Emergency & Exit Signs Business & Industrial > Signage > Facility Identification Signs Business & Industrial > Signage > Open & Closed Signs @@ -1279,79 +1106,87 @@ Business & Industrial > Signage > Security Signs Business & Industrial > Signage > Sidewalk & Yard Signs Business & Industrial > Work Safety Protective Gear Business & Industrial > Work Safety Protective Gear > Bullet Proof Vests -Business & Industrial > Work Safety Protective Gear > Dust Masks -Business & Industrial > Work Safety Protective Gear > Fire Repellent Clothing -Business & Industrial > Work Safety Protective Gear > Fireman's Masks -Business & Industrial > Work Safety Protective Gear > Gas Masks & Respirators +Business & Industrial > Work Safety Protective Gear > Gas Mask & Respirator Accessories Business & Industrial > Work Safety Protective Gear > Hardhats Business & Industrial > Work Safety Protective Gear > Hazardous Material Suits -Business & Industrial > Work Safety Protective Gear > High-Visibility Clothing Business & Industrial > Work Safety Protective Gear > Protective Aprons Business & Industrial > Work Safety Protective Gear > Protective Eyewear +Business & Industrial > Work Safety Protective Gear > Protective Masks +Business & Industrial > Work Safety Protective Gear > Protective Masks > Dust Masks +Business & Industrial > Work Safety Protective Gear > Protective Masks > Fireman's Masks +Business & Industrial > Work Safety Protective Gear > Protective Masks > Gas Masks & Respirators +Business & Industrial > Work Safety Protective Gear > Protective Masks > Medical Masks Business & Industrial > Work Safety Protective Gear > Safety Gloves -Business & Industrial > Work Safety Protective Gear > Safety Tethers +Business & Industrial > Work Safety Protective Gear > Safety Knee Pads +Business & Industrial > Work Safety Protective Gear > Welding Helmets +Business & Industrial > Work Safety Protective Gear > Work Safety Harnesses +Business & Industrial > Work Safety Protective Gear > Work Safety Tethers Cameras & Optics Cameras & Optics > Camera & Optic Accessories -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lens Accessories -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lens Accessories > Lens & Filter Adapters -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lens Accessories > Lens Bags -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lens Accessories > Lens Caps -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lens Accessories > Lens Converters -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lens Accessories > Lens Filters -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lens Accessories > Lens Filters > Close-Up Filters -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lens Accessories > Lens Hoods -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lenses -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lenses > Camera Lenses -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lenses > Surveillance Camera Lenses -Cameras & Optics > Camera & Optic Accessories > Camera & Optic Lenses > Video Camera Lenses -Cameras & Optics > Camera & Optic Accessories > Camera Accessories -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Camera Bags -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Camera Bags > Still Camera Bags -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Camera Bags > Video Camera Bags -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Camera Film -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Camera Flash Accessories -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Camera Flashes -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Camera Handles -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Camera Mounts -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Camera Remote Controls -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Camera Straps -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Flash Brackets -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Surveillance Camera Accessories -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Underwater Camera Housings -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Underwater Camera Housings > Underwater Still Camera Housings -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Underwater Camera Housings > Underwater Video Camera Housings -Cameras & Optics > Camera & Optic Accessories > Camera Accessories > Video Camera Lights +Cameras & Optics > Camera & Optic Accessories > Camera & Optic Replacement Cables +Cameras & Optics > Camera & Optic Accessories > Camera & Video Camera Lenses +Cameras & Optics > Camera & Optic Accessories > Camera & Video Camera Lenses > Camera Lenses +Cameras & Optics > Camera & Optic Accessories > Camera & Video Camera Lenses > Surveillance Camera Lenses +Cameras & Optics > Camera & Optic Accessories > Camera & Video Camera Lenses > Video Camera Lenses +Cameras & Optics > Camera & Optic Accessories > Camera Lens Accessories +Cameras & Optics > Camera & Optic Accessories > Camera Lens Accessories > Lens & Filter Adapter Rings +Cameras & Optics > Camera & Optic Accessories > Camera Lens Accessories > Lens Bags +Cameras & Optics > Camera & Optic Accessories > Camera Lens Accessories > Lens Caps +Cameras & Optics > Camera & Optic Accessories > Camera Lens Accessories > Lens Converters +Cameras & Optics > Camera & Optic Accessories > Camera Lens Accessories > Lens Filters +Cameras & Optics > Camera & Optic Accessories > Camera Lens Accessories > Lens Hoods +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Accessory Sets +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Bags & Cases +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Body Replacement Panels & Doors +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Digital Backs +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Film +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Flash Accessories +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Flashes +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Focus Devices +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Gears +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Grips +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Image Sensors +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Lens Zoom Units +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Remote Controls +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Replacement Buttons & Knobs +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Replacement Screens & Displays +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Silencers & Sound Blimps +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Stabilizers & Supports +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Straps +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Camera Sun Hoods & Viewfinder Attachments +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Flash Brackets +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > On-Camera Monitors +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Surveillance Camera Accessories +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Underwater Camera Housing Accessories +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Underwater Camera Housings +Cameras & Optics > Camera & Optic Accessories > Camera Parts & Accessories > Video Camera Lights Cameras & Optics > Camera & Optic Accessories > Optic Accessories Cameras & Optics > Camera & Optic Accessories > Optic Accessories > Binocular & Monocular Accessories -Cameras & Optics > Camera & Optic Accessories > Optic Accessories > Night Vision Optic Accessories Cameras & Optics > Camera & Optic Accessories > Optic Accessories > Optics Bags & Cases Cameras & Optics > Camera & Optic Accessories > Optic Accessories > Rangefinder Accessories Cameras & Optics > Camera & Optic Accessories > Optic Accessories > Spotting Scope Accessories -Cameras & Optics > Camera & Optic Accessories > Optic Accessories > Spotting Scope Accessories > Scope Eyepieces Cameras & Optics > Camera & Optic Accessories > Optic Accessories > Telescope Accessories Cameras & Optics > Camera & Optic Accessories > Optic Accessories > Thermal Optic Accessories -Cameras & Optics > Camera & Optic Accessories > Optic Accessories > Weapon Scope Accessories +Cameras & Optics > Camera & Optic Accessories > Optic Accessories > Weapon Scope & Sight Accessories Cameras & Optics > Camera & Optic Accessories > Tripod & Monopod Accessories Cameras & Optics > Camera & Optic Accessories > Tripod & Monopod Accessories > Tripod & Monopod Cases Cameras & Optics > Camera & Optic Accessories > Tripod & Monopod Accessories > Tripod & Monopod Heads Cameras & Optics > Camera & Optic Accessories > Tripod & Monopod Accessories > Tripod Collars & Mounts +Cameras & Optics > Camera & Optic Accessories > Tripod & Monopod Accessories > Tripod Handles +Cameras & Optics > Camera & Optic Accessories > Tripod & Monopod Accessories > Tripod Spreaders Cameras & Optics > Camera & Optic Accessories > Tripods & Monopods Cameras & Optics > Cameras +Cameras & Optics > Cameras > Borescopes Cameras & Optics > Cameras > Digital Cameras -Cameras & Optics > Cameras > Digital Cameras > Digital Point & Shoot Cameras -Cameras & Optics > Cameras > Digital Cameras > Digital SLRs -Cameras & Optics > Cameras > Digital Cameras > Mirrorless System Digital Cameras Cameras & Optics > Cameras > Disposable Cameras Cameras & Optics > Cameras > Film Cameras -Cameras & Optics > Cameras > Film Cameras > Instant Cameras Cameras & Optics > Cameras > Surveillance Cameras -Cameras & Optics > Cameras > Surveillance Cameras > Hidden Cameras Cameras & Optics > Cameras > Trail Cameras Cameras & Optics > Cameras > Video Cameras Cameras & Optics > Cameras > Webcams Cameras & Optics > Optics Cameras & Optics > Optics > Binoculars -Cameras & Optics > Optics > Binoculars > Opera Glasses Cameras & Optics > Optics > Monoculars Cameras & Optics > Optics > Rangefinders Cameras & Optics > Optics > Scopes @@ -1373,29 +1208,26 @@ Cameras & Optics > Photography > Darkroom > Enlarging Equipment > Focusing Aids Cameras & Optics > Photography > Darkroom > Enlarging Equipment > Photographic Analyzers Cameras & Optics > Photography > Darkroom > Enlarging Equipment > Photographic Enlargers Cameras & Optics > Photography > Darkroom > Photographic Chemicals -Cameras & Optics > Photography > Darkroom > Photographic Chemicals > Photographic Color Kits -Cameras & Optics > Photography > Darkroom > Photographic Chemicals > Photographic Developer -Cameras & Optics > Photography > Darkroom > Photographic Chemicals > Photographic Fixers -Cameras & Optics > Photography > Darkroom > Photographic Chemicals > Photographic Toners -Cameras & Optics > Photography > Darkroom > Photographic Chemicals > Stop Baths Cameras & Optics > Photography > Darkroom > Photographic Paper Cameras & Optics > Photography > Darkroom > Safelights Cameras & Optics > Photography > Lighting & Studio -Cameras & Optics > Photography > Lighting & Studio > Backgrounds -Cameras & Optics > Photography > Lighting & Studio > Flash Diffusers -Cameras & Optics > Photography > Lighting & Studio > Flash Diffusers > Camera Flash Diffusers -Cameras & Optics > Photography > Lighting & Studio > Flash Reflectors -Cameras & Optics > Photography > Lighting & Studio > Light Controls Cameras & Optics > Photography > Lighting & Studio > Light Meter Accessories Cameras & Optics > Photography > Lighting & Studio > Light Meters -Cameras & Optics > Photography > Lighting & Studio > Lighting Filters & Gels -Cameras & Optics > Photography > Lighting & Studio > Softboxes +Cameras & Optics > Photography > Lighting & Studio > Studio Backgrounds +Cameras & Optics > Photography > Lighting & Studio > Studio Light & Flash Accessories +Cameras & Optics > Photography > Lighting & Studio > Studio Lighting Controls +Cameras & Optics > Photography > Lighting & Studio > Studio Lighting Controls > Flash Diffusers +Cameras & Optics > Photography > Lighting & Studio > Studio Lighting Controls > Flash Reflectors +Cameras & Optics > Photography > Lighting & Studio > Studio Lighting Controls > Lighting Filters & Gobos +Cameras & Optics > Photography > Lighting & Studio > Studio Lighting Controls > Softboxes Cameras & Optics > Photography > Lighting & Studio > Studio Lights & Flashes -Cameras & Optics > Photography > Lighting & Studio > Studio Mountings +Cameras & Optics > Photography > Lighting & Studio > Studio Stand & Mount Accessories +Cameras & Optics > Photography > Lighting & Studio > Studio Stands & Mounts +Cameras & Optics > Photography > Photo Mounting Supplies Cameras & Optics > Photography > Photo Negative & Slide Storage Electronics -Electronics > 3D Printers Electronics > Arcade Equipment +Electronics > Arcade Equipment > Basketball Arcade Games Electronics > Arcade Equipment > Pinball Machine Accessories Electronics > Arcade Equipment > Pinball Machines Electronics > Arcade Equipment > Skee-Ball Machines @@ -1403,24 +1235,24 @@ Electronics > Arcade Equipment > Video Game Arcade Cabinet Accessories Electronics > Arcade Equipment > Video Game Arcade Cabinets Electronics > Audio Electronics > Audio > Audio Accessories -Electronics > Audio > Audio Accessories > Headphone Accessories -Electronics > Audio > Audio Accessories > Headphone Accessories > Headphone Cushions -Electronics > Audio > Audio Accessories > Headphone Accessories > Headphone Ear Tips +Electronics > Audio > Audio Accessories > Audio & Video Receiver Accessories +Electronics > Audio > Audio Accessories > Headphone & Headset Accessories +Electronics > Audio > Audio Accessories > Headphone & Headset Accessories > Headphone Cushions & Tips Electronics > Audio > Audio Accessories > Karaoke System Accessories Electronics > Audio > Audio Accessories > Karaoke System Accessories > Karaoke Chips Electronics > Audio > Audio Accessories > MP3 Player Accessories Electronics > Audio > Audio Accessories > MP3 Player Accessories > MP3 Player & Mobile Phone Accessory Sets Electronics > Audio > Audio Accessories > MP3 Player Accessories > MP3 Player Cases Electronics > Audio > Audio Accessories > Microphone Accessories -Electronics > Audio > Audio Accessories > Microphone Accessories > Microphone Stands -Electronics > Audio > Audio Accessories > Microphone Accessories > Microphone Windscreens +Electronics > Audio > Audio Accessories > Microphone Stands Electronics > Audio > Audio Accessories > Satellite Radio Accessories Electronics > Audio > Audio Accessories > Speaker Accessories -Electronics > Audio > Audio Accessories > Speaker Accessories > Speaker Grill Fabric +Electronics > Audio > Audio Accessories > Speaker Accessories > Speaker Bags, Covers & Cases +Electronics > Audio > Audio Accessories > Speaker Accessories > Speaker Components & Kits Electronics > Audio > Audio Accessories > Speaker Accessories > Speaker Stand Bags -Electronics > Audio > Audio Accessories > Speaker Accessories > Speaker Stands +Electronics > Audio > Audio Accessories > Speaker Accessories > Speaker Stands & Mounts +Electronics > Audio > Audio Accessories > Speaker Accessories > Tactile Transducers Electronics > Audio > Audio Accessories > Turntable Accessories -Electronics > Audio > Audio Accessories > Turntable Accessories > Turntable Cartridges & Needles Electronics > Audio > Audio Components Electronics > Audio > Audio Components > Audio & Video Receivers Electronics > Audio > Audio Components > Audio Amplifiers @@ -1432,21 +1264,18 @@ Electronics > Audio > Audio Components > Audio Transmitters > Bluetooth Transmit Electronics > Audio > Audio Components > Audio Transmitters > FM Transmitters Electronics > Audio > Audio Components > Channel Strips Electronics > Audio > Audio Components > Direct Boxes -Electronics > Audio > Audio Components > Headphones +Electronics > Audio > Audio Components > Headphones & Headsets Electronics > Audio > Audio Components > Microphones -Electronics > Audio > Audio Components > Microphones > Video Camera Microphones Electronics > Audio > Audio Components > Signal Processors Electronics > Audio > Audio Components > Signal Processors > Crossovers Electronics > Audio > Audio Components > Signal Processors > Effects Processors Electronics > Audio > Audio Components > Signal Processors > Equalizers +Electronics > Audio > Audio Components > Signal Processors > Loudspeaker Management Systems Electronics > Audio > Audio Components > Signal Processors > Microphone Preamps Electronics > Audio > Audio Components > Signal Processors > Noise Gates & Compressors Electronics > Audio > Audio Components > Signal Processors > Phono Preamps -Electronics > Audio > Audio Components > Signal Processors > Public Address Management Systems Electronics > Audio > Audio Components > Speakers -Electronics > Audio > Audio Components > Speakers > Computer Speakers -Electronics > Audio > Audio Components > Speakers > Studio Monitors -Electronics > Audio > Audio Components > Speakers > Subwoofers +Electronics > Audio > Audio Components > Studio Recording Bundles Electronics > Audio > Audio Players & Recorders Electronics > Audio > Audio Players & Recorders > Boomboxes Electronics > Audio > Audio Players & Recorders > CD Players & Recorders @@ -1458,51 +1287,54 @@ Electronics > Audio > Audio Players & Recorders > MP3 Players Electronics > Audio > Audio Players & Recorders > MiniDisc Players & Recorders Electronics > Audio > Audio Players & Recorders > Multitrack Recorders Electronics > Audio > Audio Players & Recorders > Radios -Electronics > Audio > Audio Players & Recorders > Radios > Internet Radios -Electronics > Audio > Audio Players & Recorders > Radios > Satellite Radios +Electronics > Audio > Audio Players & Recorders > Reel-to-Reel Tape Players & Recorders Electronics > Audio > Audio Players & Recorders > Stereo Systems Electronics > Audio > Audio Players & Recorders > Turntables & Record Players -Electronics > Audio > Audio Players & Recorders > Turntables & Record Players > DJ Turntables Electronics > Audio > Audio Players & Recorders > Voice Recorders +Electronics > Audio > Bullhorns Electronics > Audio > DJ & Specialty Audio Electronics > Audio > DJ & Specialty Audio > DJ CD Players -Electronics > Audio > DJ & Specialty Audio > DJ Equipment Accessories Electronics > Audio > DJ & Specialty Audio > DJ Systems Electronics > Audio > Public Address Systems Electronics > Audio > Stage Equipment Electronics > Audio > Stage Equipment > Wireless Transmitters -Electronics > Circuit Components -Electronics > Circuit Components > Circuit Decoders & Encoders -Electronics > Circuit Components > Circuit Prototyping -Electronics > Circuit Components > Circuit Prototyping > Breadboards -Electronics > Circuit Components > Electronic Filters -Electronics > Circuit Components > Optoelectronics -Electronics > Circuit Components > Optoelectronics > Laser Diodes -Electronics > Circuit Components > Passive Circuit Components -Electronics > Circuit Components > Passive Circuit Components > Capacitors -Electronics > Circuit Components > Passive Circuit Components > Electronic Oscillators -Electronics > Circuit Components > Passive Circuit Components > Inductors -Electronics > Circuit Components > Passive Circuit Components > Resistors -Electronics > Circuit Components > Passive Circuit Components > Resistors > Potentiometers -Electronics > Circuit Components > Printed Circuit Boards -Electronics > Circuit Components > Printed Circuit Boards > Development Boards -Electronics > Circuit Components > Semiconductors -Electronics > Circuit Components > Semiconductors > Diodes -Electronics > Circuit Components > Semiconductors > Integrated Circuits & Chips -Electronics > Circuit Components > Semiconductors > Integrated Circuits & Chips > Op Amps -Electronics > Circuit Components > Semiconductors > Integrated Circuits & Chips > Voltage Regulator ICs -Electronics > Circuit Components > Semiconductors > Microcontrollers -Electronics > Circuit Components > Semiconductors > Transistors +Electronics > Circuit Boards & Components +Electronics > Circuit Boards & Components > Circuit Board Accessories +Electronics > Circuit Boards & Components > Circuit Decoders & Encoders +Electronics > Circuit Boards & Components > Circuit Prototyping +Electronics > Circuit Boards & Components > Circuit Prototyping > Breadboards +Electronics > Circuit Boards & Components > Electronic Filters +Electronics > Circuit Boards & Components > Passive Circuit Components +Electronics > Circuit Boards & Components > Passive Circuit Components > Capacitors +Electronics > Circuit Boards & Components > Passive Circuit Components > Electronic Oscillators +Electronics > Circuit Boards & Components > Passive Circuit Components > Inductors +Electronics > Circuit Boards & Components > Passive Circuit Components > Resistors +Electronics > Circuit Boards & Components > Printed Circuit Boards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Camera Circuit Boards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Computer Circuit Boards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Computer Circuit Boards > Computer Inverter Boards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Computer Circuit Boards > Hard Drive Circuit Boards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Computer Circuit Boards > Motherboards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Development Boards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Exercise Machine Circuit Boards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Household Appliance Circuit Boards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Pool & Spa Circuit Boards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Printer, Copier, & Fax Machine Circuit Boards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Scanner Circuit Boards +Electronics > Circuit Boards & Components > Printed Circuit Boards > Television Circuit Boards +Electronics > Circuit Boards & Components > Semiconductors +Electronics > Circuit Boards & Components > Semiconductors > Diodes +Electronics > Circuit Boards & Components > Semiconductors > Integrated Circuits & Chips +Electronics > Circuit Boards & Components > Semiconductors > Microcontrollers +Electronics > Circuit Boards & Components > Semiconductors > Transistors Electronics > Communications Electronics > Communications > Answering Machines -Electronics > Communications > Answering Machines > On Hold Systems Electronics > Communications > Caller IDs Electronics > Communications > Communication Radio Accessories Electronics > Communications > Communication Radios Electronics > Communications > Communication Radios > CB Radios Electronics > Communications > Communication Radios > Radio Scanners Electronics > Communications > Communication Radios > Two-Way Radios -Electronics > Communications > Headsets Electronics > Communications > Intercom Accessories Electronics > Communications > Intercoms Electronics > Communications > Pagers @@ -1511,204 +1343,154 @@ Electronics > Communications > Telephony > Conference Phones Electronics > Communications > Telephony > Corded Phones Electronics > Communications > Telephony > Cordless Phones Electronics > Communications > Telephony > Mobile Phone Accessories +Electronics > Communications > Telephony > Mobile Phone Accessories > Mobile Phone Camera Accessories Electronics > Communications > Telephony > Mobile Phone Accessories > Mobile Phone Cases Electronics > Communications > Telephony > Mobile Phone Accessories > Mobile Phone Charms & Straps +Electronics > Communications > Telephony > Mobile Phone Accessories > Mobile Phone Pre-Paid Cards & SIM Cards Electronics > Communications > Telephony > Mobile Phone Accessories > Mobile Phone Replacement Parts -Electronics > Communications > Telephony > Mobile Phone Accessories > Mobile Phone Replacement Parts > Mobile Phone Digitizers -Electronics > Communications > Telephony > Mobile Phone Accessories > Mobile Phone SIM Cards Electronics > Communications > Telephony > Mobile Phone Accessories > Mobile Phone Stands +Electronics > Communications > Telephony > Mobile Phone Accessories > SIM Card Ejection Tools Electronics > Communications > Telephony > Mobile Phones -Electronics > Communications > Telephony > Mobile Phones > Feature Phones -Electronics > Communications > Telephony > Mobile Phones > Smartphones -Electronics > Communications > Telephony > Mobile Phones > Watch Phones Electronics > Communications > Telephony > Satellite Phones Electronics > Communications > Telephony > Telephone Accessories Electronics > Communications > Telephony > Telephone Accessories > Phone Cards Electronics > Communications > Video Conferencing Electronics > Components Electronics > Components > Accelerometers -Electronics > Components > Automation Control Components -Electronics > Components > Automation Control Components > Programmable Logic Controllers -Electronics > Components > Automation Control Components > Variable Frequency & Adjustable Speed Drives -Electronics > Components > Connectors -Electronics > Components > Connectors > Keystone Jacks -Electronics > Components > Connectors > RCA Jacks Electronics > Components > Converters +Electronics > Components > Converters > Audio Converters Electronics > Components > Converters > Scan Converters +Electronics > Components > Electronics Component Connectors Electronics > Components > Modulators -Electronics > Components > Modulators > RF Modulators Electronics > Components > Splitters -Electronics > Components > Splitters > RF Splitters -Electronics > Components > Splitters > UHF Splitters -Electronics > Components > Transformers -Electronics > Components > Transformers > Baluns Electronics > Computers Electronics > Computers > Barebone Computers -Electronics > Computers > Computer Accessories -Electronics > Computers > Computer Accessories > Computer Risers & Stands -Electronics > Computers > Computer Accessories > Handheld Device Accessories -Electronics > Computers > Computer Accessories > Handheld Device Accessories > E-Book Reader Accessories -Electronics > Computers > Computer Accessories > Handheld Device Accessories > E-Book Reader Accessories > E-Book Reader Cases -Electronics > Computers > Computer Accessories > Handheld Device Accessories > PDA Accessories -Electronics > Computers > Computer Accessories > Handheld Device Accessories > PDA Accessories > PDA Cases -Electronics > Computers > Computer Accessories > Keyboard & Mouse Wrist Rests -Electronics > Computers > Computer Accessories > Keyboard Trays & Platforms -Electronics > Computers > Computer Accessories > Laptop Accessories -Electronics > Computers > Computer Accessories > Laptop Accessories > Laptop Docking Stations -Electronics > Computers > Computer Accessories > Mouse Pads -Electronics > Computers > Computer Accessories > Stylus Pen Nibs & Refills -Electronics > Computers > Computer Accessories > Stylus Pens -Electronics > Computers > Computer Accessories > Tablet Computer Accessories -Electronics > Computers > Computer Accessories > Tablet Computer Accessories > Tablet Computer Replacement Parts -Electronics > Computers > Computer Accessories > Tablet Computer Accessories > Tablet Computer Replacement Parts > Tablet Computer Digitizers -Electronics > Computers > Computer Accessories > Tablet Computer Accessories > Tablet Computer Stands -Electronics > Computers > Computer Components -Electronics > Computers > Computer Components > Blade Server Enclosures -Electronics > Computers > Computer Components > Computer Cases -Electronics > Computers > Computer Components > Computer Power Supplies -Electronics > Computers > Computer Components > Computer Processors -Electronics > Computers > Computer Components > Computer Racks & Mounts -Electronics > Computers > Computer Components > Computer Starter Kits -Electronics > Computers > Computer Components > Computer System Cooling -Electronics > Computers > Computer Components > I/O Cards & Adapters -Electronics > Computers > Computer Components > I/O Cards & Adapters > Audio Cards & Adapters -Electronics > Computers > Computer Components > I/O Cards & Adapters > FireWire Cards -Electronics > Computers > Computer Components > I/O Cards & Adapters > Parallel Cards & Adapters -Electronics > Computers > Computer Components > I/O Cards & Adapters > Serial Cards & Adapters -Electronics > Computers > Computer Components > I/O Cards & Adapters > TV Tuner Cards & Adapters -Electronics > Computers > Computer Components > I/O Cards & Adapters > USB Cards -Electronics > Computers > Computer Components > I/O Cards & Adapters > Video Cards & Adapters -Electronics > Computers > Computer Components > Input Device Accessories -Electronics > Computers > Computer Components > Input Device Accessories > Barcode Scanner Stands -Electronics > Computers > Computer Components > Input Device Accessories > Game Controller Accessories -Electronics > Computers > Computer Components > Input Device Accessories > Game Controller Accessories > Game Controller Thumbsticks & Buttons -Electronics > Computers > Computer Components > Input Device Accessories > Graphics Tablet Accessories -Electronics > Computers > Computer Components > Input Devices -Electronics > Computers > Computer Components > Input Devices > Barcode Scanners -Electronics > Computers > Computer Components > Input Devices > Computer Keyboards -Electronics > Computers > Computer Components > Input Devices > Digital Note Taking Pens -Electronics > Computers > Computer Components > Input Devices > Fingerprint Readers -Electronics > Computers > Computer Components > Input Devices > Game Controllers -Electronics > Computers > Computer Components > Input Devices > Graphics Tablets -Electronics > Computers > Computer Components > Input Devices > KVM Switches -Electronics > Computers > Computer Components > Input Devices > Keyboard & Mouse Sets -Electronics > Computers > Computer Components > Input Devices > Memory Card Readers -Electronics > Computers > Computer Components > Input Devices > Mice & Trackballs -Electronics > Computers > Computer Components > Input Devices > Numeric Keypads -Electronics > Computers > Computer Components > Input Devices > Smart Card Readers -Electronics > Computers > Computer Components > Input Devices > Touchpads -Electronics > Computers > Computer Components > Laptop Parts -Electronics > Computers > Computer Components > Laptop Parts > Laptop Hinges -Electronics > Computers > Computer Components > Laptop Parts > Laptop Replacement Cables -Electronics > Computers > Computer Components > Laptop Parts > Laptop Replacement Keyboards -Electronics > Computers > Computer Components > Laptop Parts > Laptop Replacement Screens -Electronics > Computers > Computer Components > Laptop Parts > Laptop Shells -Electronics > Computers > Computer Components > Motherboards -Electronics > Computers > Computer Components > Output Devices -Electronics > Computers > Computer Components > Storage Devices -Electronics > Computers > Computer Components > Storage Devices > Disk Arrays -Electronics > Computers > Computer Components > Storage Devices > Disk Duplicators -Electronics > Computers > Computer Components > Storage Devices > Disk Duplicators > CD/DVD Duplicators -Electronics > Computers > Computer Components > Storage Devices > Disk Duplicators > Hard Drive Duplicators -Electronics > Computers > Computer Components > Storage Devices > Disk Duplicators > USB Drive Duplicators -Electronics > Computers > Computer Components > Storage Devices > Floppy Drives -Electronics > Computers > Computer Components > Storage Devices > Hard Drives -Electronics > Computers > Computer Components > Storage Devices > Hard Drives > Solid State Drives -Electronics > Computers > Computer Components > Storage Devices > Hard Drives > XBox 360 Hard Drives -Electronics > Computers > Computer Components > Storage Devices > Network Storage Systems -Electronics > Computers > Computer Components > Storage Devices > Network Storage Systems > Network Storage Servers -Electronics > Computers > Computer Components > Storage Devices > Network Storage Systems > Storage Area Networks -Electronics > Computers > Computer Components > Storage Devices > Optical Drives -Electronics > Computers > Computer Components > Storage Devices > Optical Drives > CD Drives -Electronics > Computers > Computer Components > Storage Devices > Optical Drives > DVD Drives -Electronics > Computers > Computer Components > Storage Devices > Optical Drives > MO Drives -Electronics > Computers > Computer Components > Storage Devices > Storage Drive Accessories -Electronics > Computers > Computer Components > Storage Devices > Storage Drive Accessories > Hard Drive Caddies -Electronics > Computers > Computer Components > Storage Devices > Storage Drive Accessories > Hard Drive Carrying Cases -Electronics > Computers > Computer Components > Storage Devices > Storage Drive Accessories > Hard Drive Docks -Electronics > Computers > Computer Components > Storage Devices > Storage Drive Accessories > Hard Drive Enclosures -Electronics > Computers > Computer Components > Storage Devices > Storage Drive Accessories > Hard Drive Mounts -Electronics > Computers > Computer Components > Storage Devices > Tape Drives -Electronics > Computers > Computer Components > Storage Devices > USB Flash Drives -Electronics > Computers > Computer Components > USB & FireWire Hubs Electronics > Computers > Computer Servers Electronics > Computers > Desktop Computers Electronics > Computers > Handheld Devices -Electronics > Computers > Handheld Devices > Data Collection Terminals +Electronics > Computers > Handheld Devices > Data Collectors Electronics > Computers > Handheld Devices > E-Book Readers Electronics > Computers > Handheld Devices > PDAs Electronics > Computers > Interactive Kiosks Electronics > Computers > Laptops -Electronics > Computers > Laptops > Netbooks +Electronics > Computers > Smart Glasses Electronics > Computers > Tablet Computers -Electronics > Electrical Motors +Electronics > Computers > Thin & Zero Clients +Electronics > Computers > Touch Table Computers Electronics > Electronics Accessories Electronics > Electronics Accessories > Adapters -Electronics > Electronics Accessories > Adapters > Audio & Video Adapters -Electronics > Electronics Accessories > Adapters > Memory Adapters -Electronics > Electronics Accessories > Adapters > Storage Adapters +Electronics > Electronics Accessories > Adapters > Audio & Video Cable Adapters & Couplers +Electronics > Electronics Accessories > Adapters > Memory Card Adapters Electronics > Electronics Accessories > Adapters > USB Adapters Electronics > Electronics Accessories > Antenna Accessories Electronics > Electronics Accessories > Antenna Accessories > Antenna Mounts & Brackets Electronics > Electronics Accessories > Antenna Accessories > Antenna Rotators Electronics > Electronics Accessories > Antenna Accessories > Satellite LNBs Electronics > Electronics Accessories > Antennas -Electronics > Electronics Accessories > Antennas > Communication Antennas -Electronics > Electronics Accessories > Antennas > Radio Antennas -Electronics > Electronics Accessories > Antennas > TV Antennas +Electronics > Electronics Accessories > Audio & Video Splitters & Switches +Electronics > Electronics Accessories > Audio & Video Splitters & Switches > DVI Splitters & Switches +Electronics > Electronics Accessories > Audio & Video Splitters & Switches > HDMI Splitters & Switches +Electronics > Electronics Accessories > Audio & Video Splitters & Switches > VGA Splitters & Switches Electronics > Electronics Accessories > Blank Media -Electronics > Electronics Accessories > Blank Media > Blank Audio Tapes -Electronics > Electronics Accessories > Blank Media > Blank Audio Tapes > Micro Cassettes -Electronics > Electronics Accessories > Blank Media > Blank Blu-ray Discs -Electronics > Electronics Accessories > Blank Media > Blank CDs -Electronics > Electronics Accessories > Blank Media > Blank DVDs -Electronics > Electronics Accessories > Blank Media > Blank Video Tapes -Electronics > Electronics Accessories > Blank Media > Computer Disks -Electronics > Electronics Accessories > Blank Media > Computer Disks > Floppy Disks -Electronics > Electronics Accessories > Blank Media > Computer Tapes -Electronics > Electronics Accessories > Blank Media > MiniDiscs Electronics > Electronics Accessories > Cable Management Electronics > Electronics Accessories > Cable Management > Cable Clips +Electronics > Electronics Accessories > Cable Management > Cable Tie Guns Electronics > Electronics Accessories > Cable Management > Cable Trays Electronics > Electronics Accessories > Cable Management > Patch Panels +Electronics > Electronics Accessories > Cable Management > Wire & Cable Identification Markers Electronics > Electronics Accessories > Cable Management > Wire & Cable Sleeves Electronics > Electronics Accessories > Cable Management > Wire & Cable Ties Electronics > Electronics Accessories > Cables Electronics > Electronics Accessories > Cables > Audio & Video Cables -Electronics > Electronics Accessories > Cables > Audio & Video Cables > DVI Cables -Electronics > Electronics Accessories > Cables > Audio & Video Cables > HDMI Cables -Electronics > Electronics Accessories > Cables > Audio & Video Cables > SCART Cables -Electronics > Electronics Accessories > Cables > Audio & Video Cables > Speaker Cables -Electronics > Electronics Accessories > Cables > Coaxial Cables -Electronics > Electronics Accessories > Cables > Data Transfer Cables -Electronics > Electronics Accessories > Cables > Data Transfer Cables > FireWire Cables -Electronics > Electronics Accessories > Cables > Data Transfer Cables > USB Cables -Electronics > Electronics Accessories > Cables > Data Transfer Cables > iOS Cables Electronics > Electronics Accessories > Cables > KVM Cables Electronics > Electronics Accessories > Cables > Network Cables -Electronics > Electronics Accessories > Cables > Network Cables > Ethernet Cables -Electronics > Electronics Accessories > Cables > Network Cables > Serial Cables -Electronics > Electronics Accessories > Cables > Optical Cables -Electronics > Electronics Accessories > Cables > RCA Cables -Electronics > Electronics Accessories > Cables > Storage Cables -Electronics > Electronics Accessories > Cables > Storage Cables > IDE Cables -Electronics > Electronics Accessories > Cables > Storage Cables > SAS Cables -Electronics > Electronics Accessories > Cables > Storage Cables > SATA Cables -Electronics > Electronics Accessories > Cables > Storage Cables > SCSI Cables +Electronics > Electronics Accessories > Cables > Storage & Data Transfer Cables Electronics > Electronics Accessories > Cables > System & Power Cables Electronics > Electronics Accessories > Cables > Telephone Cables +Electronics > Electronics Accessories > Computer Accessories +Electronics > Electronics Accessories > Computer Accessories > Computer Accessory Sets +Electronics > Electronics Accessories > Computer Accessories > Computer Covers & Skins +Electronics > Electronics Accessories > Computer Accessories > Computer Risers & Stands +Electronics > Electronics Accessories > Computer Accessories > Handheld Device Accessories +Electronics > Electronics Accessories > Computer Accessories > Handheld Device Accessories > E-Book Reader Accessories +Electronics > Electronics Accessories > Computer Accessories > Handheld Device Accessories > E-Book Reader Accessories > E-Book Reader Cases +Electronics > Electronics Accessories > Computer Accessories > Handheld Device Accessories > PDA Accessories +Electronics > Electronics Accessories > Computer Accessories > Handheld Device Accessories > PDA Accessories > PDA Cases +Electronics > Electronics Accessories > Computer Accessories > Keyboard & Mouse Wrist Rests +Electronics > Electronics Accessories > Computer Accessories > Keyboard Trays & Platforms +Electronics > Electronics Accessories > Computer Accessories > Laptop Docking Stations +Electronics > Electronics Accessories > Computer Accessories > Mouse Pads +Electronics > Electronics Accessories > Computer Accessories > Stylus Pen Nibs & Refills +Electronics > Electronics Accessories > Computer Accessories > Stylus Pens +Electronics > Electronics Accessories > Computer Accessories > Tablet Computer Docks & Stands +Electronics > Electronics Accessories > Computer Components +Electronics > Electronics Accessories > Computer Components > Blade Server Enclosures +Electronics > Electronics Accessories > Computer Components > Computer Backplates & I/O Shields +Electronics > Electronics Accessories > Computer Components > Computer Power Supplies +Electronics > Electronics Accessories > Computer Components > Computer Processors +Electronics > Electronics Accessories > Computer Components > Computer Racks & Mounts +Electronics > Electronics Accessories > Computer Components > Computer Starter Kits +Electronics > Electronics Accessories > Computer Components > Computer System Cooling Parts +Electronics > Electronics Accessories > Computer Components > Desktop Computer & Server Cases +Electronics > Electronics Accessories > Computer Components > E-Book Reader Parts +Electronics > Electronics Accessories > Computer Components > E-Book Reader Parts > E-Book Reader Screens & Screen Digitizers +Electronics > Electronics Accessories > Computer Components > I/O Cards & Adapters +Electronics > Electronics Accessories > Computer Components > I/O Cards & Adapters > Audio Cards & Adapters +Electronics > Electronics Accessories > Computer Components > I/O Cards & Adapters > Computer Interface Cards & Adapters +Electronics > Electronics Accessories > Computer Components > I/O Cards & Adapters > Riser Cards +Electronics > Electronics Accessories > Computer Components > I/O Cards & Adapters > TV Tuner Cards & Adapters +Electronics > Electronics Accessories > Computer Components > I/O Cards & Adapters > Video Cards & Adapters +Electronics > Electronics Accessories > Computer Components > Input Device Accessories +Electronics > Electronics Accessories > Computer Components > Input Device Accessories > Barcode Scanner Stands +Electronics > Electronics Accessories > Computer Components > Input Device Accessories > Game Controller Accessories +Electronics > Electronics Accessories > Computer Components > Input Device Accessories > Keyboard Keys & Caps +Electronics > Electronics Accessories > Computer Components > Input Device Accessories > Mice & Trackball Accessories +Electronics > Electronics Accessories > Computer Components > Input Devices +Electronics > Electronics Accessories > Computer Components > Input Devices > Barcode Scanners +Electronics > Electronics Accessories > Computer Components > Input Devices > Digital Note Taking Pens +Electronics > Electronics Accessories > Computer Components > Input Devices > Electronic Card Readers +Electronics > Electronics Accessories > Computer Components > Input Devices > Fingerprint Readers +Electronics > Electronics Accessories > Computer Components > Input Devices > Game Controllers +Electronics > Electronics Accessories > Computer Components > Input Devices > Gesture Control Input Devices +Electronics > Electronics Accessories > Computer Components > Input Devices > Graphics Tablets +Electronics > Electronics Accessories > Computer Components > Input Devices > KVM Switches +Electronics > Electronics Accessories > Computer Components > Input Devices > Keyboards +Electronics > Electronics Accessories > Computer Components > Input Devices > Memory Card Readers +Electronics > Electronics Accessories > Computer Components > Input Devices > Mice & Trackballs +Electronics > Electronics Accessories > Computer Components > Input Devices > Numeric Keypads +Electronics > Electronics Accessories > Computer Components > Input Devices > Touchpads +Electronics > Electronics Accessories > Computer Components > Laptop Parts +Electronics > Electronics Accessories > Computer Components > Laptop Parts > Laptop Hinges +Electronics > Electronics Accessories > Computer Components > Laptop Parts > Laptop Housings & Trim +Electronics > Electronics Accessories > Computer Components > Laptop Parts > Laptop Replacement Cables +Electronics > Electronics Accessories > Computer Components > Laptop Parts > Laptop Replacement Keyboards +Electronics > Electronics Accessories > Computer Components > Laptop Parts > Laptop Replacement Screens +Electronics > Electronics Accessories > Computer Components > Laptop Parts > Laptop Replacement Speakers +Electronics > Electronics Accessories > Computer Components > Laptop Parts > Laptop Screen Digitizers +Electronics > Electronics Accessories > Computer Components > Storage Devices +Electronics > Electronics Accessories > Computer Components > Storage Devices > Disk Duplicators +Electronics > Electronics Accessories > Computer Components > Storage Devices > Disk Duplicators > CD/DVD Duplicators +Electronics > Electronics Accessories > Computer Components > Storage Devices > Disk Duplicators > Hard Drive Duplicators +Electronics > Electronics Accessories > Computer Components > Storage Devices > Disk Duplicators > USB Drive Duplicators +Electronics > Electronics Accessories > Computer Components > Storage Devices > Floppy Drives +Electronics > Electronics Accessories > Computer Components > Storage Devices > Hard Drive Accessories +Electronics > Electronics Accessories > Computer Components > Storage Devices > Hard Drive Accessories > Hard Drive Carrying Cases +Electronics > Electronics Accessories > Computer Components > Storage Devices > Hard Drive Accessories > Hard Drive Docks +Electronics > Electronics Accessories > Computer Components > Storage Devices > Hard Drive Accessories > Hard Drive Enclosures & Mounts +Electronics > Electronics Accessories > Computer Components > Storage Devices > Hard Drive Arrays +Electronics > Electronics Accessories > Computer Components > Storage Devices > Hard Drives +Electronics > Electronics Accessories > Computer Components > Storage Devices > Network Storage Systems +Electronics > Electronics Accessories > Computer Components > Storage Devices > Optical Drives +Electronics > Electronics Accessories > Computer Components > Storage Devices > Tape Drives +Electronics > Electronics Accessories > Computer Components > Storage Devices > USB Flash Drives +Electronics > Electronics Accessories > Computer Components > Tablet Computer Parts +Electronics > Electronics Accessories > Computer Components > Tablet Computer Parts > Tablet Computer Housings & Trim +Electronics > Electronics Accessories > Computer Components > Tablet Computer Parts > Tablet Computer Replacement Speakers +Electronics > Electronics Accessories > Computer Components > Tablet Computer Parts > Tablet Computer Screens & Screen Digitizers +Electronics > Electronics Accessories > Computer Components > USB & FireWire Hubs Electronics > Electronics Accessories > Electronics Cleaners -Electronics > Electronics Accessories > Electronics Cleaners > Audio & Video Cleaners -Electronics > Electronics Accessories > Electronics Cleaners > Camera Cleaners -Electronics > Electronics Accessories > Electronics Cleaners > Screen Cleaners Electronics > Electronics Accessories > Electronics Films & Shields Electronics > Electronics Accessories > Electronics Films & Shields > Electronics Stickers & Decals -Electronics > Electronics Accessories > Electronics Films & Shields > Electronics Stickers & Decals > Computer Keyboard Stickers -Electronics > Electronics Accessories > Electronics Films & Shields > Electronics Stickers & Decals > Game Console Stickers & Decals -Electronics > Electronics Accessories > Electronics Films & Shields > Electronics Stickers & Decals > Laptop Stickers & Decals -Electronics > Electronics Accessories > Electronics Films & Shields > Electronics Stickers & Decals > MP3 Player Stickers & Decals -Electronics > Electronics Accessories > Electronics Films & Shields > Electronics Stickers & Decals > Mobile Phone Stickers & Decals -Electronics > Electronics Accessories > Electronics Films & Shields > Electronics Stickers & Decals > Tablet Computer Stickers & Decals Electronics > Electronics Accessories > Electronics Films & Shields > Keyboard Protectors Electronics > Electronics Accessories > Electronics Films & Shields > Privacy Filters Electronics > Electronics Accessories > Electronics Films & Shields > Screen Protectors @@ -1721,6 +1503,7 @@ Electronics > Electronics Accessories > Memory > ROM Electronics > Electronics Accessories > Memory > Video Memory Electronics > Electronics Accessories > Memory Accessories Electronics > Electronics Accessories > Memory Accessories > Memory Cases +Electronics > Electronics Accessories > Mobile Phone & Tablet Tripods & Monopods Electronics > Electronics Accessories > Power Electronics > Electronics Accessories > Power > Batteries Electronics > Electronics Accessories > Power > Batteries > Camera Batteries @@ -1728,56 +1511,50 @@ Electronics > Electronics Accessories > Power > Batteries > Cordless Phone Batte Electronics > Electronics Accessories > Power > Batteries > E-Book Reader Batteries Electronics > Electronics Accessories > Power > Batteries > General Purpose Batteries Electronics > Electronics Accessories > Power > Batteries > Laptop Batteries +Electronics > Electronics Accessories > Power > Batteries > MP3 Player Batteries Electronics > Electronics Accessories > Power > Batteries > Mobile Phone Batteries Electronics > Electronics Accessories > Power > Batteries > PDA Batteries Electronics > Electronics Accessories > Power > Batteries > Tablet Computer Batteries Electronics > Electronics Accessories > Power > Batteries > UPS Batteries Electronics > Electronics Accessories > Power > Batteries > Video Camera Batteries +Electronics > Electronics Accessories > Power > Batteries > Video Game Console & Controller Batteries Electronics > Electronics Accessories > Power > Battery Accessories +Electronics > Electronics Accessories > Power > Battery Accessories > Battery Charge Controllers +Electronics > Electronics Accessories > Power > Battery Accessories > Battery Holders Electronics > Electronics Accessories > Power > Battery Accessories > Camera Battery Chargers Electronics > Electronics Accessories > Power > Battery Accessories > General Purpose Battery Chargers -Electronics > Electronics Accessories > Power > Chargers -Electronics > Electronics Accessories > Power > Chargers > E-Book Reader Chargers -Electronics > Electronics Accessories > Power > Chargers > MP3 Player Chargers -Electronics > Electronics Accessories > Power > Chargers > Mobile Phone Chargers -Electronics > Electronics Accessories > Power > Chargers > PDA Chargers -Electronics > Electronics Accessories > Power > Chargers > Solar Chargers -Electronics > Electronics Accessories > Power > Chargers > Tablet Computer Chargers +Electronics > Electronics Accessories > Power > Battery Accessories > General Purpose Battery Testers Electronics > Electronics Accessories > Power > Fuel Cells -Electronics > Electronics Accessories > Power > Power Adapter Accessories -Electronics > Electronics Accessories > Power > Power Adapters -Electronics > Electronics Accessories > Power > Power Adapters > Laptop Power Adapters -Electronics > Electronics Accessories > Power > Power Adapters > Power Converters -Electronics > Electronics Accessories > Power > Power Adapters > Power Inverters -Electronics > Electronics Accessories > Power > Power Adapters > Travel Adapters -Electronics > Electronics Accessories > Power > Power Conditioners +Electronics > Electronics Accessories > Power > Power Adapter & Charger Accessories +Electronics > Electronics Accessories > Power > Power Adapters & Chargers Electronics > Electronics Accessories > Power > Power Control Units -Electronics > Electronics Accessories > Power > Power Enclosures -Electronics > Electronics Accessories > Power > Power Injectors & Splitters Electronics > Electronics Accessories > Power > Power Strips & Surge Suppressors +Electronics > Electronics Accessories > Power > Power Supply Enclosures Electronics > Electronics Accessories > Power > Surge Protection Devices +Electronics > Electronics Accessories > Power > Travel Converters & Adapters Electronics > Electronics Accessories > Power > UPS Electronics > Electronics Accessories > Power > UPS Accessories -Electronics > Electronics Accessories > Power > Voltage Converters Electronics > Electronics Accessories > Remote Controls Electronics > Electronics Accessories > Signal Boosters -Electronics > GPS -Electronics > GPS > Automotive GPS -Electronics > GPS > Aviation GPS -Electronics > GPS > Sport GPS +Electronics > Electronics Accessories > Signal Jammers +Electronics > Electronics Accessories > Signal Jammers > GPS Jammers +Electronics > Electronics Accessories > Signal Jammers > Mobile Phone Jammers +Electronics > Electronics Accessories > Signal Jammers > Radar Jammers Electronics > GPS Accessories Electronics > GPS Accessories > GPS Cases Electronics > GPS Accessories > GPS Mounts -Electronics > GPS Trackers +Electronics > GPS Navigation Systems +Electronics > GPS Tracking Devices Electronics > Marine Electronics Electronics > Marine Electronics > Fish Finders +Electronics > Marine Electronics > Marine Audio & Video Receivers Electronics > Marine Electronics > Marine Chartplotters & GPS Electronics > Marine Electronics > Marine Radar Electronics > Marine Electronics > Marine Radios +Electronics > Marine Electronics > Marine Speakers Electronics > Networking Electronics > Networking > Bridges & Routers Electronics > Networking > Bridges & Routers > Network Bridges -Electronics > Networking > Bridges & Routers > Network Bridges > Wireless Bridges Electronics > Networking > Bridges & Routers > VoIP Gateways & Routers Electronics > Networking > Bridges & Routers > Wireless Access Points Electronics > Networking > Bridges & Routers > Wireless Routers @@ -1786,39 +1563,31 @@ Electronics > Networking > Hubs & Switches Electronics > Networking > Modem Accessories Electronics > Networking > Modems Electronics > Networking > Network Cards & Adapters -Electronics > Networking > Network Cards & Adapters > XBox 360 Network Cards Electronics > Networking > Network Security & Firewall Devices -Electronics > Networking > Power Line Network Adapters +Electronics > Networking > Power Over Ethernet Adapters Electronics > Networking > Print Servers Electronics > Networking > Repeaters & Transceivers -Electronics > Plug & Play TV Games Electronics > Print, Copy, Scan & Fax -Electronics > Print, Copy, Scan & Fax > Fax Machines -Electronics > Print, Copy, Scan & Fax > Printers & Copiers -Electronics > Print, Copy, Scan & Fax > Printers & Copiers > Copiers -Electronics > Print, Copy, Scan & Fax > Printers & Copiers > Printers -Electronics > Print, Copy, Scan & Fax > Printers & Copiers > Printers > Dot Matrix Printers -Electronics > Print, Copy, Scan & Fax > Printers & Copiers > Printers > Inkjet Printers -Electronics > Print, Copy, Scan & Fax > Printers & Copiers > Printers > Laser Printers -Electronics > Print, Copy, Scan & Fax > Printers & Copiers > Printers > Photo Printers +Electronics > Print, Copy, Scan & Fax > 3D Printer Accessories +Electronics > Print, Copy, Scan & Fax > 3D Printers +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer Consumables +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer Consumables > Printer Drums & Drum Kits +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer Consumables > Printer Filters +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer Consumables > Printer Maintenance Kits +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer Consumables > Printer Ribbons +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer Consumables > Printheads +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer Consumables > Toner & Inkjet Cartridge Refills +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer Consumables > Toner & Inkjet Cartridges +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer Duplexers +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer Memory +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer Stands +Electronics > Print, Copy, Scan & Fax > Printer, Copier & Fax Machine Accessories > Printer, Copier & Fax Machine Replacement Parts +Electronics > Print, Copy, Scan & Fax > Printers, Copiers & Fax Machines +Electronics > Print, Copy, Scan & Fax > Scanner Accessories Electronics > Print, Copy, Scan & Fax > Scanners -Electronics > Print, Copy, Scan & Fax Accessories -Electronics > Print, Copy, Scan & Fax Accessories > Copier Accessories -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Consumables -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Consumables > Printer Filters -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Consumables > Printer Ribbons -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Consumables > Printheads -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Consumables > Toner & Inkjet Cartridge Refills -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Consumables > Toner & Inkjet Cartridges -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Consumables > Toner & Inkjet Cartridges > Oil Rollers -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Consumables > Toner & Inkjet Cartridges > Printer Drum Kits -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Duplexers -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Maintenance Kits -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Memory -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Stands -Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Printer Trays -Electronics > Print, Copy, Scan & Fax Accessories > Scanner Accessories +Electronics > Radar Detectors +Electronics > Speed Radars Electronics > Toll Collection Devices Electronics > Video Electronics > Video > Computer Monitors @@ -1830,10 +1599,6 @@ Electronics > Video > Satellite & Cable TV Electronics > Video > Satellite & Cable TV > Cable TV Receivers Electronics > Video > Satellite & Cable TV > Satellite Receivers Electronics > Video > Televisions -Electronics > Video > Televisions > CRT Televisions -Electronics > Video > Televisions > Flat Panel Televisions -Electronics > Video > Televisions > Portable Televisions -Electronics > Video > Televisions > Projection Televisions Electronics > Video > Video Accessories Electronics > Video > Video Accessories > 3D Glasses Electronics > Video > Video Accessories > Computer Monitor Accessories @@ -1845,158 +1610,58 @@ Electronics > Video > Video Accessories > Projector Accessories > Projection Scr Electronics > Video > Video Accessories > Projector Accessories > Projector Mounts Electronics > Video > Video Accessories > Projector Accessories > Projector Replacement Lamps Electronics > Video > Video Accessories > Rewinders -Electronics > Video > Video Accessories > Television Accessories -Electronics > Video > Video Accessories > Television Accessories > TV & Monitor Mounts -Electronics > Video > Video Accessories > Television Accessories > TV Replacement Lamps -Electronics > Video > Video Accessories > Television Accessories > TV Scan Converters +Electronics > Video > Video Accessories > Television Parts & Accessories +Electronics > Video > Video Accessories > Television Parts & Accessories > TV & Monitor Mounts +Electronics > Video > Video Accessories > Television Parts & Accessories > TV Converter Boxes +Electronics > Video > Video Accessories > Television Parts & Accessories > TV Replacement Lamps +Electronics > Video > Video Accessories > Television Parts & Accessories > TV Replacement Speakers Electronics > Video > Video Editing Hardware & Production Equipment Electronics > Video > Video Multiplexers Electronics > Video > Video Players & Recorders Electronics > Video > Video Players & Recorders > DVD & Blu-ray Players Electronics > Video > Video Players & Recorders > DVD Recorders -Electronics > Video > Video Players & Recorders > DVD Recorders > Blu-Ray Recorders -Electronics > Video > Video Players & Recorders > DVD Recorders > HD-DVD Recorders Electronics > Video > Video Players & Recorders > Digital Video Recorders -Electronics > Video > Video Players & Recorders > Home Media Players +Electronics > Video > Video Players & Recorders > Streaming & Home Media Players Electronics > Video > Video Players & Recorders > VCRs Electronics > Video > Video Servers Electronics > Video > Video Transmitters -Electronics > Video > Video Transmitters > Video Senders Electronics > Video Game Console Accessories Electronics > Video Game Console Accessories > Home Game Console Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > All PlayStation Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > All PlayStation Accessories > PlayStation (original) Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > All PlayStation Accessories > PlayStation 2 Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > All PlayStation Accessories > PlayStation 3 Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > All XBox Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > All XBox Accessories > XBox (original) Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > All XBox Accessories > XBox 360 Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > Dreamcast Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > GameCube Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > Jaguar Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > Neo Geo Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > Nintendo 64 Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > Nintendo Entertainment System Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > Sega Genesis Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > Sega Master System Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > Super Nintendo Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > Turbografx 16 Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > Wii Accessories -Electronics > Video Game Console Accessories > Home Game Console Accessories > Wii U Accessories Electronics > Video Game Console Accessories > Portable Game Console Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > All Game Boy Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > All Game Boy Accessories > Game Boy Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > All Game Boy Accessories > Game Boy Advance Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > Atari Lynx Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > Game Gear Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > N-Gage Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > Neo Geo Pocket Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > Nintendo 3DS Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > Nintendo DS Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > PlayStation Portable Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > PlayStation Vita Accessories -Electronics > Video Game Console Accessories > Portable Game Console Accessories > WonderSwan Accessories Electronics > Video Game Consoles -Electronics > Video Game Consoles > Home Game Consoles -Electronics > Video Game Consoles > Home Game Consoles > Dreamcast Consoles -Electronics > Video Game Consoles > Home Game Consoles > GameCube Consoles -Electronics > Video Game Consoles > Home Game Consoles > Jaguar Consoles -Electronics > Video Game Consoles > Home Game Consoles > Neo Geo Consoles -Electronics > Video Game Consoles > Home Game Consoles > Nintendo 64 Consoles -Electronics > Video Game Consoles > Home Game Consoles > Nintendo Entertainment System Consoles -Electronics > Video Game Consoles > Home Game Consoles > PlayStation (original) Consoles -Electronics > Video Game Consoles > Home Game Consoles > PlayStation 2 Consoles -Electronics > Video Game Consoles > Home Game Consoles > PlayStation 3 Consoles -Electronics > Video Game Consoles > Home Game Consoles > Sega Genesis Consoles -Electronics > Video Game Consoles > Home Game Consoles > Sega Master System Consoles -Electronics > Video Game Consoles > Home Game Consoles > Super Nintendo Consoles -Electronics > Video Game Consoles > Home Game Consoles > Turbografx 16 Consoles -Electronics > Video Game Consoles > Home Game Consoles > Wii Consoles -Electronics > Video Game Consoles > Home Game Consoles > Wii U Consoles -Electronics > Video Game Consoles > Home Game Consoles > XBox (original) Consoles -Electronics > Video Game Consoles > Home Game Consoles > XBox 360 Consoles -Electronics > Video Game Consoles > Portable Game Consoles -Electronics > Video Game Consoles > Portable Game Consoles > All Game Boy Consoles -Electronics > Video Game Consoles > Portable Game Consoles > All Game Boy Consoles > Game Boy Advance Consoles -Electronics > Video Game Consoles > Portable Game Consoles > All Game Boy Consoles > Game Boy Consoles -Electronics > Video Game Consoles > Portable Game Consoles > Atari Lynx Consoles -Electronics > Video Game Consoles > Portable Game Consoles > Game Gear Consoles -Electronics > Video Game Consoles > Portable Game Consoles > LeapFrog Consoles -Electronics > Video Game Consoles > Portable Game Consoles > N-Gage Consoles -Electronics > Video Game Consoles > Portable Game Consoles > Neo Geo Pocket Consoles -Electronics > Video Game Consoles > Portable Game Consoles > Nintendo 3DS -Electronics > Video Game Consoles > Portable Game Consoles > Nintendo DS Consoles -Electronics > Video Game Consoles > Portable Game Consoles > PlayStation Portable Consoles -Electronics > Video Game Consoles > Portable Game Consoles > PlayStation Vita -Electronics > Video Game Consoles > Portable Game Consoles > Retrogen -Electronics > Video Game Consoles > Portable Game Consoles > WonderSwan Food, Beverages & Tobacco Food, Beverages & Tobacco > Beverages -Food, Beverages & Tobacco > Beverages > Beer -Food, Beverages & Tobacco > Beverages > Beer > Non-Alcoholic Beer -Food, Beverages & Tobacco > Beverages > Bitters +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Beer +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Bitters +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Cocktail Mixes +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Flavored Alcoholic Beverages +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Hard Cider +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Liquor & Spirits +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Liquor & Spirits > Absinthe +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Liquor & Spirits > Brandy +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Liquor & Spirits > Gin +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Liquor & Spirits > Liqueurs +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Liquor & Spirits > Rum +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Liquor & Spirits > Shochu & Soju +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Liquor & Spirits > Tequila +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Liquor & Spirits > Vodka +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Liquor & Spirits > Whiskey +Food, Beverages & Tobacco > Beverages > Alcoholic Beverages > Wine Food, Beverages & Tobacco > Beverages > Buttermilk -Food, Beverages & Tobacco > Beverages > Cocktail Mixes Food, Beverages & Tobacco > Beverages > Coffee -Food, Beverages & Tobacco > Beverages > Coffee > Bottled Coffee Drinks -Food, Beverages & Tobacco > Beverages > Coffee > Coffee Pods -Food, Beverages & Tobacco > Beverages > Coffee > Ground & Whole Bean Coffee -Food, Beverages & Tobacco > Beverages > Coffee > Instant Coffee Food, Beverages & Tobacco > Beverages > Eggnog -Food, Beverages & Tobacco > Beverages > Flavored Malt Beverages Food, Beverages & Tobacco > Beverages > Fruit Flavored Drinks -Food, Beverages & Tobacco > Beverages > Hard Cider Food, Beverages & Tobacco > Beverages > Hot Chocolate Food, Beverages & Tobacco > Beverages > Juice -Food, Beverages & Tobacco > Beverages > Liquor & Spirits -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Brandy -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Brandy > Cognac -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Gin -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Liqueurs -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Rum -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Schnapps -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Tequila -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Vodka -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Whiskey -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Whiskey > American Whiskey -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Whiskey > Irish Whiskey -Food, Beverages & Tobacco > Beverages > Liquor & Spirits > Whiskey > Scotch Whiskey Food, Beverages & Tobacco > Beverages > Milk -Food, Beverages & Tobacco > Beverages > Milk > Powdered Milk Food, Beverages & Tobacco > Beverages > Non-Dairy Milk -Food, Beverages & Tobacco > Beverages > Non-Dairy Milk > Almond Milk -Food, Beverages & Tobacco > Beverages > Non-Dairy Milk > Coconut Milk -Food, Beverages & Tobacco > Beverages > Non-Dairy Milk > Hemp Milk -Food, Beverages & Tobacco > Beverages > Non-Dairy Milk > Oat Milk -Food, Beverages & Tobacco > Beverages > Non-Dairy Milk > Rice Milk -Food, Beverages & Tobacco > Beverages > Non-Dairy Milk > Soy Milk Food, Beverages & Tobacco > Beverages > Powdered Beverage Mixes Food, Beverages & Tobacco > Beverages > Soda Food, Beverages & Tobacco > Beverages > Sports & Energy Drinks Food, Beverages & Tobacco > Beverages > Tea & Infusions -Food, Beverages & Tobacco > Beverages > Tea & Infusions > Bottled Tea Drinks -Food, Beverages & Tobacco > Beverages > Tea & Infusions > Powdered Tea -Food, Beverages & Tobacco > Beverages > Tea & Infusions > Tea Bags & Loose Tea Leaves Food, Beverages & Tobacco > Beverages > Vinegar Drinks Food, Beverages & Tobacco > Beverages > Water -Food, Beverages & Tobacco > Beverages > Water > Bottled Water -Food, Beverages & Tobacco > Beverages > Water > Carbonated Water -Food, Beverages & Tobacco > Beverages > Water > Flavored Water -Food, Beverages & Tobacco > Beverages > Water > Tonic Water -Food, Beverages & Tobacco > Beverages > Wine -Food, Beverages & Tobacco > Beverages > Wine > Blush Wine -Food, Beverages & Tobacco > Beverages > Wine > Dessert Wine -Food, Beverages & Tobacco > Beverages > Wine > Dessert Wine > Port -Food, Beverages & Tobacco > Beverages > Wine > Dessert Wine > Sherry -Food, Beverages & Tobacco > Beverages > Wine > Dessert Wine > Vermouth -Food, Beverages & Tobacco > Beverages > Wine > Mead -Food, Beverages & Tobacco > Beverages > Wine > Non-Alcoholic Wine -Food, Beverages & Tobacco > Beverages > Wine > Red Wine -Food, Beverages & Tobacco > Beverages > Wine > Rice Wine -Food, Beverages & Tobacco > Beverages > Wine > Rosé Wine -Food, Beverages & Tobacco > Beverages > Wine > Sangria -Food, Beverages & Tobacco > Beverages > Wine > Sparkling Wine -Food, Beverages & Tobacco > Beverages > Wine > White Wine Food, Beverages & Tobacco > Food Items Food, Beverages & Tobacco > Food Items > Bakery Food, Beverages & Tobacco > Food Items > Bakery > Bagels @@ -2014,136 +1679,80 @@ Food, Beverages & Tobacco > Food Items > Bakery > Pastries & Scones Food, Beverages & Tobacco > Food Items > Bakery > Pies & Tarts Food, Beverages & Tobacco > Food Items > Bakery > Taco Shells & Tostadas Food, Beverages & Tobacco > Food Items > Bakery > Tortillas & Wraps -Food, Beverages & Tobacco > Food Items > Candy & Gum -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Candy Bars & Pieces -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Candy Brittle -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Candy Canes -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Candy Corn -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Caramels -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Chocolate Assortments -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Chocolate Bars -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Chocolate Covered Fruit -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Chocolate Truffles -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Fruit Chews -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Gumdrops -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Gummy Candies -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Hard Candies -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Jelly Beans -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Licorice -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Lollipops & Suckers -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Marshmallow Candies -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Mints -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Nougat -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Peanut Butter Cups -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Peppermint Patties -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Taffy -Food, Beverages & Tobacco > Food Items > Candy & Gum > Candy & Chocolate > Toffee -Food, Beverages & Tobacco > Food Items > Candy & Gum > Chewing Gum +Food, Beverages & Tobacco > Food Items > Candied & Chocolate Covered Fruit +Food, Beverages & Tobacco > Food Items > Candy & Chocolate Food, Beverages & Tobacco > Food Items > Condiments & Sauces -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Barbecue Sauce -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Chutney Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Cocktail Sauce -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Coffee Creamer Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Curry Sauce Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Dessert Toppings Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Dessert Toppings > Fruit Toppings Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Dessert Toppings > Ice Cream Syrup -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Dessert Toppings > Whipped Topping Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Fish Sauce Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Gravy Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Honey Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Horseradish Sauce Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Hot Sauce Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Ketchup -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Marinade +Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Marinades & Grilling Sauces Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Mayonnaise Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Mustard Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Olives & Capers Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Pasta Sauce -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Pickles & Relishes +Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Pickled Fruits & Vegetables Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Pizza Sauce +Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Relish & Chutney Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Salad Dressing Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Satay Sauce Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Soy Sauce -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Steak Sauce +Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Sweet and Sour Sauces Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Syrup -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Syrup > Agave Syrup -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Syrup > Flavored Syrup -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Syrup > Pancake & Maple Syrup -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Syrup > Snow Cone Syrup Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Tahini Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Tartar Sauce -Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Vinegar +Food, Beverages & Tobacco > Food Items > Condiments & Sauces > White & Cream Sauces Food, Beverages & Tobacco > Food Items > Condiments & Sauces > Worcestershire Sauce Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Artificial Sweeteners Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Chips Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Chocolate -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Extracts -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Extracts > Almond Extract -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Extracts > Vanilla Extract -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Kits +Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Flavors & Extracts Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Mixes -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Mixes > Bread & Muffin Mixes -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Mixes > Bread & Muffin Mixes > Bread Mixes -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Mixes > Bread & Muffin Mixes > Muffin Mixes -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Mixes > Bread Pudding Mixes -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Mixes > Cake & Brownie Mixes -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Mixes > Cookie Mixes -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Mixes > Pretzel Mixes -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Mixes > Waffle & Pancake Mixes Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Powder Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Baking Soda Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Batter & Coating Mixes Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Bean Paste Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Bread Crumbs -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Condensed Milk -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Canola Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Chili Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Coconut Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Cooking Spray -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Corn Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Hemp Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Olive Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Peanut Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Rice Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Sesame Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Soybean Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Vegetable Oil -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oil > Walnut Oil +Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Canned & Dry Milk +Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cookie Decorating Kits +Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Oils Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Starch Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Cooking Wine Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Corn Syrup Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Dough Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Dough > Bread & Pastry Dough -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Dough > Bread & Pastry Dough > Pizza Dough Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Dough > Cookie & Brownie Dough Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Dough > Pie Crusts Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Edible Baking Decorations -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Edible Baking Decorations > Colored Sugar -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Edible Baking Decorations > Edible Baking Decoration Kits -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Edible Baking Decorations > Sprinkles -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Edible Baking Decorations > Sugar Pearls -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Evaporated Milk Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Floss Sugar Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Flour Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Food Coloring Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Frosting & Icing +Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Lemon & Lime Juice Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Marshmallows +Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Meal Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Molasses -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Pie Fillings +Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Pie & Pastry Fillings Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Shortening & Lard -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Shredded Coconut Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Starter Cultures -Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Sugar +Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Sugar & Sweeteners +Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Tapioca Pearls Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Tomato Paste Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Unflavored Gelatin +Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Vinegar +Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Waffle & Pancake Mixes Food, Beverages & Tobacco > Food Items > Cooking & Baking Ingredients > Yeast Food, Beverages & Tobacco > Food Items > Dairy Products Food, Beverages & Tobacco > Food Items > Dairy Products > Butter & Margarine Food, Beverages & Tobacco > Food Items > Dairy Products > Cheese +Food, Beverages & Tobacco > Food Items > Dairy Products > Coffee Creamer Food, Beverages & Tobacco > Food Items > Dairy Products > Cottage Cheese Food, Beverages & Tobacco > Food Items > Dairy Products > Cream Food, Beverages & Tobacco > Food Items > Dairy Products > Sour Cream @@ -2151,37 +1760,29 @@ Food, Beverages & Tobacco > Food Items > Dairy Products > Whipped Cream Food, Beverages & Tobacco > Food Items > Dairy Products > Yogurt Food, Beverages & Tobacco > Food Items > Dips & Spreads Food, Beverages & Tobacco > Food Items > Dips & Spreads > Apple Butter -Food, Beverages & Tobacco > Food Items > Dips & Spreads > Cheese Spread +Food, Beverages & Tobacco > Food Items > Dips & Spreads > Cheese Dips & Spreads Food, Beverages & Tobacco > Food Items > Dips & Spreads > Cream Cheese Food, Beverages & Tobacco > Food Items > Dips & Spreads > Guacamole Food, Beverages & Tobacco > Food Items > Dips & Spreads > Hummus Food, Beverages & Tobacco > Food Items > Dips & Spreads > Jams & Jellies Food, Beverages & Tobacco > Food Items > Dips & Spreads > Nut Butters -Food, Beverages & Tobacco > Food Items > Dips & Spreads > Nut Butters > Almond Butter -Food, Beverages & Tobacco > Food Items > Dips & Spreads > Nut Butters > Hazelnut Butter -Food, Beverages & Tobacco > Food Items > Dips & Spreads > Nut Butters > Peanut Butter Food, Beverages & Tobacco > Food Items > Dips & Spreads > Salsa Food, Beverages & Tobacco > Food Items > Dips & Spreads > Tapenade Food, Beverages & Tobacco > Food Items > Dips & Spreads > Vegetable Dip Food, Beverages & Tobacco > Food Items > Food Gift Baskets Food, Beverages & Tobacco > Food Items > Frozen Desserts & Novelties -Food, Beverages & Tobacco > Food Items > Frozen Desserts & Novelties > Frozen Yogurt -Food, Beverages & Tobacco > Food Items > Frozen Desserts & Novelties > Ice Cream -Food, Beverages & Tobacco > Food Items > Frozen Desserts & Novelties > Nondairy Frozen Desserts -Food, Beverages & Tobacco > Food Items > Frozen Desserts & Novelties > Popsicles -Food, Beverages & Tobacco > Food Items > Frozen Desserts & Novelties > Sherbet -Food, Beverages & Tobacco > Food Items > Frozen Desserts & Novelties > Sorbet -Food, Beverages & Tobacco > Food Items > Frozen Desserts & Novelties > Sundae Cones +Food, Beverages & Tobacco > Food Items > Frozen Desserts & Novelties > Ice Cream & Frozen Yogurt +Food, Beverages & Tobacco > Food Items > Frozen Desserts & Novelties > Ice Cream Novelties +Food, Beverages & Tobacco > Food Items > Frozen Desserts & Novelties > Ice Pops Food, Beverages & Tobacco > Food Items > Fruits & Vegetables +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Canned & Jarred Fruits Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Canned & Jarred Vegetables Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Canned & Prepared Beans -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Canned Fruits Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Dried Fruits Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Dried Vegetables Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Dry Beans Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Apples -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Apricots Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Atemoyas Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Avocados Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Babacos @@ -2190,43 +1791,37 @@ Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fr Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Breadfruit Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Cactus Pears Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Cherimoyas -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Cherries +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Citrus Fruits +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Citrus Fruits > Grapefruits +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Citrus Fruits > Kumquats +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Citrus Fruits > Lemons +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Citrus Fruits > Limequats +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Citrus Fruits > Limes +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Citrus Fruits > Oranges +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Citrus Fruits > Tangelos Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Coconuts -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Currants Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Dates Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Feijoas Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Figs -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Grapefruits +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Fruit Mixes Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Grapes Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Guavas -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Homli Fruits -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Kiwifruit -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Kumquats -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Lemons -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Limequats -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Limes +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Homely Fruits +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Kiwis 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 > Mangoes Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Mangosteens Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Melons -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Melons > Bitter Melons -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Melons > Kiwanos -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Nectarines -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Oranges Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Papayas Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Passion Fruit -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Peaches Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Pears Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Persimmons Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Physalis Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Pineapples Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Pitahayas -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Plumcots -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Plums Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Pomegranates Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Quince Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Rambutans @@ -2234,21 +1829,21 @@ Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fr Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Sapote Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Soursops Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Starfruits +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Stone Fruits +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Stone Fruits > Apricots +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Stone Fruits > Cherries +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Stone Fruits > Mangoes +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Stone Fruits > Peaches & Nectarines +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Stone Fruits > Plumcots +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Stone Fruits > Plums Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Sugar Apples Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Tamarindo -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Tangelos -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Tangerines Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Alfafa Sprouts Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Arracachas Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Artichokes -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Arugula Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Asparagus -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Bean Sprouts Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Beans -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Beet Greens Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Beets -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Bok Choy Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Borage Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Broccoli Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Brussel Sprouts @@ -2259,14 +1854,6 @@ Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Ve Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Cauliflower Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Celery Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Celery Roots -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Chard -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Chick Peas -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Chicory -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Chicory > Belgian Endives -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Chicory > Endives -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Chicory > Escarole -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Chicory > Radicchio -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Choy Sum Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Corn Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Cucumbers Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Eggplants @@ -2277,56 +1864,57 @@ Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Ve Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Garlic Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Ginger Root Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Gobo Root -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Gourds Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > Arugula +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > Beet Greens +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > Bok Choy +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > Chard +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > Chicory +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > Choy Sum +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > Kale +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > Lettuce +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > On Choy +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > Salad Mixes +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > Spinach +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Greens > Yu Choy Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Horseradish Root Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Jicama -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Kale Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Kohlrabi Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Leeks -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Lettuce Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Lotus Roots Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Malangas Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Mushrooms Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Okra -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > On Choy Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Onions Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Parsley Roots Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Parsnips Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Peas Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Peppers Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Potatoes -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Pumpkins Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Radishes Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Rhubarb -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Rutabagas -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Salad Mixes Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Shallots -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Spinach -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Squashes +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Sprouts +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Squashes & Gourds Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Sugar Cane Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Sunchokes Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Sweet Potatoes Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Tamarillos Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Taro Root Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Tomatoes -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Turnips -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Tuscan Cabbage +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Turnips & Rutabagas +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Vegetable Mixes Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Water Chestnuts Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Watercress Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Wheatgrass -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Yu Choy +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Yams Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Yuca Root -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Vegetables > Ñame -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fruit Sauce -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fruit Sauce > Apple Sauce -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fruit Sauce > Cranberry Sauce +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fruit Sauces Food, Beverages & Tobacco > Food Items > Grains, Rice & Cereal Food, Beverages & Tobacco > Food Items > Grains, Rice & Cereal > Amaranth Food, Beverages & Tobacco > Food Items > Grains, Rice & Cereal > Barley Food, Beverages & Tobacco > Food Items > Grains, Rice & Cereal > Buckwheat Food, Beverages & Tobacco > Food Items > Grains, Rice & Cereal > Cereal & Granola -Food, Beverages & Tobacco > Food Items > Grains, Rice & Cereal > Cornmeal Food, Beverages & Tobacco > Food Items > Grains, Rice & Cereal > Couscous Food, Beverages & Tobacco > Food Items > Grains, Rice & Cereal > Millet Food, Beverages & Tobacco > Food Items > Grains, Rice & Cereal > Oats, Grits & Hot Cereal @@ -2340,104 +1928,28 @@ Food, Beverages & Tobacco > Food Items > Meat, Seafood & Eggs > Meat Food, Beverages & Tobacco > Food Items > Meat, Seafood & Eggs > Meat > Canned Meats Food, Beverages & Tobacco > Food Items > Meat, Seafood & Eggs > Meat > Fresh & Frozen Meats Food, Beverages & Tobacco > Food Items > Meat, Seafood & Eggs > Meat > Lunch & Deli Meats -Food, Beverages & Tobacco > Food Items > Meat, Seafood & Eggs > Meat > Meat Patties -Food, Beverages & Tobacco > Food Items > Meat, Seafood & Eggs > Meat > Sausages & Hot Dogs Food, Beverages & Tobacco > Food Items > Meat, Seafood & Eggs > Seafood Food, Beverages & Tobacco > Food Items > Meat, Seafood & Eggs > Seafood > Canned Seafood Food, Beverages & Tobacco > Food Items > Meat, Seafood & Eggs > Seafood > Fresh & Frozen Seafood Food, Beverages & Tobacco > Food Items > Nuts & Seeds -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Almonds -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Brazil Nuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Cashews -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Chestnuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Chocolate Covered Nuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Hazelnuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Kola Nuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Macadamia Nuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Mamoncillo Nuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Maya Nuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Mixed Nuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Ogbono Nuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Paradise Nuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Peanuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Pecans -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Pine Nuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Pistachios -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Pumpkin Seeds -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Sesame Seeds -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Soy Nuts -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Sunflower Seeds -Food, Beverages & Tobacco > Food Items > Nuts & Seeds > Walnuts Food, Beverages & Tobacco > Food Items > Pasta & Noodles Food, Beverages & Tobacco > Food Items > Prepared Foods -Food, Beverages & Tobacco > Food Items > Prepared Foods > Appetizers & Hors d'Oeuvres -Food, Beverages & Tobacco > Food Items > Prepared Foods > Frozen Dinners -Food, Beverages & Tobacco > Food Items > Prepared Foods > Prepared Entrées & Sides -Food, Beverages & Tobacco > Food Items > Prepared Foods > Prepared Entrées & Sides > Breakfast Sandwiches -Food, Beverages & Tobacco > Food Items > Prepared Foods > Prepared Entrées & Sides > Pizza -Food, Beverages & Tobacco > Food Items > Prepared Foods > Prepared Entrées & Sides > Prepared Entrées -Food, Beverages & Tobacco > Food Items > Prepared Foods > Prepared Entrées & Sides > Prepared Sandwiches -Food, Beverages & Tobacco > Food Items > Prepared Foods > Prepared Entrées & Sides > Prepared Side Dishes -Food, Beverages & Tobacco > Food Items > Prepared Foods > Prepared Entrées & Sides > Waffles, Pancakes & French Toast -Food, Beverages & Tobacco > Food Items > Prepared Foods > Skillet Dinners -Food, Beverages & Tobacco > Food Items > Prepared Foods > Sushi +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 > Seasonings & Spices Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Anise -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Basil -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Bay Leaves -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Chili Powder -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Chives -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Cilantro -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Cinnamon -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Cumin -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Dill -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Fennel Seeds -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Garlic Powder -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Lemon Grass -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Marjoram -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Mint -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Onion Powder -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Oregano -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Paprika -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Parsley -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Rosemary -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Sage -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Salsify -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Savory -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Sorrel -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Tarragon -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Thyme -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices > Vanilla Beans Food, Beverages & Tobacco > Food Items > Seasonings & Spices > MSG Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Pepper Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Salt -Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Seasoning & Spice Blends Food, Beverages & Tobacco > Food Items > Snack Foods Food, Beverages & Tobacco > Food Items > Snack Foods > Breadsticks Food, Beverages & Tobacco > Food Items > Snack Foods > Cereal & Granola Bars Food, Beverages & Tobacco > Food Items > Snack Foods > Cheese Puffs Food, Beverages & Tobacco > Food Items > Snack Foods > Chips -Food, Beverages & Tobacco > Food Items > Snack Foods > Chips > Bagel Chips -Food, Beverages & Tobacco > Food Items > Snack Foods > Chips > Corn & Tortilla Chips -Food, Beverages & Tobacco > Food Items > Snack Foods > Chips > Pita Chips -Food, Beverages & Tobacco > Food Items > Snack Foods > Chips > Potato Chips Food, Beverages & Tobacco > Food Items > Snack Foods > Crackers -Food, Beverages & Tobacco > Food Items > Snack Foods > Crackers > Cheese Crackers -Food, Beverages & Tobacco > Food Items > Snack Foods > Crackers > Cracker Assortments -Food, Beverages & Tobacco > Food Items > Snack Foods > Crackers > Flatbread Crackers -Food, Beverages & Tobacco > Food Items > Snack Foods > Crackers > Graham Crackers -Food, Beverages & Tobacco > Food Items > Snack Foods > Crackers > Matzo Crackers -Food, Beverages & Tobacco > Food Items > Snack Foods > Crackers > Oyster Crackers -Food, Beverages & Tobacco > Food Items > Snack Foods > Crackers > Saltine Crackers -Food, Beverages & Tobacco > Food Items > Snack Foods > Crackers > Sandwich Crackers -Food, Beverages & Tobacco > Food Items > Snack Foods > Crackers > Water Crackers Food, Beverages & Tobacco > Food Items > Snack Foods > Croutons Food, Beverages & Tobacco > Food Items > Snack Foods > Fruit Snacks Food, Beverages & Tobacco > Food Items > Snack Foods > Jerky -Food, Beverages & Tobacco > Food Items > Snack Foods > Jerky > Beef Jerky -Food, Beverages & Tobacco > Food Items > Snack Foods > Jerky > Pork Jerky -Food, Beverages & Tobacco > Food Items > Snack Foods > Jerky > Turkey Jerky Food, Beverages & Tobacco > Food Items > Snack Foods > Popcorn Food, Beverages & Tobacco > Food Items > Snack Foods > Pork Rinds Food, Beverages & Tobacco > Food Items > Snack Foods > Pretzels @@ -2449,9 +1961,6 @@ Food, Beverages & Tobacco > Food Items > Snack Foods > Snack Cakes Food, Beverages & Tobacco > Food Items > Snack Foods > Sticky Rice Cakes Food, Beverages & Tobacco > Food Items > Snack Foods > Trail & Snack Mixes Food, Beverages & Tobacco > Food Items > Soups & Broths -Food, Beverages & Tobacco > Food Items > Soups & Broths > Bouillon Cubes -Food, Beverages & Tobacco > Food Items > Soups & Broths > Canned Soups & Broths -Food, Beverages & Tobacco > Food Items > Soups & Broths > Dried Soups Food, Beverages & Tobacco > Food Items > Tofu, Soy & Vegetarian Products Food, Beverages & Tobacco > Food Items > Tofu, Soy & Vegetarian Products > Cheese Alternatives Food, Beverages & Tobacco > Food Items > Tofu, Soy & Vegetarian Products > Meat Alternatives @@ -2462,13 +1971,13 @@ Food, Beverages & Tobacco > Tobacco Products Food, Beverages & Tobacco > Tobacco Products > Chewing Tobacco Food, Beverages & Tobacco > Tobacco Products > Cigarettes Food, Beverages & Tobacco > Tobacco Products > Cigars -Food, Beverages & Tobacco > Tobacco Products > Electronic Cigarettes Food, Beverages & Tobacco > Tobacco Products > Loose Tobacco +Food, Beverages & Tobacco > Tobacco Products > Smoking Pipes +Food, Beverages & Tobacco > Tobacco Products > Vaporizers & Electronic Cigarettes Furniture Furniture > Baby & Toddler Furniture Furniture > Baby & Toddler Furniture > Baby & Toddler Furniture Sets Furniture > Baby & Toddler Furniture > Bassinet & Cradle Accessories -Furniture > Baby & Toddler Furniture > Bassinet & Cradle Accessories > Bassinet Stands Furniture > Baby & Toddler Furniture > Bassinets & Cradles Furniture > Baby & Toddler Furniture > Changing Tables Furniture > Baby & Toddler Furniture > Crib & Toddler Bed Accessories @@ -2476,21 +1985,13 @@ Furniture > Baby & Toddler Furniture > Crib & Toddler Bed Accessories > Crib Bum Furniture > Baby & Toddler Furniture > Crib & Toddler Bed Accessories > Crib Conversion Kits Furniture > Baby & Toddler Furniture > Cribs & Toddler Beds Furniture > Baby & Toddler Furniture > High Chair & Booster Seat Accessories -Furniture > Baby & Toddler Furniture > High Chairs & Boosters -Furniture > Baby & Toddler Furniture > Playroom Furniture +Furniture > Baby & Toddler Furniture > High Chairs & Booster Seats Furniture > Beds & Accessories -Furniture > Beds & Accessories > Bed Accessories -Furniture > Beds & Accessories > Bed Accessories > Waterbed Accessories -Furniture > Beds & Accessories > Bed Frames -Furniture > Beds & Accessories > Beds -Furniture > Beds & Accessories > Beds > Bunk Beds -Furniture > Beds & Accessories > Beds > Day Beds -Furniture > Beds & Accessories > Beds > Murphy Beds -Furniture > Beds & Accessories > Beds > Trundle Beds +Furniture > Beds & Accessories > Bed & Bed Frame Accessories +Furniture > Beds & Accessories > Beds & Bed Frames Furniture > Beds & Accessories > Headboards & Footboards Furniture > Beds & Accessories > Mattress Foundations Furniture > Beds & Accessories > Mattresses -Furniture > Beds & Accessories > Mattresses > Waterbeds Furniture > Benches Furniture > Benches > Kitchen & Dining Benches Furniture > Benches > Storage & Entryway Benches @@ -2501,11 +2002,11 @@ Furniture > Cabinets & Storage > Buffets & Sideboards Furniture > Cabinets & Storage > China Cabinets & Hutches Furniture > Cabinets & Storage > Dressers Furniture > Cabinets & Storage > File Cabinets +Furniture > Cabinets & Storage > Ironing Centers +Furniture > Cabinets & Storage > Kitchen Cabinets Furniture > Cabinets & Storage > Magazine Racks -Furniture > Cabinets & Storage > Media Storage -Furniture > Cabinets & Storage > Media Storage > Media Cabinets -Furniture > Cabinets & Storage > Media Storage > Media Racks & Towers -Furniture > Cabinets & Storage > Pantries +Furniture > Cabinets & Storage > Media Storage Cabinets & Racks +Furniture > Cabinets & Storage > Storage Cabinets & Lockers Furniture > Cabinets & Storage > Storage Chests Furniture > Cabinets & Storage > Storage Chests > Hope Chests Furniture > Cabinets & Storage > Storage Chests > Toy Chests @@ -2518,23 +2019,23 @@ Furniture > Carts & Islands Furniture > Carts & Islands > Kitchen & Dining Carts Furniture > Carts & Islands > Kitchen Islands Furniture > Chair Accessories +Furniture > Chair Accessories > Hanging Chair Replacement Parts Furniture > Chairs Furniture > Chairs > Arm Chairs, Recliners & Sleeper Chairs -Furniture > Chairs > Arm Chairs, Recliners & Sleeper Chairs > Arm Chairs -Furniture > Chairs > Arm Chairs, Recliners & Sleeper Chairs > Recliners -Furniture > Chairs > Arm Chairs, Recliners & Sleeper Chairs > Sleeper Chairs -Furniture > Chairs > Bar Stools Furniture > Chairs > Bean Bag Chairs Furniture > Chairs > Chaises Furniture > Chairs > Electric Massaging Chairs +Furniture > Chairs > Floor Chairs Furniture > Chairs > Folding Chairs & Stools Furniture > Chairs > Gaming Chairs Furniture > Chairs > Hanging Chairs Furniture > Chairs > Kitchen & Dining Room Chairs Furniture > Chairs > Rocking Chairs Furniture > Chairs > Slipper Chairs +Furniture > Chairs > Table & Bar Stools Furniture > Entertainment Centers & TV Stands Furniture > Furniture Sets +Furniture > Furniture Sets > Bathroom Furniture Sets Furniture > Furniture Sets > Bedroom Furniture Sets Furniture > Furniture Sets > Kitchen & Dining Furniture Sets Furniture > Furniture Sets > Living Room Furniture Sets @@ -2542,20 +2043,18 @@ Furniture > Futon Frames Furniture > Futon Pads Furniture > Futons Furniture > Office Furniture -Furniture > Office Furniture > Desk Hutches Furniture > Office Furniture > Desks -Furniture > Office Furniture > Office Chair Accessories Furniture > Office Furniture > Office Chairs -Furniture > Office Furniture > Office Chairs > Kneeling Chairs +Furniture > Office Furniture > Office Furniture Sets Furniture > Office Furniture > Workspace Tables Furniture > Office Furniture > Workspace Tables > Art & Drafting Tables Furniture > Office Furniture > Workspace Tables > Conference Room Tables -Furniture > Office Furniture > Workstation & Cubicle Accessories Furniture > Office Furniture > Workstations & Cubicles +Furniture > Office Furniture Accessories +Furniture > Office Furniture Accessories > Desk Parts & Accessories +Furniture > Office Furniture Accessories > Office Chair Accessories +Furniture > Office Furniture Accessories > Workstation & Cubicle Accessories Furniture > Ottomans -Furniture > Ottomans > Convertible Ottomans -Furniture > Ottomans > Glider Ottomans -Furniture > Ottomans > Storage Ottomans Furniture > Outdoor Furniture Furniture > Outdoor Furniture > Outdoor Beds Furniture > Outdoor Furniture > Outdoor Furniture Sets @@ -2563,6 +2062,7 @@ Furniture > Outdoor Furniture > Outdoor Ottomans Furniture > Outdoor Furniture > Outdoor Seating Furniture > Outdoor Furniture > Outdoor Seating > Outdoor Benches Furniture > Outdoor Furniture > Outdoor Seating > Outdoor Chairs +Furniture > Outdoor Furniture > Outdoor Seating > Outdoor Sectional Sofa Units Furniture > Outdoor Furniture > Outdoor Seating > Outdoor Sofas Furniture > Outdoor Furniture > Outdoor Seating > Sunloungers Furniture > Outdoor Furniture > Outdoor Storage Boxes @@ -2573,14 +2073,13 @@ Furniture > Room Divider Accessories Furniture > Room Dividers Furniture > Shelving Furniture > Shelving > Bookcases & Standing Shelves +Furniture > Shelving > Wall Shelves & Ledges Furniture > Shelving Accessories Furniture > Shelving Accessories > Replacement Shelves Furniture > Sofa Accessories Furniture > Sofa Accessories > Chair & Sofa Supports +Furniture > Sofa Accessories > Sectional Sofa Units Furniture > Sofas -Furniture > Sofas > Loveseats -Furniture > Sofas > Sectional Sofas -Furniture > Sofas > Sofabeds Furniture > Table Accessories Furniture > Table Accessories > Table Legs Furniture > Table Accessories > Table Tops @@ -2592,276 +2091,238 @@ Furniture > Tables > Accent Tables > Sofa Tables Furniture > Tables > Activity Tables Furniture > Tables > Folding Tables Furniture > Tables > Kitchen & Dining Room Tables -Furniture > Tables > Kitchen & Dining Room Tables > Hightop Tables +Furniture > Tables > Kotatsu Furniture > Tables > Nightstands Furniture > Tables > Poker & Game Tables Furniture > Tables > Sewing Machine Tables Hardware -Hardware > Adhesives, Coatings & Sealants -Hardware > Adhesives, Coatings & Sealants > Caulk -Hardware > Adhesives, Coatings & Sealants > Cements -Hardware > Adhesives, Coatings & Sealants > Coatings -Hardware > Adhesives, Coatings & Sealants > Glues -Hardware > Adhesives, Coatings & Sealants > Glues > Glitter Glue -Hardware > Adhesives, Coatings & Sealants > Glues > Glue Gun Sticks -Hardware > Adhesives, Coatings & Sealants > Glues > Glue Sticks -Hardware > Adhesives, Coatings & Sealants > Glues > Rubber Cement -Hardware > Adhesives, Coatings & Sealants > Glues > Super Glue -Hardware > Adhesives, Coatings & Sealants > Glues > White Glue -Hardware > Adhesives, Coatings & Sealants > Glues > Wood Glue -Hardware > Adhesives, Coatings & Sealants > Joint Compounds -Hardware > Adhesives, Coatings & Sealants > Sealants +Hardware > Building Consumables +Hardware > Building Consumables > Chemicals +Hardware > Building Consumables > Chemicals > Acid Neutralizers +Hardware > Building Consumables > Chemicals > Ammonia +Hardware > Building Consumables > Chemicals > Chimney Cleaners +Hardware > Building Consumables > Chemicals > Concrete & Masonry Cleaners +Hardware > Building Consumables > Chemicals > De-icers +Hardware > Building Consumables > Chemicals > Deck & Fence Cleaners +Hardware > Building Consumables > Chemicals > Drain Cleaners +Hardware > Building Consumables > Chemicals > Electrical Freeze Sprays +Hardware > Building Consumables > Chemicals > Lighter Fluid +Hardware > Building Consumables > Chemicals > Septic Tank & Cesspool Treatments +Hardware > Building Consumables > Hardware Glue & Adhesives +Hardware > Building Consumables > Hardware Tape +Hardware > Building Consumables > Lubricants +Hardware > Building Consumables > Masonry Consumables +Hardware > Building Consumables > Masonry Consumables > Bricks & Concrete Blocks +Hardware > Building Consumables > Masonry Consumables > Cement, Mortar & Concrete Mixes +Hardware > Building Consumables > Masonry Consumables > Grout +Hardware > Building Consumables > Painting Consumables +Hardware > Building Consumables > Painting Consumables > Paint +Hardware > Building Consumables > Painting Consumables > Paint Binders +Hardware > Building Consumables > Painting Consumables > Primers +Hardware > Building Consumables > Painting Consumables > Stains +Hardware > Building Consumables > Painting Consumables > Varnishes & Finishes +Hardware > Building Consumables > Plumbing Primer +Hardware > Building Consumables > Protective Coatings & Sealants +Hardware > Building Consumables > Solder & Flux +Hardware > Building Consumables > Solvents, Strippers & Thinners +Hardware > Building Consumables > Wall Patching Compounds & Plaster Hardware > Building Materials -Hardware > Building Materials > Bricks, Stones & Concrete -Hardware > Building Materials > Carpets Hardware > Building Materials > Countertops -Hardware > Building Materials > Countertops > Ceramic & Porcelain Countertops -Hardware > Building Materials > Countertops > Granite Countertops -Hardware > Building Materials > Countertops > Hardwood Countertops -Hardware > Building Materials > Countertops > Laminate Countertops -Hardware > Building Materials > Countertops > Marble Countertops -Hardware > Building Materials > Countertops > Vinyl Countertops +Hardware > Building Materials > Door Hardware +Hardware > Building Materials > Door Hardware > Door Bells & Chimes +Hardware > Building Materials > Door Hardware > Door Closers +Hardware > Building Materials > Door Hardware > Door Frames +Hardware > Building Materials > Door Hardware > Door Keyhole Escutcheons +Hardware > Building Materials > Door Hardware > Door Knobs & Handles +Hardware > Building Materials > Door Hardware > Door Knockers +Hardware > Building Materials > Door Hardware > Door Push Plates +Hardware > Building Materials > Door Hardware > Door Stops +Hardware > Building Materials > Door Hardware > Door Strikes Hardware > Building Materials > Doors Hardware > Building Materials > Doors > Garage Doors Hardware > Building Materials > Doors > Home Doors -Hardware > Building Materials > Doors > Home Doors > Screen Doors -Hardware > Building Materials > Doors > Utility Doors -Hardware > Building Materials > Doors > Utility Doors > Pet Doors -Hardware > Building Materials > Flooring -Hardware > Building Materials > Flooring > Artificial Turf -Hardware > Building Materials > Flooring > Ceramic & Porcelain Flooring -Hardware > Building Materials > Flooring > Granite Flooring -Hardware > Building Materials > Flooring > Hardwood Flooring -Hardware > Building Materials > Flooring > Laminate Flooring -Hardware > Building Materials > Flooring > Marble Flooring -Hardware > Building Materials > Flooring > Rubber Flooring -Hardware > Building Materials > Flooring > Vinyl Flooring +Hardware > Building Materials > Drywall +Hardware > Building Materials > Flooring & Carpet Hardware > Building Materials > Glass +Hardware > Building Materials > Handrails & Railing Systems Hardware > Building Materials > Hatches -Hardware > Building Materials > Lumber +Hardware > Building Materials > Insulation +Hardware > Building Materials > Lumber & Sheet Stock Hardware > Building Materials > Molding +Hardware > Building Materials > Rebar & Remesh +Hardware > Building Materials > Roofing +Hardware > Building Materials > Roofing > Gutter Accessories +Hardware > Building Materials > Roofing > Gutters +Hardware > Building Materials > Roofing > Roof Flashings +Hardware > Building Materials > Roofing > Roofing Shingles & Tiles Hardware > Building Materials > Shutters -Hardware > Building Materials > Sound Dampening Materials +Hardware > Building Materials > Siding +Hardware > Building Materials > Sound Dampening Panels & Foam Hardware > Building Materials > Staircases -Hardware > Building Materials > Tile -Hardware > Building Materials > Tile > Tile Countertops -Hardware > Building Materials > Tile > Tile Flooring +Hardware > Building Materials > Wall & Ceiling Tile Hardware > Building Materials > Wall Paneling +Hardware > Building Materials > Weather Stripping & Weatherization Supplies +Hardware > Building Materials > Window Hardware +Hardware > Building Materials > Window Hardware > Window Cranks +Hardware > Building Materials > Window Hardware > Window Frames Hardware > Building Materials > Windows -Hardware > Cabinetry -Hardware > Chemicals -Hardware > Chemicals > Acid Neutralizers -Hardware > Chemicals > Ammonia -Hardware > Chemicals > Chimney Cleaners -Hardware > Chemicals > Concrete & Masonry Cleaners -Hardware > Chemicals > De-icers -Hardware > Chemicals > Deck & Fence Cleaners -Hardware > Chemicals > Drain Cleaners -Hardware > Chemicals > Septic Tank & Cesspool Cleaners -Hardware > Electrical Supplies -Hardware > Electrical Supplies > Ballasts & Starters -Hardware > Electrical Supplies > Bypass Switches -Hardware > Electrical Supplies > Charge Controllers -Hardware > Electrical Supplies > Circuit Breaker Panels -Hardware > Electrical Supplies > Electrical Plug Caps -Hardware > Electrical Supplies > Electrical Switches -Hardware > Electrical Supplies > Electrical Switches > Light Switches -Hardware > Electrical Supplies > Electrical Timers -Hardware > Electrical Supplies > Electrical Wires -Hardware > Electrical Supplies > Extension Cord Accessories -Hardware > Electrical Supplies > Extension Cord Accessories > Extension Cord Connectors -Hardware > Electrical Supplies > Extension Cord Accessories > Extension Cord Reels & Winders -Hardware > Electrical Supplies > Extension Cords -Hardware > Electrical Supplies > Heat-Shrink Tubing -Hardware > Electrical Supplies > Mount Boxes & Brackets -Hardware > Electrical Supplies > Relays -Hardware > Electrical Supplies > Wall Plates -Hardware > Electrical Supplies > Wall Sockets -Hardware > Electrical Supplies > Wire Binding Spools -Hardware > Electrical Supplies > Wire Caps & Nuts -Hardware > Generators +Hardware > Fencing & Barriers +Hardware > Fencing & Barriers > Fence & Gate Accessories +Hardware > Fencing & Barriers > Fence Panels +Hardware > Fencing & Barriers > Fence Pickets +Hardware > Fencing & Barriers > Fence Posts & Rails +Hardware > Fencing & Barriers > Garden Borders & Edging +Hardware > Fencing & Barriers > Gates +Hardware > Fencing & Barriers > Lattice +Hardware > Fencing & Barriers > Safety & Crowd Control Barriers +Hardware > Fuel Containers & Tanks Hardware > Hardware Accessories -Hardware > Hardware Accessories > Brackets -Hardware > Hardware Accessories > Bungee Cords +Hardware > Hardware Accessories > Brackets & Reinforcement Braces Hardware > Hardware Accessories > Cabinet Hardware +Hardware > Hardware Accessories > Cabinet Hardware > Cabinet & Furniture Keyhole Escutcheons Hardware > Hardware Accessories > Cabinet Hardware > Cabinet Backplates Hardware > Hardware Accessories > Cabinet Hardware > Cabinet Catches Hardware > Hardware Accessories > Cabinet Hardware > Cabinet Doors Hardware > Hardware Accessories > Cabinet Hardware > Cabinet Knobs & Handles -Hardware > Hardware Accessories > Cabinet Hardware > Cabinet Locks -Hardware > Hardware Accessories > Chains +Hardware > Hardware Accessories > Casters +Hardware > Hardware Accessories > Chain, Wire & Rope +Hardware > Hardware Accessories > Chain, Wire & Rope > Bungee Cords +Hardware > Hardware Accessories > Chain, Wire & Rope > Chains +Hardware > Hardware Accessories > Chain, Wire & Rope > Pull Chains +Hardware > Hardware Accessories > Chain, Wire & Rope > Ropes & Hardware Cable +Hardware > Hardware Accessories > Chain, Wire & Rope > Tie Down Straps +Hardware > Hardware Accessories > Chain, Wire & Rope > Twine +Hardware > Hardware Accessories > Chain, Wire & Rope > Utility Wire Hardware > Hardware Accessories > Coils Hardware > Hardware Accessories > Concrete Molds -Hardware > Hardware Accessories > Controls -Hardware > Hardware Accessories > Controls > Control Panels -Hardware > Hardware Accessories > Controls > HVAC Controls -Hardware > Hardware Accessories > Controls > Thermostats -Hardware > Hardware Accessories > Door Hardware -Hardware > Hardware Accessories > Door Hardware > Door Bells & Chimes -Hardware > Hardware Accessories > Door Hardware > Door Closers -Hardware > Hardware Accessories > Door Hardware > Door Knobs & Handles -Hardware > Hardware Accessories > Door Hardware > Door Knockers -Hardware > Hardware Accessories > Door Hardware > Door Stops -Hardware > Hardware Accessories > Door Hardware > Door Strikes Hardware > Hardware Accessories > Dowel Pins & Rods +Hardware > Hardware Accessories > Drawer Slides Hardware > Hardware Accessories > Drop Cloths -Hardware > Hardware Accessories > Drywall Anchors Hardware > Hardware Accessories > Filters & Screens Hardware > Hardware Accessories > Flagging & Caution Tape Hardware > Hardware Accessories > Gas Hoses -Hardware > Hardware Accessories > Hardware Tape -Hardware > Hardware Accessories > Hardware Tape > Copper Tape -Hardware > Hardware Accessories > Hardware Tape > Drywall Tape -Hardware > Hardware Accessories > Hardware Tape > Duct Tape -Hardware > Hardware Accessories > Hardware Tape > Electrical Tape -Hardware > Hardware Accessories > Hardware Tape > Foil Tape -Hardware > Hardware Accessories > Hardware Tape > Gaffers Tape -Hardware > Hardware Accessories > Hardware Tape > Heat Tape -Hardware > Hardware Accessories > Hardware Tape > Masking Tape -Hardware > Hardware Accessories > Hardware Tape > Teflon Tape +Hardware > Hardware Accessories > Ground Spikes +Hardware > Hardware Accessories > Hardware Fasteners +Hardware > Hardware Accessories > Hardware Fasteners > Drywall Anchors +Hardware > Hardware Accessories > Hardware Fasteners > Nails +Hardware > Hardware Accessories > Hardware Fasteners > Nuts & Bolts +Hardware > Hardware Accessories > Hardware Fasteners > Rivets +Hardware > Hardware Accessories > Hardware Fasteners > Screw Posts +Hardware > Hardware Accessories > Hardware Fasteners > Screws +Hardware > Hardware Accessories > Hardware Fasteners > Threaded Rods +Hardware > Hardware Accessories > Hardware Fasteners > Washers Hardware > Hardware Accessories > Hinges -Hardware > Hardware Accessories > Latches -Hardware > Hardware Accessories > Lubricants +Hardware > Hardware Accessories > Hooks, Buckles & Fasteners +Hardware > Hardware Accessories > Hooks, Buckles & Fasteners > Chain Connectors & Links +Hardware > Hardware Accessories > Hooks, Buckles & Fasteners > Gear Ties +Hardware > Hardware Accessories > Hooks, Buckles & Fasteners > Lifting Hooks, Clamps & Shackles +Hardware > Hardware Accessories > Hooks, Buckles & Fasteners > Utility Buckles Hardware > Hardware Accessories > Lubrication Hoses -Hardware > Hardware Accessories > Nails -Hardware > Hardware Accessories > Nuts & Bolts +Hardware > Hardware Accessories > Metal Casting Molds +Hardware > Hardware Accessories > Moving & Soundproofing Blankets & Covers Hardware > Hardware Accessories > Pneumatic Hoses -Hardware > Hardware Accessories > Pull Chains -Hardware > Hardware Accessories > Pulleys -Hardware > Hardware Accessories > Rivets -Hardware > Hardware Accessories > Rope -Hardware > Hardware Accessories > Screw Posts -Hardware > Hardware Accessories > Screws -Hardware > Hardware Accessories > Screws > Drywall Screws -Hardware > Hardware Accessories > Screws > Metal Screws -Hardware > Hardware Accessories > Screws > Wood Screws -Hardware > Hardware Accessories > Solder & Flux +Hardware > Hardware Accessories > Post Base Plates +Hardware > Hardware Accessories > Springs Hardware > Hardware Accessories > Tarps -Hardware > Hardware Accessories > Tie Down Straps Hardware > Hardware Accessories > Tool Storage & Organization Hardware > Hardware Accessories > Tool Storage & Organization > Garden Hose Storage -Hardware > Hardware Accessories > Tool Storage & Organization > Tool & Duty Belts +Hardware > Hardware Accessories > Tool Storage & Organization > Tool & Equipment Belts Hardware > Hardware Accessories > Tool Storage & Organization > Tool Bags Hardware > Hardware Accessories > Tool Storage & Organization > Tool Boxes Hardware > Hardware Accessories > Tool Storage & Organization > Tool Cabinets & Chests +Hardware > Hardware Accessories > Tool Storage & Organization > Tool Organizer Liners & Inserts Hardware > Hardware Accessories > Tool Storage & Organization > Tool Sheaths Hardware > Hardware Accessories > Tool Storage & Organization > Work Benches -Hardware > Hardware Accessories > Twine -Hardware > Hardware Accessories > Utility Wire -Hardware > Hardware Accessories > Vents & Flues -Hardware > Hardware Accessories > Washers -Hardware > Hardware Accessories > Wood Biscuits -Hardware > Hardware Torches -Hardware > Home Fencing -Hardware > Home Fencing > Fence Pickets -Hardware > Home Fencing > Fences -Hardware > Home Fencing > Fences > Wire Fencing -Hardware > Home Fencing > Fences > Wire Fencing > Barbed & Razor Wire -Hardware > Home Fencing > Fences > Wire Fencing > Chain Link Fencing -Hardware > Home Fencing > Fencing Posts -Hardware > Home Fencing > Gates -Hardware > Insulation -Hardware > Insulation > Building Insulation -Hardware > Insulation > Pipe Insulation -Hardware > Insulation > Window & Door Insulation -Hardware > Key Blanks -Hardware > Locks & Locksmithing -Hardware > Locks & Locksmithing > Cylinder Locks -Hardware > Locks & Locksmithing > Deadbolt Locks -Hardware > Locks & Locksmithing > Key Card Entry Systems -Hardware > Locks & Locksmithing > Padlocks -Hardware > Locks & Locksmithing > Pocket Door Locks -Hardware > Painting & Wall Covering Supplies -Hardware > Painting & Wall Covering Supplies > Finishes -Hardware > Painting & Wall Covering Supplies > Paint -Hardware > Painting & Wall Covering Supplies > Paint Binders -Hardware > Painting & Wall Covering Supplies > Paint Strippers -Hardware > Painting & Wall Covering Supplies > Primers -Hardware > Painting & Wall Covering Supplies > Solvents -Hardware > Painting & Wall Covering Supplies > Stains -Hardware > Painting & Wall Covering Supplies > Varnishes -Hardware > Painting & Wall Covering Supplies > Wallpaper +Hardware > Hardware Accessories > Wall Jacks & Braces +Hardware > Hardware Pumps +Hardware > Hardware Pumps > Home Appliance Pumps +Hardware > Hardware Pumps > Pool, Fountain & Pond Pumps +Hardware > Hardware Pumps > Sprinkler, Booster & Irrigation System Pumps +Hardware > Hardware Pumps > Sump, Sewage & Effluent Pumps +Hardware > Hardware Pumps > Utility Pumps +Hardware > Hardware Pumps > Well Pumps & Systems +Hardware > Heating, Ventilation & Air Conditioning +Hardware > Heating, Ventilation & Air Conditioning > Air & Filter Dryers +Hardware > Heating, Ventilation & Air Conditioning > Air Ducts +Hardware > Heating, Ventilation & Air Conditioning > HVAC Controls +Hardware > Heating, Ventilation & Air Conditioning > HVAC Controls > Control Panels +Hardware > Heating, Ventilation & Air Conditioning > HVAC Controls > Humidistats +Hardware > Heating, Ventilation & Air Conditioning > HVAC Controls > Thermostats +Hardware > Heating, Ventilation & Air Conditioning > Vents & Flues +Hardware > Locks & Keys +Hardware > Locks & Keys > Key Blanks +Hardware > Locks & Keys > Key Caps +Hardware > Locks & Keys > Key Card Entry Systems +Hardware > Locks & Keys > Locks & Latches Hardware > Plumbing -Hardware > Plumbing > Ballcocks & Flappers -Hardware > Plumbing > Drain Accessories -Hardware > Plumbing > Drain Accessories > Drain Frames -Hardware > Plumbing > Drain Accessories > Drain Liners -Hardware > Plumbing > Drain Accessories > Drain Openers -Hardware > Plumbing > Drain Accessories > Drain Rods -Hardware > Plumbing > Drain Accessories > Plumbing Wastes -Hardware > Plumbing > Fuel Lines & Accessories -Hardware > Plumbing > Hoses -Hardware > Plumbing > Nozzles -Hardware > Plumbing > Plumbing Fittings -Hardware > Plumbing > Plumbing Fittings > Flanges -Hardware > Plumbing > Plumbing Fittings > Pipe Adaptors & Bushings -Hardware > Plumbing > Plumbing Fittings > Pipe Caps & Plugs -Hardware > Plumbing > Plumbing Fittings > Pipe Clamps -Hardware > Plumbing > Plumbing Fittings > Pipe Connectors -Hardware > Plumbing > Plumbing Fittings > Plumbing Gaskets +Hardware > Plumbing > Plumbing Fittings & Supports +Hardware > Plumbing > Plumbing Fittings & Supports > Gaskets & O-Rings +Hardware > Plumbing > Plumbing Fittings & Supports > In-Wall Carriers & Mounting Frames +Hardware > Plumbing > Plumbing Fittings & Supports > Nozzles +Hardware > Plumbing > Plumbing Fittings & Supports > Pipe Adapters & Bushings +Hardware > Plumbing > Plumbing Fittings & Supports > Pipe Caps & Plugs +Hardware > Plumbing > Plumbing Fittings & Supports > Pipe Connectors +Hardware > Plumbing > Plumbing Fittings & Supports > Plumbing Flanges +Hardware > Plumbing > Plumbing Fittings & Supports > Plumbing Pipe Clamps +Hardware > Plumbing > Plumbing Fittings & Supports > Plumbing Regulators +Hardware > Plumbing > Plumbing Fittings & Supports > Plumbing Valves +Hardware > Plumbing > Plumbing Fixture Hardware & Parts +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Bathtub Accessories +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Bathtub Accessories > Bathtub Bases & Feet +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Bathtub Accessories > Bathtub Skirts +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Bathtub Accessories > Bathtub Spouts +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Drain Components +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Drain Components > Drain Covers & Strainers +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Drain Components > Drain Frames +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Drain Components > Drain Liners +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Drain Components > Drain Openers +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Drain Components > Drain Rods +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Drain Components > Plumbing Traps +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Drain Components > Plumbing Wastes +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Drains +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Faucet Accessories +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Faucet Accessories > Faucet Aerators +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Faucet Accessories > Faucet Handles & Controls +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Fixture Plates +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Shower Parts +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Shower Parts > Bathtub & Shower Jets +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Shower Parts > Electric & Power Showers +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Shower Parts > Shower Arms & Connectors +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Shower Parts > Shower Bases +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Shower Parts > Shower Columns +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Shower Parts > Shower Doors & Enclosures +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Shower Parts > Shower Heads +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Shower Parts > Shower Walls & Surrounds +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Shower Parts > Shower Water Filters +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Sink Accessories +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Sink Accessories > Sink Legs +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Toilet & Bidet Accessories +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Toilet & Bidet Accessories > Ballcocks & Flappers +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Toilet & Bidet Accessories > Bidet Faucets & Sprayers +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Toilet & Bidet Accessories > Toilet & Bidet Seats +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Toilet & Bidet Accessories > Toilet Seat Covers +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Toilet & Bidet Accessories > Toilet Seat Lid Covers +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Toilet & Bidet Accessories > Toilet Tank Covers +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Toilet & Bidet Accessories > Toilet Tank Levers +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Toilet & Bidet Accessories > Toilet Tanks +Hardware > Plumbing > Plumbing Fixture Hardware & Parts > Toilet & Bidet Accessories > Toilet Trim Hardware > Plumbing > Plumbing Fixtures -Hardware > Plumbing > Plumbing Fixtures > Bathtub Accessories -Hardware > Plumbing > Plumbing Fixtures > Bathtub Accessories > Bathtub Skirts -Hardware > Plumbing > Plumbing Fixtures > Bathtub Accessories > Bathtub Spouts +Hardware > Plumbing > Plumbing Fixtures > Bathroom Suites Hardware > Plumbing > Plumbing Fixtures > Bathtubs -Hardware > Plumbing > Plumbing Fixtures > Faucet Accessories Hardware > Plumbing > Plumbing Fixtures > Faucets -Hardware > Plumbing > Plumbing Fixtures > Faucets > Pre-Rinse Faucets -Hardware > Plumbing > Plumbing Fixtures > Fixture Plates -Hardware > Plumbing > Plumbing Fixtures > Shower -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Arm Diverters -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Arm Mounts -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Bars -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Bases -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Columns -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Doors & Enclosures -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Heads -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Hoses -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Rods -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Stalls & Kits -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Walls & Surrounds -Hardware > Plumbing > Plumbing Fixtures > Shower > Shower Water Filters -Hardware > Plumbing > Plumbing Fixtures > Sink Accessories -Hardware > Plumbing > Plumbing Fixtures > Sink Accessories > Sink Legs +Hardware > Plumbing > Plumbing Fixtures > Shower Stalls & Kits Hardware > Plumbing > Plumbing Fixtures > Sinks Hardware > Plumbing > Plumbing Fixtures > Sinks > Bathroom Sinks Hardware > Plumbing > Plumbing Fixtures > Sinks > Kitchen & Utility Sinks -Hardware > Plumbing > Plumbing Fixtures > Toilet & Bidet Accessories -Hardware > Plumbing > Plumbing Fixtures > Toilet & Bidet Accessories > Toilet & Bidet Seats -Hardware > Plumbing > Plumbing Fixtures > Toilet & Bidet Accessories > Toilet Seat Covers -Hardware > Plumbing > Plumbing Fixtures > Toilet & Bidet Accessories > Toilet Seat Lid Covers -Hardware > Plumbing > Plumbing Fixtures > Toilet & Bidet Accessories > Toilet Tank Covers -Hardware > Plumbing > Plumbing Fixtures > Toilet & Bidet Accessories > Toilet Tank Levers -Hardware > Plumbing > Plumbing Fixtures > Toilet & Bidet Accessories > Toilet Tanks -Hardware > Plumbing > Plumbing Fixtures > Toilet & Bidet Accessories > Toilet Trim Hardware > Plumbing > Plumbing Fixtures > Toilets & Bidets Hardware > Plumbing > Plumbing Fixtures > Toilets & Bidets > Bidets Hardware > Plumbing > Plumbing Fixtures > Toilets & Bidets > Toilets Hardware > Plumbing > Plumbing Fixtures > Toilets & Bidets > Urinals +Hardware > Plumbing > Plumbing Hoses & Supply Lines Hardware > Plumbing > Plumbing Pipes -Hardware > Plumbing > Plumbing Pipes > Piping Nipples -Hardware > Plumbing > Plumbing Pipes > Push-Fit Pipes -Hardware > Plumbing > Plumbing Pipes > Steel Pipes -Hardware > Plumbing > Plumbing Pumps -Hardware > Plumbing > Plumbing Regulators -Hardware > Plumbing > Plumbing Stoppers -Hardware > Plumbing > Plumbing Taps -Hardware > Plumbing > Plumbing Taps > Gas Taps -Hardware > Plumbing > Plumbing Taps > Hose Bibbs -Hardware > Plumbing > Plumbing Taps > Water Taps -Hardware > Plumbing > Plumbing Valves -Hardware > Plumbing > Plumbing Valves > Ball Valves -Hardware > Plumbing > Repair Kits -Hardware > Plumbing > Repair Kits > Tube Repair Kits -Hardware > Plumbing > Thermocouples -Hardware > Plumbing > Traps & Strainers -Hardware > Plumbing > Traps & Strainers > Bath Traps -Hardware > Plumbing > Traps & Strainers > Bottle Traps -Hardware > Plumbing > Traps & Strainers > Shower Traps -Hardware > Plumbing > Tube Cleaners +Hardware > Plumbing > Plumbing Repair Kits Hardware > Plumbing > Water Dispensing & Filtration Hardware > Plumbing > Water Dispensing & Filtration > In-Line Water Filters -Hardware > Plumbing > Water Dispensing & Filtration > In-Line Water Filters > Refrigerator Water Filters Hardware > Plumbing > Water Dispensing & Filtration > Water Dispensers Hardware > Plumbing > Water Dispensing & Filtration > Water Dispensers > Drinking Fountains Hardware > Plumbing > Water Dispensing & Filtration > Water Dispensers > Water Chillers @@ -2869,61 +2330,88 @@ Hardware > Plumbing > Water Dispensing & Filtration > Water Distillers Hardware > Plumbing > Water Dispensing & Filtration > Water Filtration Accessories Hardware > Plumbing > Water Dispensing & Filtration > Water Filtration Accessories > Water Filter Cartridges Hardware > Plumbing > Water Dispensing & Filtration > Water Filtration Accessories > Water Filter Housings -Hardware > Plumbing > Water Dispensing & Filtration > Water Softener Accessories -Hardware > Plumbing > Water Dispensing & Filtration > Water Softener Accessories > Water Softener Salt +Hardware > Plumbing > Water Dispensing & Filtration > Water Softener Salt Hardware > Plumbing > Water Dispensing & Filtration > Water Softeners Hardware > Plumbing > Water Levelers Hardware > Plumbing > Water Timers Hardware > Plumbing > Well Supplies -Hardware > Renewable Energy -Hardware > Renewable Energy > Solar Energy -Hardware > Renewable Energy > Solar Energy > Solar Energy Kits -Hardware > Renewable Energy > Solar Energy > Solar Panels -Hardware > Renewable Energy > Wind Power -Hardware > Renewable Energy > Wind Power > Wind Turbines -Hardware > Roofing -Hardware > Roofing > Gutter Accessories -Hardware > Roofing > Gutters -Hardware > Roofing > Roof Flashings -Hardware > Shop Stools +Hardware > Power & Electrical Supplies +Hardware > Power & Electrical Supplies > Armatures, Rotors & Stators +Hardware > Power & Electrical Supplies > Ballasts & Starters +Hardware > Power & Electrical Supplies > Carbon Brushes +Hardware > Power & Electrical Supplies > Circuit Breaker Panels +Hardware > Power & Electrical Supplies > Conduit & Housings +Hardware > Power & Electrical Supplies > Conduit & Housings > Electrical Conduit +Hardware > Power & Electrical Supplies > Conduit & Housings > Heat-Shrink Tubing +Hardware > Power & Electrical Supplies > Electrical Motors +Hardware > Power & Electrical Supplies > Electrical Mount Boxes & Brackets +Hardware > Power & Electrical Supplies > Electrical Plug Caps +Hardware > Power & Electrical Supplies > Electrical Switches +Hardware > Power & Electrical Supplies > Electrical Switches > Light Switches +Hardware > Power & Electrical Supplies > Electrical Switches > Specialty Electrical Switches & Relays +Hardware > Power & Electrical Supplies > Electrical Wires & Cable +Hardware > Power & Electrical Supplies > Extension Cord Accessories +Hardware > Power & Electrical Supplies > Extension Cords +Hardware > Power & Electrical Supplies > Generator Accessories +Hardware > Power & Electrical Supplies > Generators +Hardware > Power & Electrical Supplies > Home Automation Kits +Hardware > Power & Electrical Supplies > Phone & Data Jacks +Hardware > Power & Electrical Supplies > Power Converters +Hardware > Power & Electrical Supplies > Power Inlets +Hardware > Power & Electrical Supplies > Power Inverters +Hardware > Power & Electrical Supplies > Power Outlets & Sockets +Hardware > Power & Electrical Supplies > Solar Energy Kits +Hardware > Power & Electrical Supplies > Solar Panels +Hardware > Power & Electrical Supplies > Voltage Transformers & Regulators +Hardware > Power & Electrical Supplies > Wall Plates & Covers +Hardware > Power & Electrical Supplies > Wall Socket Controls & Sensors +Hardware > Power & Electrical Supplies > Wire Caps & Nuts +Hardware > Power & Electrical Supplies > Wire Terminals & Connectors +Hardware > Small Engines Hardware > Storage Tanks -Hardware > Storage Tanks > Food Tanks -Hardware > Storage Tanks > Oil Tanks -Hardware > Storage Tanks > Water Tanks Hardware > Tool Accessories -Hardware > Tool Accessories > Air Brush Accessories +Hardware > Tool Accessories > Abrasive Blaster Accessories +Hardware > Tool Accessories > Abrasive Blaster Accessories > Sandblasting Cabinets Hardware > Tool Accessories > Axe Accessories +Hardware > Tool Accessories > Axe Accessories > Axe Handles Hardware > Tool Accessories > Axe Accessories > Axe Heads -Hardware > Tool Accessories > Drill Accessories -Hardware > Tool Accessories > Drill Accessories > Drill Bit Extensions -Hardware > Tool Accessories > Drill Accessories > Drill Bit Sharpeners -Hardware > Tool Accessories > Drill Accessories > Drill Bits -Hardware > Tool Accessories > Drill Accessories > Hole Saws +Hardware > Tool Accessories > Cutter Accessories +Hardware > Tool Accessories > Cutter Accessories > Nibbler Dies +Hardware > Tool Accessories > Drill & Screwdriver Accessories +Hardware > Tool Accessories > Drill & Screwdriver Accessories > Drill & Screwdriver Bits +Hardware > Tool Accessories > Drill & Screwdriver Accessories > Drill Bit Extensions +Hardware > Tool Accessories > Drill & Screwdriver Accessories > Drill Bit Sharpeners +Hardware > Tool Accessories > Drill & Screwdriver Accessories > Drill Chucks +Hardware > Tool Accessories > Drill & Screwdriver Accessories > Drill Stands & Guides +Hardware > Tool Accessories > Drill & Screwdriver Accessories > Hole Saws Hardware > Tool Accessories > Driver Accessories Hardware > Tool Accessories > Flashlight Accessories -Hardware > Tool Accessories > Generator Accessories -Hardware > Tool Accessories > Generator Accessories > Generator Covers -Hardware > Tool Accessories > Generator Accessories > Generator Replacement Parts +Hardware > Tool Accessories > Grinder Accessories +Hardware > Tool Accessories > Grinder Accessories > Grinding Wheels & Points Hardware > Tool Accessories > Hammer Accessories Hardware > Tool Accessories > Hammer Accessories > Air Hammer Accessories +Hardware > Tool Accessories > Hammer Accessories > Hammer Handles Hardware > Tool Accessories > Hammer Accessories > Hammer Heads -Hardware > Tool Accessories > Hammer Accessories > Hammer Wedges Hardware > Tool Accessories > Industrial Staples Hardware > Tool Accessories > Jigs Hardware > Tool Accessories > Magnetizers & Demagnetizers +Hardware > Tool Accessories > Mattock & Pickaxe Accessories +Hardware > Tool Accessories > Mattock & Pickaxe Accessories > Mattock & Pickaxe Handles Hardware > Tool Accessories > Measuring Tool & Sensor Accessories +Hardware > Tool Accessories > Measuring Tool & Sensor Accessories > Electrical Testing Tool Accessories Hardware > Tool Accessories > Measuring Tool & Sensor Accessories > Gas Detector Accessories -Hardware > Tool Accessories > Measuring Tool & Sensor Accessories > Metal & Voltage Detector Accessories +Hardware > Tool Accessories > Measuring Tool & Sensor Accessories > Measuring Scale Accessories Hardware > Tool Accessories > Measuring Tool & Sensor Accessories > Multimeter Accessories +Hardware > Tool Accessories > Mixing Tool Paddles Hardware > Tool Accessories > Paint Tool Accessories +Hardware > Tool Accessories > Paint Tool Accessories > Airbrush Accessories +Hardware > Tool Accessories > Paint Tool Accessories > Paint Brush Cleaning Solutions Hardware > Tool Accessories > Paint Tool Accessories > Paint Roller Accessories Hardware > Tool Accessories > Power Tool Batteries Hardware > Tool Accessories > Power Tool Chargers Hardware > Tool Accessories > Router Accessories Hardware > Tool Accessories > Router Accessories > Router Bits Hardware > Tool Accessories > Router Accessories > Router Tables -Hardware > Tool Accessories > Sandblasting Accessories -Hardware > Tool Accessories > Sandblasting Accessories > Sandblasting Cabinets Hardware > Tool Accessories > Sanding Accessories Hardware > Tool Accessories > Sanding Accessories > Sandpaper & Sanding Sponges Hardware > Tool Accessories > Saw Accessories @@ -2932,60 +2420,42 @@ Hardware > Tool Accessories > Saw Accessories > Handheld Circular Saw Accessorie Hardware > Tool Accessories > Saw Accessories > Jigsaw Accessories Hardware > Tool Accessories > Saw Accessories > Miter Saw Accessories Hardware > Tool Accessories > Saw Accessories > Table Saw Accessories -Hardware > Tool Accessories > Screwdriver Accessories -Hardware > Tool Accessories > Screwdriver Accessories > Screwdriver Tips Hardware > Tool Accessories > Shaper Accessories Hardware > Tool Accessories > Shaper Accessories > Shaper Cutters Hardware > Tool Accessories > Soldering Iron Accessories Hardware > Tool Accessories > Soldering Iron Accessories > Soldering Iron Stands Hardware > Tool Accessories > Soldering Iron Accessories > Soldering Iron Tips Hardware > Tool Accessories > Tool Blades +Hardware > Tool Accessories > Tool Blades > Cutter & Scraper Blades Hardware > Tool Accessories > Tool Blades > Saw Blades -Hardware > Tool Accessories > Tool Blades > Utility Knife Blades -Hardware > Tool Accessories > Tool Handles -Hardware > Tool Accessories > Tool Handles > Axe Handles -Hardware > Tool Accessories > Tool Handles > Hammer Handles +Hardware > Tool Accessories > Tool Handle Wedges +Hardware > Tool Accessories > Tool Safety Tethers Hardware > Tool Accessories > Tool Sockets Hardware > Tool Accessories > Tool Stands Hardware > Tool Accessories > Tool Stands > Saw Stands +Hardware > Tool Accessories > Wedge Tools +Hardware > Tool Accessories > Welding Accessories Hardware > Tools +Hardware > Tools > Abrasive Blasters Hardware > Tools > Anvils -Hardware > Tools > Awls Hardware > Tools > Axes -Hardware > Tools > Benders -Hardware > Tools > Blowguns -Hardware > Tools > Brushes -Hardware > Tools > Brushes > Airbrushes -Hardware > Tools > Brushes > Chimney Brushes -Hardware > Tools > Brushes > Paint Brushes -Hardware > Tools > Brushes > Pipe Brushes Hardware > Tools > Carpentry Jointers -Hardware > Tools > Carving Tools -Hardware > Tools > Carving Tools > Carving Spoons -Hardware > Tools > Carving Tools > Chisels -Hardware > Tools > Carving Tools > Gouges -Hardware > Tools > Carving Tools > Wood Carving Knives +Hardware > Tools > Carving Chisels & Gouges Hardware > Tools > Caulking Tools -Hardware > Tools > Caulking Tools > Caulking Guns -Hardware > Tools > Caulking Tools > Caulking Irons -Hardware > Tools > Caulking Tools > Caulking Mallets +Hardware > Tools > Chimney Brushes Hardware > Tools > Compactors Hardware > Tools > Compressors -Hardware > Tools > Compressors > Air Compressors -Hardware > Tools > Compressors > Gas Compressors Hardware > Tools > Concrete Brooms -Hardware > Tools > Crowbars Hardware > Tools > Cutters Hardware > Tools > Cutters > Bolt Cutters Hardware > Tools > Cutters > Glass Cutters -Hardware > Tools > Cutters > Nibblers +Hardware > Tools > Cutters > Handheld Metal Shears & Nibblers Hardware > Tools > Cutters > Nippers Hardware > Tools > Cutters > Pipe Cutters Hardware > Tools > Cutters > Rebar Cutters Hardware > Tools > Cutters > Tile & Shingle Cutters Hardware > Tools > Cutters > Utility Knives Hardware > Tools > Deburrers -Hardware > Tools > Die Sets Hardware > Tools > Dollies & Hand Trucks Hardware > Tools > Drills Hardware > Tools > Drills > Augers @@ -2994,90 +2464,70 @@ Hardware > Tools > Drills > Handheld Power Drills Hardware > Tools > Drills > Mortisers Hardware > Tools > Drills > Pneumatic Drills Hardware > Tools > Electrician Fish Tape -Hardware > Tools > Flashlights -Hardware > Tools > Flashlights > Headlamps -Hardware > Tools > Glue Guns +Hardware > Tools > Flashlights & Headlamps Hardware > Tools > Grease Guns Hardware > Tools > Grinders Hardware > Tools > Grips -Hardware > Tools > Hammering -Hardware > Tools > Hammering > Air Hammers -Hardware > Tools > Hammering > Automatic Hammers -Hardware > Tools > Hammering > Hammers -Hardware > Tools > Hammering > Hammers > Mallets -Hardware > Tools > Hammering > Hammers > Sledgehammers -Hardware > Tools > Hammering > Jackhammers -Hardware > Tools > Hardware Shears +Hardware > Tools > Hammers +Hardware > Tools > Hammers > Manual Hammers +Hardware > Tools > Hammers > Powered Hammers +Hardware > Tools > Handheld Power Mixers +Hardware > Tools > Hardware Torches Hardware > Tools > Heat Guns -Hardware > Tools > Hoisting -Hardware > Tools > Hoisting > Air Casters -Hardware > Tools > Hoisting > Buckhoists -Hardware > Tools > Hoisting > Chainhoists -Hardware > Tools > Hoisting > Jack Lifts -Hardware > Tools > Hoisting > Pallet Jacks -Hardware > Tools > Hoisting > Wire Hoists Hardware > Tools > Impact Wrenches & Drivers Hardware > Tools > Industrial Vibrators Hardware > Tools > Inspection Mirrors Hardware > Tools > Ladders & Scaffolding Hardware > Tools > Ladders & Scaffolding > Ladder Carts Hardware > Tools > Ladders & Scaffolding > Ladders -Hardware > Tools > Ladders & Scaffolding > Ladders > Aluminum Ladders -Hardware > Tools > Ladders & Scaffolding > Ladders > Wooden Ladders Hardware > Tools > Ladders & Scaffolding > Scaffolding Hardware > Tools > Ladders & Scaffolding > Step Stools Hardware > Tools > Ladders & Scaffolding > Work Platforms Hardware > Tools > Lathes -Hardware > Tools > Lifting Magnets Hardware > Tools > Light Bulb Changers +Hardware > Tools > Lighters & Matches Hardware > Tools > Log Splitters +Hardware > Tools > Magnetic Sweepers Hardware > Tools > Marking Tools -Hardware > Tools > Marking Tools > Carpenter Crayons & Pencils -Hardware > Tools > Marking Tools > Chalk Lines -Hardware > Tools > Marking Tools > Marking Chisels -Hardware > Tools > Marking Tools > Marking Knives Hardware > Tools > Masonry Tools Hardware > Tools > Masonry Tools > Brick Tools Hardware > Tools > Masonry Tools > Cement Mixers Hardware > Tools > Masonry Tools > Construction Lines -Hardware > Tools > Masonry Tools > Edgers Hardware > Tools > Masonry Tools > Floats Hardware > Tools > Masonry Tools > Grout Sponges +Hardware > Tools > Masonry Tools > Masonry Edgers & Groovers Hardware > Tools > Masonry Tools > Masonry Jointers Hardware > Tools > Masonry Tools > Masonry Trowels Hardware > Tools > Masonry Tools > Power Trowels +Hardware > Tools > Mattocks & Pickaxes Hardware > Tools > Measuring Tools & Sensors Hardware > Tools > Measuring Tools & Sensors > Air Quality Meters Hardware > Tools > Measuring Tools & Sensors > Altimeters +Hardware > Tools > Measuring Tools & Sensors > Anemometers Hardware > Tools > Measuring Tools & Sensors > Barometers -Hardware > Tools > Measuring Tools & Sensors > Cable Testers Hardware > Tools > Measuring Tools & Sensors > Calipers Hardware > Tools > Measuring Tools & Sensors > Cruising Rods -Hardware > Tools > Measuring Tools & Sensors > Diagnostic Thermal Cameras Hardware > Tools > Measuring Tools & Sensors > Distance Meters Hardware > Tools > Measuring Tools & Sensors > Dividers +Hardware > Tools > Measuring Tools & Sensors > Electrical Testing Tools Hardware > Tools > Measuring Tools & Sensors > Flow Meters & Controllers Hardware > Tools > Measuring Tools & Sensors > Gas Detectors Hardware > Tools > Measuring Tools & Sensors > Gauges Hardware > Tools > Measuring Tools & Sensors > Geiger Counters Hardware > Tools > Measuring Tools & Sensors > Hygrometers +Hardware > Tools > Measuring Tools & Sensors > Infrared Thermometers Hardware > Tools > Measuring Tools & Sensors > Knife Guides -Hardware > Tools > Measuring Tools & Sensors > Leak Detectors Hardware > Tools > Measuring Tools & Sensors > Levels Hardware > Tools > Measuring Tools & Sensors > Levels > Bubble Levels Hardware > Tools > Measuring Tools & Sensors > Levels > Laser Levels Hardware > Tools > Measuring Tools & Sensors > Levels > Sight Levels +Hardware > Tools > Measuring Tools & Sensors > Measuring Scales Hardware > Tools > Measuring Tools & Sensors > Measuring Wheels -Hardware > Tools > Measuring Tools & Sensors > Metal & Voltage Detectors Hardware > Tools > Measuring Tools & Sensors > Moisture Meters -Hardware > Tools > Measuring Tools & Sensors > Multimeters -Hardware > Tools > Measuring Tools & Sensors > Pipe Locators -Hardware > Tools > Measuring Tools & Sensors > Pressure Sensors & Transducers Hardware > Tools > Measuring Tools & Sensors > Probes & Finders Hardware > Tools > Measuring Tools & Sensors > Protractors Hardware > Tools > Measuring Tools & Sensors > Rebar Locators Hardware > Tools > Measuring Tools & Sensors > Rulers -Hardware > Tools > Measuring Tools & Sensors > Scales Hardware > Tools > Measuring Tools & Sensors > Seismometer Hardware > Tools > Measuring Tools & Sensors > Sound Meters Hardware > Tools > Measuring Tools & Sensors > Squares @@ -3085,12 +2535,12 @@ Hardware > Tools > Measuring Tools & Sensors > Straight Edges Hardware > Tools > Measuring Tools & Sensors > Stud Sensors Hardware > Tools > Measuring Tools & Sensors > Tape Measures Hardware > Tools > Measuring Tools & Sensors > Theodolites -Hardware > Tools > Measuring Tools & Sensors > Thermometers -Hardware > Tools > Measuring Tools & Sensors > Thermometers > Infrared Thermometers +Hardware > Tools > Measuring Tools & Sensors > Thermal Imaging Cameras +Hardware > Tools > Measuring Tools & Sensors > Thermocouples & Thermopiles +Hardware > Tools > Measuring Tools & Sensors > Transducers Hardware > Tools > Measuring Tools & Sensors > UV Light Meters Hardware > Tools > Measuring Tools & Sensors > Vibration Meters Hardware > Tools > Measuring Tools & Sensors > Weather Forecasters & Stations -Hardware > Tools > Measuring Tools & Sensors > Wire Locators Hardware > Tools > Measuring Tools & Sensors > pH Meters Hardware > Tools > Milling Machines Hardware > Tools > Multifunction Power Tools @@ -3098,85 +2548,75 @@ Hardware > Tools > Nail Pullers Hardware > Tools > Nailers & Staplers Hardware > Tools > Oil Filter Drains Hardware > Tools > Paint Tools +Hardware > Tools > Paint Tools > Airbrushes +Hardware > Tools > Paint Tools > Paint Brushes Hardware > Tools > Paint Tools > Paint Edgers Hardware > Tools > Paint Tools > Paint Rollers +Hardware > Tools > Paint Tools > Paint Shakers Hardware > Tools > Paint Tools > Paint Sponges Hardware > Tools > Paint Tools > Paint Sprayers -Hardware > Tools > Paint Tools > Paint Stripper +Hardware > Tools > Paint Tools > Paint Strainers Hardware > Tools > Paint Tools > Paint Trays Hardware > Tools > Pickup Tools -Hardware > Tools > Pickup Tools > Utility Tweezers +Hardware > Tools > Pipe & Bar Benders +Hardware > Tools > Pipe & Tube Cleaners +Hardware > Tools > Pipe Brushes Hardware > Tools > Planers Hardware > Tools > Planes Hardware > Tools > Pliers Hardware > Tools > Plungers Hardware > Tools > Polishers & Buffers Hardware > Tools > Post Hole Diggers -Hardware > Tools > Punches +Hardware > Tools > Pry Bars +Hardware > Tools > Punches & Awls Hardware > Tools > Putty Knives & Scrapers Hardware > Tools > Reamers Hardware > Tools > Riveting Tools Hardware > Tools > Riveting Tools > Rivet Guns Hardware > Tools > Riveting Tools > Rivet Pliers Hardware > Tools > Routing Tools -Hardware > Tools > Sandblasters Hardware > Tools > Sanders Hardware > Tools > Sanding Blocks Hardware > Tools > Saw Horses Hardware > Tools > Saws Hardware > Tools > Saws > Band Saws Hardware > Tools > Saws > Cut-Off Saws -Hardware > Tools > Saws > Hacksaws Hardware > Tools > Saws > Hand Saws Hardware > Tools > Saws > Handheld Circular Saws -Hardware > Tools > Saws > Jig Saws +Hardware > Tools > Saws > Jigsaws Hardware > Tools > Saws > Masonry & Tile Saws Hardware > Tools > Saws > Miter Saws +Hardware > Tools > Saws > Panel Saws Hardware > Tools > Saws > Reciprocating Saws Hardware > Tools > Saws > Scroll Saws Hardware > Tools > Saws > Table Saws Hardware > Tools > Screwdrivers -Hardware > Tools > Screwdrivers > Manual Screwdrivers -Hardware > Tools > Screwdrivers > Power Screwdrivers Hardware > Tools > Shapers Hardware > Tools > Sharpeners -Hardware > Tools > Sharpeners > Chainsaw Sharpeners -Hardware > Tools > Sharpeners > Sharpening Stones Hardware > Tools > Socket Drivers Hardware > Tools > Soldering Irons Hardware > Tools > Tap Reseaters +Hardware > Tools > Taps & Dies Hardware > Tools > Threading Machines -Hardware > Tools > Tool Clamps -Hardware > Tools > Tool Clamps > Vise Clamps +Hardware > Tools > Tool Clamps & Vises Hardware > Tools > Tool Files Hardware > Tools > Tool Keys -Hardware > Tools > Tool Keys > Manhole Keys -Hardware > Tools > Tool Keys > Radiator Keys -Hardware > Tools > Tool Keys > Stopcock Keys Hardware > Tools > Tool Knives -Hardware > Tools > Tool Knives > Sheetrock Knives Hardware > Tools > Tool Sets Hardware > Tools > Tool Sets > Hand Tool Sets Hardware > Tools > Tool Sets > Power Tool Combo Sets -Hardware > Tools > Welding Tools +Hardware > Tools > Welding Guns & Plasma Cutters Hardware > Tools > Wire & Cable Hand Tools -Hardware > Tools > Wire & Cable Hand Tools > Cable Slitters -Hardware > Tools > Wire & Cable Hand Tools > Snippers -Hardware > Tools > Wire & Cable Hand Tools > Wire Crimpers -Hardware > Tools > Wire & Cable Hand Tools > Wire Cutters -Hardware > Tools > Wire & Cable Hand Tools > Wire Strippers Hardware > Tools > Work Lights Hardware > Tools > Wrenches -Hardware > Tools > Wrenches > Hex Keys -Hardware > Tools > Wrenches > Socket Wrenches Health & Beauty Health & Beauty > Health Care Health & Beauty > Health Care > Acupuncture Health & Beauty > Health Care > Acupuncture > Acupuncture Models Health & Beauty > Health Care > Acupuncture > Acupuncture Needles -Health & Beauty > Health Care > Arthritis Care Health & Beauty > Health Care > Bed Pans Health & Beauty > Health Care > Biometric Monitor Accessories +Health & Beauty > Health Care > Biometric Monitor Accessories > Activity Monitor Accessories Health & Beauty > Health Care > Biometric Monitor Accessories > Blood Glucose Meter Accessories Health & Beauty > Health Care > Biometric Monitor Accessories > Blood Glucose Meter Accessories > Blood Glucose Control Solution Health & Beauty > Health Care > Biometric Monitor Accessories > Blood Glucose Meter Accessories > Blood Glucose Test Strips @@ -3184,72 +2624,57 @@ Health & Beauty > Health Care > Biometric Monitor Accessories > Blood Glucose Me Health & Beauty > Health Care > Biometric Monitor Accessories > Blood Pressure Monitor Accessories Health & Beauty > Health Care > Biometric Monitor Accessories > Blood Pressure Monitor Accessories > Blood Pressure Monitor Cuffs Health & Beauty > Health Care > Biometric Monitor Accessories > Body Weight Scale Accessories -Health & Beauty > Health Care > Biometric Monitor Accessories > Fertility Monitor Accessories -Health & Beauty > Health Care > Biometric Monitor Accessories > Heart Rate Monitor Accessories Health & Beauty > Health Care > Biometric Monitors +Health & Beauty > Health Care > Biometric Monitors > Activity Monitors Health & Beauty > Health Care > Biometric Monitors > Blood Glucose Meters Health & Beauty > Health Care > Biometric Monitors > Blood Pressure Monitors Health & Beauty > Health Care > Biometric Monitors > Body Fat Analyzers Health & Beauty > Health Care > Biometric Monitors > Body Weight Scales Health & Beauty > Health Care > Biometric Monitors > Breathalyzers Health & Beauty > Health Care > Biometric Monitors > Cholesterol Analyzers -Health & Beauty > Health Care > Biometric Monitors > Fertility Monitors -Health & Beauty > Health Care > Biometric Monitors > Heart Rate Monitors +Health & Beauty > Health Care > Biometric Monitors > Fertility Monitors and Ovulation Tests Health & Beauty > Health Care > Biometric Monitors > Medical Thermometers -Health & Beauty > Health Care > Biometric Monitors > Pedometers -Health & Beauty > Health Care > Biometric Monitors > Prenatal Heart Monitors +Health & Beauty > Health Care > Biometric Monitors > Prenatal Heart Rate Monitors Health & Beauty > Health Care > Biometric Monitors > Pulse Oximeters -Health & Beauty > Health Care > Blood Typing Test Kits -Health & Beauty > Health Care > CPAP Equipment -Health & Beauty > Health Care > CPAP Equipment > CPAP Machines -Health & Beauty > Health Care > CPAP Equipment > CPAP Masks +Health & Beauty > Health Care > Condoms +Health & Beauty > Health Care > Conductivity Gels & Lotions Health & Beauty > Health Care > Contraceptive Cases -Health & Beauty > Health Care > Contraceptives -Health & Beauty > Health Care > Contraceptives > Condoms -Health & Beauty > Health Care > Contraceptives > Spermicides -Health & Beauty > Health Care > Diabetes Care -Health & Beauty > Health Care > Drug Tests Health & Beauty > Health Care > First Aid Health & Beauty > Health Care > First Aid > Antiseptics & Cleaning Supplies Health & Beauty > Health Care > First Aid > Cast & Bandage Protectors Health & Beauty > Health Care > First Aid > Eye Wash Supplies Health & Beauty > Health Care > First Aid > First Aid Kits -Health & Beauty > Health Care > First Aid > Heat Rubs Health & Beauty > Health Care > First Aid > Hot & Cold Therapies +Health & Beauty > Health Care > First Aid > Hot & Cold Therapies > Heat Rubs Health & Beauty > Health Care > First Aid > Hot & Cold Therapies > Heating Pads Health & Beauty > Health Care > First Aid > Hot & Cold Therapies > Ice Packs Health & Beauty > Health Care > First Aid > Medical Tape & Bandages -Health & Beauty > Health Care > First Aid > Rash & Anti-Itch Treatments Health & Beauty > Health Care > Fitness & Nutrition -Health & Beauty > Health Care > Fitness & Nutrition > Diet Pills 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 > Protein Powder -Health & Beauty > Health Care > Fitness & Nutrition > Traditional Chinese Tonics +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 > HIV Tests Health & Beauty > Health Care > Hearing Aids -Health & Beauty > Health Care > Hearing Enhancers Health & Beauty > Health Care > Incontinence Aids +Health & Beauty > Health Care > Light Therapy Lamps Health & Beauty > Health Care > Medical Alarm Systems Health & Beauty > Health Care > Medical Identification Tags & Jewelry +Health & Beauty > Health Care > Medical Tests +Health & Beauty > Health Care > Medical Tests > Allergy Test Kits +Health & Beauty > Health Care > Medical Tests > Blood Typing Test Kits +Health & Beauty > Health Care > Medical Tests > Drug Tests +Health & Beauty > Health Care > Medical Tests > HIV Tests +Health & Beauty > Health Care > Medical Tests > Pregnancy Tests +Health & Beauty > Health Care > Medical Tests > Urinary Tract Infection Tests Health & Beauty > Health Care > Medicine & Drugs -Health & Beauty > Health Care > Medicine & Drugs > OTC Drugs -Health & Beauty > Health Care > Medicine & Drugs > OTC Drugs > Allergies & Sinus -Health & Beauty > Health Care > Medicine & Drugs > OTC Drugs > Cough, Cold & Flu -Health & Beauty > Health Care > Medicine & Drugs > OTC Drugs > Gastrointestinal Treatments -Health & Beauty > Health Care > Medicine & Drugs > OTC Drugs > Pain Relievers -Health & Beauty > Health Care > Medicine & Drugs > OTC Drugs > Sleeping Medication Health & Beauty > Health Care > Mobility & Accessibility Health & Beauty > Health Care > Mobility & Accessibility > Accessibility Equipment Health & Beauty > Health Care > Mobility & Accessibility > Accessibility Equipment > Mobility Scooters Health & Beauty > Health Care > Mobility & Accessibility > Accessibility Equipment > Stair Lifts +Health & Beauty > Health Care > Mobility & Accessibility > Accessibility Equipment > Transfer Boards & Sheets Health & Beauty > Health Care > Mobility & Accessibility > Accessibility Equipment > Wheelchairs -Health & Beauty > Health Care > Mobility & Accessibility > Accessibility Equipment > Wheelchairs > Manual Wheelchairs -Health & Beauty > Health Care > Mobility & Accessibility > Accessibility Equipment > Wheelchairs > Powered Wheelchairs Health & Beauty > Health Care > Mobility & Accessibility > Accessibility Equipment Accessories Health & Beauty > Health Care > Mobility & Accessibility > Accessibility Furniture & Fixtures Health & Beauty > Health Care > Mobility & Accessibility > Accessibility Furniture & Fixtures > Shower Benches & Seats @@ -3258,22 +2683,33 @@ Health & Beauty > Health Care > Mobility & Accessibility > Walking Aids Health & Beauty > Health Care > Mobility & Accessibility > Walking Aids > Canes & Walking Sticks Health & Beauty > Health Care > Mobility & Accessibility > Walking Aids > Crutches Health & Beauty > Health Care > Mobility & Accessibility > Walking Aids > Walkers -Health & Beauty > Health Care > Physical Therapy Equipment -Health & Beauty > Health Care > Physical Therapy Equipment > Hand Exercisers -Health & Beauty > Health Care > Pregnancy Tests +Health & Beauty > Health Care > Occupational & Physical Therapy Equipment +Health & Beauty > Health Care > Occupational & Physical Therapy Equipment > Electrical Muscle Stimulators +Health & Beauty > Health Care > Occupational & Physical Therapy Equipment > Therapeutic Swings +Health & Beauty > Health Care > Pillboxes Health & Beauty > Health Care > Respiratory Care Health & Beauty > Health Care > Respiratory Care > Nebulizers -Health & Beauty > Health Care > Smoking Cessation +Health & Beauty > Health Care > Respiratory Care > Oxygen Tanks +Health & Beauty > Health Care > Respiratory Care > PAP Machines +Health & Beauty > Health Care > Respiratory Care > PAP Masks +Health & Beauty > Health Care > Respiratory Care > Steam Inhalers +Health & Beauty > Health Care > Specimen Cups +Health & Beauty > Health Care > Spermicides +Health & Beauty > Health Care > Stump Shrinkers Health & Beauty > Health Care > Supports & Braces Health & Beauty > Health Care > Surgical Lubricants Health & Beauty > Jewelry Cleaning & Care -Health & Beauty > Jewelry Cleaning & Care > Jewelry Cleaners +Health & Beauty > Jewelry Cleaning & Care > Jewelry Cleaning Solutions & Polishes +Health & Beauty > Jewelry Cleaning & Care > Jewelry Cleaning Tools Health & Beauty > Jewelry Cleaning & Care > Jewelry Holders +Health & Beauty > Jewelry Cleaning & Care > Jewelry Steam Cleaners +Health & Beauty > Jewelry Cleaning & Care > Watch Repair Kits Health & Beauty > Personal Care Health & Beauty > Personal Care > Back Care Health & Beauty > Personal Care > Back Care > Back & Lumbar Support Cushions Health & Beauty > Personal Care > Cosmetics Health & Beauty > Personal Care > Cosmetics > Bath & Body +Health & Beauty > Personal Care > Cosmetics > Bath & Body > Adult Hygienic Wipes Health & Beauty > Personal Care > Cosmetics > Bath & Body > Bar Soap Health & Beauty > Personal Care > Cosmetics > Bath & Body > Bath Additives Health & Beauty > Personal Care > Cosmetics > Bath & Body > Bath Brushes @@ -3283,9 +2719,9 @@ Health & Beauty > Personal Care > Cosmetics > Bath & Body > Hand Sanitizers & Wi Health & Beauty > Personal Care > Cosmetics > Bath & Body > Liquid Hand Soap Health & Beauty > Personal Care > Cosmetics > Bath & Body > Powdered Hand Soap Health & Beauty > Personal Care > Cosmetics > Bath & Body > Shower Caps -Health & Beauty > Personal Care > Cosmetics > Bath & Body > Wet Wipes Health & Beauty > Personal Care > Cosmetics > Bath & Body Gift Sets -Health & Beauty > Personal Care > Cosmetics > Cosmetic Tool Cleaners +Health & Beauty > Personal Care > Cosmetics > Cosmetic Sets +Health & Beauty > Personal Care > Cosmetics > Cosmetic Tool Cleansers Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > Double Eyelid Glue & Tape @@ -3294,58 +2730,67 @@ Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > Ey Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > Eyelash Curlers Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > Face Mirrors Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > Facial Blotting Paper -Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > False Eyelash Adhesive -Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > False Eyelash Applicators +Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > False Eyelash Accessories +Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > False Eyelash Accessories > False Eyelash Adhesive +Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > False Eyelash Accessories > False Eyelash Applicators +Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > False Eyelash Accessories > False Eyelash Remover Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > Makeup Brushes Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > Makeup Sponges +Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Makeup Tools > Refillable Makeup Palettes & Cases Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools > Cuticle Pushers Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools > Cuticle Scissors +Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools > Manicure & Pedicure Spacers Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools > Manicure Tool Sets Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools > Nail Buffers Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools > Nail Clippers +Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools > Nail Drill Accessories Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools > Nail Drills Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools > Nail Dryers Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools > Nail Files & Emery Boards -Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Nail Tools > Pedicure Toe Seperators Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Skin Care Tools Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Skin Care Tools > Facial Saunas Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Skin Care Tools > Foot Files +Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Skin Care Tools > Lotion & Sunscreen Applicators Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Skin Care Tools > Pumice Stones Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Skin Care Tools > Skin Care Extractors Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Skin Care Tools > Skin Care Rollers +Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Skin Care Tools > Skin Cleansing Brush Heads Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Skin Care Tools > Skin Cleansing Brushes & Systems -Health & Beauty > Personal Care > Cosmetics > Cosmetic Tools > Tweezers Health & Beauty > Personal Care > Cosmetics > Makeup Health & Beauty > Personal Care > Cosmetics > Makeup > Body Makeup Health & Beauty > Personal Care > Cosmetics > Makeup > Body Makeup > Body & Hair Glitter Health & Beauty > Personal Care > Cosmetics > Makeup > Body Makeup > Body Paint & Foundation Health & Beauty > Personal Care > Cosmetics > Makeup > Costume & Stage Makeup Health & Beauty > Personal Care > Cosmetics > Makeup > Eye Makeup -Health & Beauty > Personal Care > Cosmetics > Makeup > Eye Makeup > Eye Shadow & Primer +Health & Beauty > Personal Care > Cosmetics > Makeup > Eye Makeup > Eye Primer +Health & Beauty > Personal Care > Cosmetics > Makeup > Eye Makeup > Eye Shadow Health & Beauty > Personal Care > Cosmetics > Makeup > Eye Makeup > Eyebrow Enhancers -Health & Beauty > Personal Care > Cosmetics > Makeup > Eye Makeup > Eyelash Growth Treatments Health & Beauty > Personal Care > Cosmetics > Makeup > Eye Makeup > Eyeliner Health & Beauty > Personal Care > Cosmetics > Makeup > Eye Makeup > False Eyelashes +Health & Beauty > Personal Care > Cosmetics > Makeup > Eye Makeup > Lash & Brow Growth Treatments Health & Beauty > Personal Care > Cosmetics > Makeup > Eye Makeup > Mascara +Health & Beauty > Personal Care > Cosmetics > Makeup > Eye Makeup > Mascara Primer Health & Beauty > Personal Care > Cosmetics > Makeup > Face Makeup Health & Beauty > Personal Care > Cosmetics > Makeup > Face Makeup > Blushes & Bronzers Health & Beauty > Personal Care > Cosmetics > Makeup > Face Makeup > Face Powder +Health & Beauty > Personal Care > Cosmetics > Makeup > Face Makeup > Face Primer Health & Beauty > Personal Care > Cosmetics > Makeup > Face Makeup > Foundations & Concealers Health & Beauty > Personal Care > Cosmetics > Makeup > Face Makeup > Highlighters & Luminizers Health & Beauty > Personal Care > Cosmetics > Makeup > Lip Makeup Health & Beauty > Personal Care > Cosmetics > Makeup > Lip Makeup > Lip & Cheek Stains Health & Beauty > Personal Care > Cosmetics > Makeup > Lip Makeup > Lip Gloss Health & Beauty > Personal Care > Cosmetics > Makeup > Lip Makeup > Lip Liner +Health & Beauty > Personal Care > Cosmetics > Makeup > Lip Makeup > Lip Primer Health & Beauty > Personal Care > Cosmetics > Makeup > Lip Makeup > Lipstick Health & Beauty > Personal Care > Cosmetics > Makeup > Makeup Finishing Sprays -Health & Beauty > Personal Care > Cosmetics > Makeup > Makeup Sets Health & Beauty > Personal Care > Cosmetics > Makeup > Temporary Tattoos Health & Beauty > Personal Care > Cosmetics > Nail Care Health & Beauty > Personal Care > Cosmetics > Nail Care > Cuticle Cream & Oil Health & Beauty > Personal Care > Cosmetics > Nail Care > False Nails +Health & Beauty > Personal Care > Cosmetics > Nail Care > Manicure Glue Health & Beauty > Personal Care > Cosmetics > Nail Care > Nail Art Kits & Accessories -Health & Beauty > Personal Care > Cosmetics > Nail Care > Nail Glue +Health & Beauty > Personal Care > Cosmetics > Nail Care > Nail Polish Drying Drops & Sprays Health & Beauty > Personal Care > Cosmetics > Nail Care > Nail Polish Removers Health & Beauty > Personal Care > Cosmetics > Nail Care > Nail Polish Thinners Health & Beauty > Personal Care > Cosmetics > Nail Care > Nail Polishes @@ -3361,10 +2806,6 @@ Health & Beauty > Personal Care > Cosmetics > Skin Care > Facial Cleansing Kits Health & Beauty > Personal Care > Cosmetics > Skin Care > Facial Pore Strips Health & Beauty > Personal Care > Cosmetics > Skin Care > Lip Balms & Treatments Health & Beauty > Personal Care > Cosmetics > Skin Care > Lotion & Moisturizer -Health & Beauty > Personal Care > Cosmetics > Skin Care > Lotion & Moisturizer > Body Lotion -Health & Beauty > Personal Care > Cosmetics > Skin Care > Lotion & Moisturizer > Face Lotion -Health & Beauty > Personal Care > Cosmetics > Skin Care > Lotion & Moisturizer > Foot Lotion -Health & Beauty > Personal Care > Cosmetics > Skin Care > Lotion & Moisturizer > Hand Lotion Health & Beauty > Personal Care > Cosmetics > Skin Care > Makeup Removers Health & Beauty > Personal Care > Cosmetics > Skin Care > Petroleum Jelly Health & Beauty > Personal Care > Cosmetics > Skin Care > Skin Care Masks & Peels @@ -3372,9 +2813,9 @@ Health & Beauty > Personal Care > Cosmetics > Skin Care > Skin Insect Repellent Health & Beauty > Personal Care > Cosmetics > Skin Care > Sunscreen Health & Beauty > Personal Care > Cosmetics > Skin Care > Tanning Products Health & Beauty > Personal Care > Cosmetics > Skin Care > Tanning Products > Self Tanner -Health & Beauty > Personal Care > Cosmetics > Skin Care > Tanning Products > Self Tanner Applicators & Removers Health & Beauty > Personal Care > Cosmetics > Skin Care > Tanning Products > Tanning Oil & Lotion Health & Beauty > Personal Care > Cosmetics > Skin Care > Toners & Astringents +Health & Beauty > Personal Care > Cosmetics > Skin Care > Wart Removers Health & Beauty > Personal Care > Cotton Balls Health & Beauty > Personal Care > Cotton Swabs Health & Beauty > Personal Care > Deodorant & Anti-Perspirant @@ -3382,6 +2823,7 @@ Health & Beauty > Personal Care > Ear Care Health & Beauty > Personal Care > Ear Care > Ear Candles Health & Beauty > Personal Care > Ear Care > Ear Drops Health & Beauty > Personal Care > Ear Care > Ear Dryers +Health & Beauty > Personal Care > Ear Care > Ear Picks & Spoons Health & Beauty > Personal Care > Ear Care > Ear Syringes Health & Beauty > Personal Care > Ear Care > Ear Wax Removal Kits Health & Beauty > Personal Care > Ear Care > Earplug Dispensers @@ -3389,17 +2831,18 @@ Health & Beauty > Personal Care > Ear Care > Earplugs Health & Beauty > Personal Care > Enema Kits & Supplies Health & Beauty > Personal Care > Feminine Sanitary Supplies Health & Beauty > Personal Care > Feminine Sanitary Supplies > Feminine Deodorant -Health & Beauty > Personal Care > Feminine Sanitary Supplies > Feminine Douches & Washes +Health & Beauty > Personal Care > Feminine Sanitary Supplies > Feminine Douches & Creams Health & Beauty > Personal Care > Feminine Sanitary Supplies > Feminine Pads & Protectors +Health & Beauty > Personal Care > Feminine Sanitary Supplies > Menstrual Cups Health & Beauty > Personal Care > Feminine Sanitary Supplies > Tampons Health & Beauty > Personal Care > Foot Care -Health & Beauty > Personal Care > Foot Care > Antifungal Treatments -Health & Beauty > Personal Care > Foot Care > Bunion Care -Health & Beauty > Personal Care > Foot Care > Corn & Callus Care +Health & Beauty > Personal Care > Foot Care > Bunion Care Supplies +Health & Beauty > Personal Care > Foot Care > Corn & Callus Care Supplies Health & Beauty > Personal Care > Foot Care > Foot Odor Removers Health & Beauty > Personal Care > Foot Care > Insoles & Inserts Health & Beauty > Personal Care > Foot Care > Toe Spacers Health & Beauty > Personal Care > Hair Care +Health & Beauty > Personal Care > Hair Care > Hair Care Kits Health & Beauty > Personal Care > Hair Care > Hair Color Health & Beauty > Personal Care > Hair Care > Hair Color Removers Health & Beauty > Personal Care > Hair Care > Hair Coloring Accessories @@ -3419,26 +2862,32 @@ Health & Beauty > Personal Care > Hair Care > Hair Styling Tools > Curling Irons Health & Beauty > Personal Care > Hair Care > Hair Styling Tools > Hair Curlers Health & Beauty > Personal Care > Hair Care > Hair Styling Tools > Hair Dryers Health & Beauty > Personal Care > Hair Care > Hair Styling Tools > Hair Straighteners +Health & Beauty > Personal Care > Hair Care > Hair Styling Tools > Hair Styling Tool Sets Health & Beauty > Personal Care > Hair Care > Shampoo & Conditioner Health & Beauty > Personal Care > Massage & Relaxation +Health & Beauty > Personal Care > Massage & Relaxation > Back Scratchers +Health & Beauty > Personal Care > Massage & Relaxation > Eye Pillows Health & Beauty > Personal Care > Massage & Relaxation > Massage Chairs Health & Beauty > Personal Care > Massage & Relaxation > Massage Oil +Health & Beauty > Personal Care > Massage & Relaxation > Massage Stone Warmers +Health & Beauty > Personal Care > Massage & Relaxation > Massage Stones Health & Beauty > Personal Care > Massage & Relaxation > Massage Tables Health & Beauty > Personal Care > Massage & Relaxation > Massagers Health & Beauty > Personal Care > Oral Care Health & Beauty > Personal Care > Oral Care > Breath Spray Health & Beauty > Personal Care > Oral Care > Dental Floss +Health & Beauty > Personal Care > Oral Care > Dental Mouthguards Health & Beauty > Personal Care > Oral Care > Dental Water Jet Replacement Tips Health & Beauty > Personal Care > Oral Care > Dental Water Jets Health & Beauty > Personal Care > Oral Care > Denture Adhesives Health & Beauty > Personal Care > Oral Care > Denture Cleaners +Health & Beauty > Personal Care > Oral Care > Denture Repair Kits Health & Beauty > Personal Care > Oral Care > Dentures Health & Beauty > Personal Care > Oral Care > Gum Stimulators -Health & Beauty > Personal Care > Oral Care > Mouthguards Health & Beauty > Personal Care > Oral Care > Mouthwash -Health & Beauty > Personal Care > Oral Care > Oral Pain Relievers +Health & Beauty > Personal Care > Oral Care > Orthodontic Appliance Cases Health & Beauty > Personal Care > Oral Care > Power Flossers -Health & Beauty > Personal Care > Oral Care > Teeth Whitening +Health & Beauty > Personal Care > Oral Care > Teeth Whiteners Health & Beauty > Personal Care > Oral Care > Tongue Scrapers Health & Beauty > Personal Care > Oral Care > Toothbrush Accessories Health & Beauty > Personal Care > Oral Care > Toothbrush Accessories > Toothbrush Covers @@ -3451,6 +2900,7 @@ Health & Beauty > Personal Care > Oral Care > Toothpicks Health & Beauty > Personal Care > Personal Lubricants Health & Beauty > Personal Care > Shaving & Grooming Health & Beauty > Personal Care > Shaving & Grooming > Aftershave +Health & Beauty > Personal Care > Shaving & Grooming > Body & Facial Hair Bleach Health & Beauty > Personal Care > Shaving & Grooming > Electric Razor Accessories Health & Beauty > Personal Care > Shaving & Grooming > Electric Razors Health & Beauty > Personal Care > Shaving & Grooming > Hair Clipper & Trimmer Accessories @@ -3459,20 +2909,23 @@ Health & Beauty > Personal Care > Shaving & Grooming > Hair Removal Health & Beauty > Personal Care > Shaving & Grooming > Hair Removal > Depilatories Health & Beauty > Personal Care > Shaving & Grooming > Hair Removal > Electrolysis Devices Health & Beauty > Personal Care > Shaving & Grooming > Hair Removal > Epilators -Health & Beauty > Personal Care > Shaving & Grooming > Hair Removal > Laser Hair Removal Devices -Health & Beauty > Personal Care > Shaving & Grooming > Hair Removal > Waxing Kits +Health & Beauty > Personal Care > Shaving & Grooming > Hair Removal > Hair Removal Wax Warmers +Health & Beauty > Personal Care > Shaving & Grooming > Hair Removal > Laser & IPL Hair Removal Devices +Health & Beauty > Personal Care > Shaving & Grooming > Hair Removal > Waxing Kits & Supplies Health & Beauty > Personal Care > Shaving & Grooming > Razors & Razor Blades +Health & Beauty > Personal Care > Shaving & Grooming > Shaving Bowls & Mugs Health & Beauty > Personal Care > Shaving & Grooming > Shaving Brushes Health & Beauty > Personal Care > Shaving & Grooming > Shaving Cream Health & Beauty > Personal Care > Shaving & Grooming > Shaving Kits Health & Beauty > Personal Care > Shaving & Grooming > Styptic Pencils Health & Beauty > Personal Care > Sleeping Aids -Health & Beauty > Personal Care > Sleeping Aids > Sleep Masks +Health & Beauty > Personal Care > Sleeping Aids > Eye Masks Health & Beauty > Personal Care > Sleeping Aids > Snoring & Sleep Apnea Aids Health & Beauty > Personal Care > Sleeping Aids > Travel Pillows Health & Beauty > Personal Care > Sleeping Aids > White Noise Machines Health & Beauty > Personal Care > Spray Tanning Tents Health & Beauty > Personal Care > Tanning Beds +Health & Beauty > Personal Care > Tweezers Health & Beauty > Personal Care > Vision Care Health & Beauty > Personal Care > Vision Care > Contact Lens Care Health & Beauty > Personal Care > Vision Care > Contact Lens Care > Contact Lens Care Kits @@ -3480,12 +2933,13 @@ Health & Beauty > Personal Care > Vision Care > Contact Lens Care > Contact Lens Health & Beauty > Personal Care > Vision Care > Contact Lens Care > Contact Lens Solution Health & Beauty > Personal Care > Vision Care > Contact Lenses Health & Beauty > Personal Care > Vision Care > Eye Drops & Lubricants -Health & Beauty > Personal Care > Vision Care > Eyeglass Accessories -Health & Beauty > Personal Care > Vision Care > Eyeglass Accessories > Eyeglass Cases & Holders Health & Beauty > Personal Care > Vision Care > Eyeglass Lenses Health & Beauty > Personal Care > Vision Care > Eyeglasses +Health & Beauty > Personal Care > Vision Care > Eyewear Accessories +Health & Beauty > Personal Care > Vision Care > Eyewear Accessories > Eyewear Cases & Holders +Health & Beauty > Personal Care > Vision Care > Eyewear Accessories > Eyewear Lens Cleaning Solutions +Health & Beauty > Personal Care > Vision Care > Eyewear Accessories > Eyewear Straps & Chains Health & Beauty > Personal Care > Vision Care > Sunglass Lenses -Health & Beauty > Personal Care > Wart Removers Home & Garden Home & Garden > Bathroom Accessories Home & Garden > Bathroom Accessories > Bath Caddies @@ -3498,48 +2952,57 @@ Home & Garden > Bathroom Accessories > Hand Dryer Accessories Home & Garden > Bathroom Accessories > Hand Dryers Home & Garden > Bathroom Accessories > Medicine Cabinets Home & Garden > Bathroom Accessories > Robe Hooks +Home & Garden > Bathroom Accessories > Safety Grab Bars Home & Garden > Bathroom Accessories > Shower Curtain Rings Home & Garden > Bathroom Accessories > Shower Curtains +Home & Garden > Bathroom Accessories > Shower Rods Home & Garden > Bathroom Accessories > Soap & Lotion Dispensers Home & Garden > Bathroom Accessories > Soap Dishes & Holders Home & Garden > Bathroom Accessories > Toilet Brush Replacement Heads -Home & Garden > Bathroom Accessories > Toilet Brushes +Home & Garden > Bathroom Accessories > Toilet Brushes & Holders Home & Garden > Bathroom Accessories > Toilet Paper Holders Home & Garden > Bathroom Accessories > Toothbrush Holders Home & Garden > Bathroom Accessories > Towel Racks & Holders +Home & Garden > Business & Home Security +Home & Garden > Business & Home Security > Dummy Surveillance Cameras +Home & Garden > Business & Home Security > Home Alarm Systems +Home & Garden > Business & Home Security > Motion Sensors +Home & Garden > Business & Home Security > Safety & Security Mirrors +Home & Garden > Business & Home Security > Security Lights +Home & Garden > Business & Home Security > Security Monitors & Recorders +Home & Garden > Business & Home Security > Security Safe Accessories +Home & Garden > Business & Home Security > Security Safes +Home & Garden > Business & Home Security > Security System Sensors Home & Garden > Decor Home & Garden > Decor > Address Signs Home & Garden > Decor > Artificial Flora -Home & Garden > Decor > Artificial Flora > Artificial Flowers -Home & Garden > Decor > Artificial Flora > Artificial Plants -Home & Garden > Decor > Artificial Flora > Artificial Topiaries -Home & Garden > Decor > Artificial Flora > Artificial Trees Home & Garden > Decor > Artificial Food +Home & Garden > Decor > Artwork +Home & Garden > Decor > Artwork > Decorative Tapestries +Home & Garden > Decor > Artwork > Posters, Prints, & Visual Artwork +Home & Garden > Decor > Artwork > Sculptures & Statues Home & Garden > Decor > Backrest Pillows -Home & Garden > Decor > Backyard Feeders -Home & Garden > Decor > Backyard Feeders > Bird Feeders -Home & Garden > Decor > Backyard Feeders > Butterfly Feeders -Home & Garden > Decor > Backyard Feeders > Squirrel Feeders Home & Garden > Decor > Baskets +Home & Garden > Decor > Bird & Wildlife Feeder Accessories +Home & Garden > Decor > Bird & Wildlife Feeders +Home & Garden > Decor > Bird & Wildlife Feeders > Bird Feeders +Home & Garden > Decor > Bird & Wildlife Feeders > Butterfly Feeders +Home & Garden > Decor > Bird & Wildlife Feeders > Squirrel Feeders +Home & Garden > Decor > Bird & Wildlife House Accessories +Home & Garden > Decor > Bird & Wildlife Houses +Home & Garden > Decor > Bird & Wildlife Houses > Bat Houses +Home & Garden > Decor > Bird & Wildlife Houses > Birdhouses +Home & Garden > Decor > Bird & Wildlife Houses > Butterfly Houses Home & Garden > Decor > Bird Baths -Home & Garden > Decor > Bird Feeder Accessories -Home & Garden > Decor > Birdhouses Home & Garden > Decor > Bookends -Home & Garden > Decor > Candle & Oil Warmers -Home & Garden > Decor > Candle Holders -Home & Garden > Decor > Candle Holders > Menorahs -Home & Garden > Decor > Candles -Home & Garden > Decor > Candles > Hanukkah Candles -Home & Garden > Decor > Candles > Unity Candles +Home & Garden > Decor > Cardboard Cutouts Home & Garden > Decor > Chair & Sofa Cushions +Home & Garden > Decor > Clock Parts Home & Garden > Decor > Clocks Home & Garden > Decor > Clocks > Alarm Clocks -Home & Garden > Decor > Clocks > Alarm Clocks > Clock Radios -Home & Garden > Decor > Clocks > Alarm Clocks > Portable Alarm Clocks Home & Garden > Decor > Clocks > Desk & Shelf Clocks Home & Garden > Decor > Clocks > Floor & Grandfather Clocks Home & Garden > Decor > Clocks > Wall Clocks -Home & Garden > Decor > Clocks > Wall Clocks > Cuckoo Clocks Home & Garden > Decor > Coat & Hat Racks Home & Garden > Decor > Decorative Bells Home & Garden > Decor > Decorative Bottles @@ -3556,32 +3019,37 @@ Home & Garden > Decor > Figurines Home & Garden > Decor > Finials Home & Garden > Decor > Flag & Windsock Accessories Home & Garden > Decor > Flag & Windsock Accessories > Flag & Windsock Pole Lights +Home & Garden > Decor > Flag & Windsock Accessories > Flag & Windsock Pole Mounting Hardware & Kits Home & Garden > Decor > Flag & Windsock Accessories > Flag & Windsock Poles Home & Garden > Decor > Flags & Windsocks Home & Garden > Decor > Flameless Candles Home & Garden > Decor > Fountains & Ponds Home & Garden > Decor > Fountains & Ponds > Fountain & Pond Accessories -Home & Garden > Decor > Fountains & Ponds > Fountains -Home & Garden > Decor > Fountains & Ponds > Fountains > Indoor Fountains -Home & Garden > Decor > Fountains & Ponds > Fountains > Outdoor Fountains +Home & Garden > Decor > Fountains & Ponds > Fountains & Waterfalls Home & Garden > Decor > Fountains & Ponds > Ponds Home & Garden > Decor > Garden & Stepping Stones -Home & Garden > Decor > Garden Sculptures +Home & Garden > Decor > Growth Charts +Home & Garden > Decor > Home Decor Decals +Home & Garden > Decor > Home Fragrance Accessories +Home & Garden > Decor > Home Fragrance Accessories > Candle & Oil Warmers +Home & Garden > Decor > Home Fragrance Accessories > Candle Holders +Home & Garden > Decor > Home Fragrance Accessories > Candle Snuffers +Home & Garden > Decor > Home Fragrance Accessories > Incense Holders Home & Garden > Decor > Home Fragrances Home & Garden > Decor > Home Fragrances > Air Fresheners +Home & Garden > Decor > Home Fragrances > Candles Home & Garden > Decor > Home Fragrances > Fragrance Oil Home & Garden > Decor > Home Fragrances > Incense -Home & Garden > Decor > Home Fragrances > Incense Holders Home & Garden > Decor > Home Fragrances > Potpourri +Home & Garden > Decor > Home Fragrances > Wax Tarts +Home & Garden > Decor > Hourglasses Home & Garden > Decor > House Numbers & Letters -Home & Garden > Decor > Lawn Ornaments -Home & Garden > Decor > Lawn Ornaments > Gazing Globes +Home & Garden > Decor > Lawn Ornaments & Garden Sculptures Home & Garden > Decor > Mail Slots Home & Garden > Decor > Mailbox Accessories Home & Garden > Decor > Mailbox Accessories > Mailbox Covers Home & Garden > Decor > Mailbox Accessories > Mailbox Enclosures Home & Garden > Decor > Mailbox Accessories > Mailbox Flags -Home & Garden > Decor > Mailbox Accessories > Mailbox Mounting Brackets Home & Garden > Decor > Mailbox Accessories > Mailbox Posts Home & Garden > Decor > Mailbox Accessories > Mailbox Replacement Doors Home & Garden > Decor > Mailboxes @@ -3591,36 +3059,34 @@ Home & Garden > Decor > Napkin Rings Home & Garden > Decor > Novelty Signs Home & Garden > Decor > Ottoman Cushions Home & Garden > Decor > Picture Frames -Home & Garden > Decor > Picture Frames > Digital Picture Frames -Home & Garden > Decor > Picture Frames > Poster Frames Home & Garden > Decor > Piggy Banks & Money Jars Home & Garden > Decor > Rain Chains Home & Garden > Decor > Rain Gauges Home & Garden > Decor > Refrigerator Magnets Home & Garden > Decor > Rugs -Home & Garden > Decor > Rugs > Electric Rugs Home & Garden > Decor > Seasonal & Holiday Decorations Home & Garden > Decor > Seasonal & Holiday Decorations > Advent Calendars Home & Garden > Decor > Seasonal & Holiday Decorations > Christmas Tree Skirts Home & Garden > Decor > Seasonal & Holiday Decorations > Christmas Tree Stands -Home & Garden > Decor > Seasonal & Holiday Decorations > Holiday Ornament Displays +Home & Garden > Decor > Seasonal & Holiday Decorations > Easter Egg Decorating Kits +Home & Garden > Decor > Seasonal & Holiday Decorations > Holiday Ornament Displays & Stands +Home & Garden > Decor > Seasonal & Holiday Decorations > Holiday Ornament Hooks Home & Garden > Decor > Seasonal & Holiday Decorations > Holiday Ornaments Home & Garden > Decor > Seasonal & Holiday Decorations > Holiday Stocking Hangers Home & Garden > Decor > Seasonal & Holiday Decorations > Holiday Stockings +Home & Garden > Decor > Seasonal & Holiday Decorations > Japanese Traditional Dolls Home & Garden > Decor > Seasonal & Holiday Decorations > Nativity Sets -Home & Garden > Decor > Seasonal & Holiday Decorations > Ornament Storage Containers +Home & Garden > Decor > Seasonal & Holiday Decorations > Seasonal Village Sets & Accessories Home & Garden > Decor > Shadow Boxes Home & Garden > Decor > Slipcovers Home & Garden > Decor > Snow Globes Home & Garden > Decor > Suncatchers Home & Garden > Decor > Sundials Home & Garden > Decor > Throw Pillows -Home & Garden > Decor > Tiki Torches & Oil Lamps Home & Garden > Decor > Trunks +Home & Garden > Decor > Vase Fillers & Table Scatters Home & Garden > Decor > Vases -Home & Garden > Decor > Wall & Window Decals -Home & Garden > Decor > Wall Shelves & Ledges -Home & Garden > Decor > Wax Tarts +Home & Garden > Decor > Wallpaper Home & Garden > Decor > Weather Vanes & Roof Decor Home & Garden > Decor > Wind Chimes Home & Garden > Decor > Wind Wheels & Spinners @@ -3641,19 +3107,10 @@ Home & Garden > Decor > World Globes Home & Garden > Decor > Wreaths & Garlands Home & Garden > Emergency Preparedness Home & Garden > Emergency Preparedness > Earthquake Alarms -Home & Garden > Emergency Preparedness > Emergency Food +Home & Garden > Emergency Preparedness > Emergency Blankets +Home & Garden > Emergency Preparedness > Emergency Food Kits Home & Garden > Emergency Preparedness > Emergency Tools & Kits Home & Garden > Emergency Preparedness > Furniture Anchors -Home & Garden > Fire & Gas Safety -Home & Garden > Fire & Gas Safety > Carbon Monoxide Detectors -Home & Garden > Fire & Gas Safety > Fire Alarm Control Panels -Home & Garden > Fire & Gas Safety > Fire Alarms -Home & Garden > Fire & Gas Safety > Fire Extinguisher Cabinets -Home & Garden > Fire & Gas Safety > Fire Extinguishers -Home & Garden > Fire & Gas Safety > Fire Sprinklers -Home & Garden > Fire & Gas Safety > Heat Detectors -Home & Garden > Fire & Gas Safety > Radon Detectors -Home & Garden > Fire & Gas Safety > Smoke Detectors Home & Garden > Fireplace & Wood Stove Accessories Home & Garden > Fireplace & Wood Stove Accessories > Bellows Home & Garden > Fireplace & Wood Stove Accessories > Fireplace & Wood Stove Grates @@ -3667,63 +3124,46 @@ Home & Garden > Fireplace & Wood Stove Accessories > Log Rack & Carrier Accessor Home & Garden > Fireplace & Wood Stove Accessories > Log Racks & Carriers Home & Garden > Fireplace & Wood Stove Accessories > Wood Stove Fans & Blowers Home & Garden > Fireplaces -Home & Garden > Fireplaces > Indoor Fireplaces -Home & Garden > Fireplaces > Outdoor Fireplaces -Home & Garden > Home Security -Home & Garden > Home Security > Dummy Surveillance Cameras -Home & Garden > Home Security > Home Alarm Systems -Home & Garden > Home Security > Motion Sensors -Home & Garden > Home Security > Security Lights -Home & Garden > Home Security > Security Monitors & Recorders -Home & Garden > Home Security > Security Safes -Home & Garden > Home Security > Security System Sensors -Home & Garden > Home Security > Tracking Devices +Home & Garden > Flood, Fire & Gas Safety +Home & Garden > Flood, Fire & Gas Safety > Fire Alarm Control Panels +Home & Garden > Flood, Fire & Gas Safety > Fire Alarms +Home & Garden > Flood, Fire & Gas Safety > Fire Extinguisher & Equipment Storage +Home & Garden > Flood, Fire & Gas Safety > Fire Extinguishers +Home & Garden > Flood, Fire & Gas Safety > Fire Sprinklers +Home & Garden > Flood, Fire & Gas Safety > Heat Detectors +Home & Garden > Flood, Fire & Gas Safety > Smoke & Carbon Monoxide Detectors +Home & Garden > Flood, Fire & Gas Safety > Water & Flood Detectors Home & Garden > Household Appliance Accessories Home & Garden > Household Appliance Accessories > Air Conditioner Accessories Home & Garden > Household Appliance Accessories > Air Conditioner Accessories > Air Conditioner Covers Home & Garden > Household Appliance Accessories > Air Conditioner Accessories > Air Conditioner Filters Home & Garden > Household Appliance Accessories > Air Purifier Accessories Home & Garden > Household Appliance Accessories > Air Purifier Accessories > Air Purifier Filters -Home & Garden > Household Appliance Accessories > Carpet & Steam Cleaner Accessories Home & Garden > Household Appliance Accessories > Dehumidifier Accessories -Home & Garden > Household Appliance Accessories > Dehumidifier Accessories > Dehumidifier Filters Home & Garden > Household Appliance Accessories > Fan Accessories +Home & Garden > Household Appliance Accessories > Floor & Steam Cleaner Accessories Home & Garden > Household Appliance Accessories > Furnace & Boiler Accessories Home & Garden > Household Appliance Accessories > Heating Radiator Accessories Home & Garden > Household Appliance Accessories > Heating Radiator Accessories > Heating Radiator Reflectors Home & Garden > Household Appliance Accessories > Humidifier Accessories Home & Garden > Household Appliance Accessories > Humidifier Accessories > Humidifier Filters Home & Garden > Household Appliance Accessories > Laundry Appliance Accessories -Home & Garden > Household Appliance Accessories > Laundry Appliance Accessories > Dryer Accessories Home & Garden > Household Appliance Accessories > Laundry Appliance Accessories > Garment Steamer Accessories Home & Garden > Household Appliance Accessories > Laundry Appliance Accessories > Iron Accessories Home & Garden > Household Appliance Accessories > Laundry Appliance Accessories > Steam Press Accessories -Home & Garden > Household Appliance Accessories > Laundry Appliance Accessories > Washing Machine Accessories -Home & Garden > Household Appliance Accessories > Laundry Appliance Accessories > Washing Machine Accessories > Washing Machine & Dryer Pedestals -Home & Garden > Household Appliance Accessories > Laundry Appliance Accessories > Washing Machine Accessories > Washing Machine Hoses -Home & Garden > Household Appliance Accessories > Laundry Appliance Accessories > Washing Machine Accessories > Washing Machine Valves +Home & Garden > Household Appliance Accessories > Laundry Appliance Accessories > Washer & Dryer Accessories Home & Garden > Household Appliance Accessories > Patio Heater Accessories Home & Garden > Household Appliance Accessories > Patio Heater Accessories > Patio Heater Covers -Home & Garden > Household Appliance Accessories > Sewing Machine Accessories -Home & Garden > Household Appliance Accessories > Sewing Machine Accessories > Bobbins & Bobbin Cases -Home & Garden > Household Appliance Accessories > Sewing Machine Accessories > Sewing Machine Cases & Covers -Home & Garden > Household Appliance Accessories > Sewing Machine Accessories > Sewing Machine Feet -Home & Garden > Household Appliance Accessories > Sewing Machine Accessories > Sewing Machine Needles Home & Garden > Household Appliance Accessories > Vacuum Accessories -Home & Garden > Household Appliance Accessories > Vacuum Accessories > Vacuum Attachments -Home & Garden > Household Appliance Accessories > Vacuum Accessories > Vacuum Bags -Home & Garden > Household Appliance Accessories > Vacuum Accessories > Vacuum Belts -Home & Garden > Household Appliance Accessories > Vacuum Accessories > Vacuum Filters -Home & Garden > Household Appliance Accessories > Vacuum Accessories > Vacuum Hoses Home & Garden > Household Appliance Accessories > Water Heater Accessories Home & Garden > Household Appliance Accessories > Water Heater Accessories > Anode Rods -Home & Garden > Household Appliance Accessories > Water Heater Accessories > Hot Water Cylinders +Home & Garden > Household Appliance Accessories > Water Heater Accessories > Hot Water Tanks Home & Garden > Household Appliance Accessories > Water Heater Accessories > Water Heater Elements +Home & Garden > Household Appliance Accessories > Water Heater Accessories > Water Heater Expansion Tanks Home & Garden > Household Appliance Accessories > Water Heater Accessories > Water Heater Pans Home & Garden > Household Appliance Accessories > Water Heater Accessories > Water Heater Stacks Home & Garden > Household Appliance Accessories > Water Heater Accessories > Water Heater Vents Home & Garden > Household Appliances -Home & Garden > Household Appliances > Carpet & Steam Cleaners Home & Garden > Household Appliances > Climate Control Appliances Home & Garden > Household Appliances > Climate Control Appliances > Air Conditioners Home & Garden > Household Appliances > Climate Control Appliances > Air Purifiers @@ -3733,40 +3173,41 @@ Home & Garden > Household Appliances > Climate Control Appliances > Evaporative Home & Garden > Household Appliances > Climate Control Appliances > Fans Home & Garden > Household Appliances > Climate Control Appliances > Fans > Ceiling Fans Home & Garden > Household Appliances > Climate Control Appliances > Fans > Desk & Pedestal Fans -Home & Garden > Household Appliances > Climate Control Appliances > Fans > Personal Handheld Fans +Home & Garden > Household Appliances > Climate Control Appliances > Fans > Powered Hand Fans & Misters Home & Garden > Household Appliances > Climate Control Appliances > Fans > Ventilation Fans +Home & Garden > Household Appliances > Climate Control Appliances > Fans > Wall Mount Fans Home & Garden > Household Appliances > Climate Control Appliances > Furnaces & Boilers Home & Garden > Household Appliances > Climate Control Appliances > Heating Radiators Home & Garden > Household Appliances > Climate Control Appliances > Humidifiers -Home & Garden > Household Appliances > Climate Control Appliances > Kotatsu Home & Garden > Household Appliances > Climate Control Appliances > Outdoor Misting Systems Home & Garden > Household Appliances > Climate Control Appliances > Patio Heaters Home & Garden > Household Appliances > Climate Control Appliances > Space Heaters +Home & Garden > Household Appliances > Floor & Carpet Dryers +Home & Garden > Household Appliances > Floor & Steam Cleaners Home & Garden > Household Appliances > Floor Polishers & Buffers Home & Garden > Household Appliances > Futon Dryers +Home & Garden > Household Appliances > Garage Door Keypads & Remotes Home & Garden > Household Appliances > Garage Door Openers -Home & Garden > Household Appliances > Garage Door Remotes -Home & Garden > Household Appliances > Garage Parking Sensors -Home & Garden > Household Appliances > Home Automation Home & Garden > Household Appliances > Laundry Appliances Home & Garden > Household Appliances > Laundry Appliances > Dryers Home & Garden > Household Appliances > Laundry Appliances > Garment Steamers -Home & Garden > Household Appliances > Laundry Appliances > Irons +Home & Garden > Household Appliances > Laundry Appliances > Irons & Ironing Systems Home & Garden > Household Appliances > Laundry Appliances > Laundry Combo Units Home & Garden > Household Appliances > Laundry Appliances > Steam Presses Home & Garden > Household Appliances > Laundry Appliances > Washing Machines -Home & Garden > Household Appliances > Sewing Machines -Home & Garden > Household Appliances > Sewing Machines > Sergers +Home & Garden > Household Appliances > Ultrasonic Cleaners Home & Garden > Household Appliances > Vacuums Home & Garden > Household Appliances > Wallpaper Steamers Home & Garden > Household Appliances > Water Heaters -Home & Garden > Household Appliances > Water Heaters > Immersion Heaters Home & Garden > Household Supplies Home & Garden > Household Supplies > Drawer & Shelf Liners +Home & Garden > Household Supplies > Floor Protection Films & Runners Home & Garden > Household Supplies > Furniture Floor Protectors +Home & Garden > Household Supplies > Garage Floor Mats Home & Garden > Household Supplies > Garbage Bags Home & Garden > Household Supplies > Household Cleaning Supplies Home & Garden > Household Supplies > Household Cleaning Supplies > Broom & Mop Handles +Home & Garden > Household Supplies > Household Cleaning Supplies > Broom Heads Home & Garden > Household Supplies > Household Cleaning Supplies > Brooms Home & Garden > Household Supplies > Household Cleaning Supplies > Buckets Home & Garden > Household Supplies > Household Cleaning Supplies > Carpet Sweepers @@ -3778,26 +3219,26 @@ Home & Garden > Household Supplies > Household Cleaning Supplies > Fabric & Upho Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > All-Purpose Cleaners Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Carpet Cleaners +Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Descalers & Decalcifiers Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Dish Detergent & Soap Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Dishwasher Cleaners Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Fabric & Upholstery Cleaners +Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Floor Cleaners Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Furniture Cleaners & Polish Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Glass & Surface Cleaners -Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Hardwood Floor Cleaners Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Household Disinfectants -Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Leather Care & Dyes Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Oven & Grill Cleaners Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Pet Odor & Stain Removers Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Rinse Aids Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Stainless Steel Cleaners & Polishes -Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Stone Cleaners Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Toilet Bowl Cleaners Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Tub & Tile Cleaners Home & Garden > Household Supplies > Household Cleaning Supplies > Household Cleaning Products > Washing Machine Cleaners Home & Garden > Household Supplies > Household Cleaning Supplies > Mop Heads & Refills Home & Garden > Household Supplies > Household Cleaning Supplies > Mops +Home & Garden > Household Supplies > Household Cleaning Supplies > Scrub Brush Heads & Refills Home & Garden > Household Supplies > Household Cleaning Supplies > Scrub Brushes -Home & Garden > Household Supplies > Household Cleaning Supplies > Scrub Brushes > Baby Bottle Brushes +Home & Garden > Household Supplies > Household Cleaning Supplies > Shop Towels & General-Purpose Cleaning Cloths Home & Garden > Household Supplies > Household Cleaning Supplies > Sponges & Scouring Pads Home & Garden > Household Supplies > Household Cleaning Supplies > Squeegees Home & Garden > Household Supplies > Household Paper Products @@ -3817,14 +3258,16 @@ Home & Garden > Household Supplies > Laundry Supplies > Fabric Softeners & Dryer Home & Garden > Household Supplies > Laundry Supplies > Fabric Stain Removers Home & Garden > Household Supplies > Laundry Supplies > Fabric Starch Home & Garden > Household Supplies > Laundry Supplies > Garment Shields +Home & Garden > Household Supplies > Laundry Supplies > Iron Rests Home & Garden > Household Supplies > Laundry Supplies > Ironing Board Pads & Covers +Home & Garden > Household Supplies > Laundry Supplies > Ironing Board Replacement Parts Home & Garden > Household Supplies > Laundry Supplies > Ironing Boards Home & Garden > Household Supplies > Laundry Supplies > Laundry Balls Home & Garden > Household Supplies > Laundry Supplies > Laundry Baskets Home & Garden > Household Supplies > Laundry Supplies > Laundry Detergent +Home & Garden > Household Supplies > Laundry Supplies > Laundry Wash Bags & Frames Home & Garden > Household Supplies > Laundry Supplies > Lint Rollers -Home & Garden > Household Supplies > Laundry Supplies > Washing Bags & Baskets -Home & Garden > Household Supplies > Laundry Supplies > Wrinkle Releasers & Static Sprays +Home & Garden > Household Supplies > Laundry Supplies > Wrinkle Releasers & Anti-Static Sprays Home & Garden > Household Supplies > Moisture Absorbers Home & Garden > Household Supplies > Pest Control Home & Garden > Household Supplies > Pest Control > Fly Swatters @@ -3840,19 +3283,23 @@ Home & Garden > Household Supplies > Shoe Care & Tools > Shoe Bags Home & Garden > Household Supplies > Shoe Care & Tools > Shoe Brushes Home & Garden > Household Supplies > Shoe Care & Tools > Shoe Care Kits Home & Garden > Household Supplies > Shoe Care & Tools > Shoe Dryers -Home & Garden > Household Supplies > Shoe Care & Tools > Shoe Horns +Home & Garden > Household Supplies > Shoe Care & Tools > Shoe Horns & Dressing Aids Home & Garden > Household Supplies > Shoe Care & Tools > Shoe Polishers Home & Garden > Household Supplies > Shoe Care & Tools > Shoe Polishes & Waxes Home & Garden > Household Supplies > Shoe Care & Tools > Shoe Scrapers Home & Garden > Household Supplies > Shoe Care & Tools > Shoe Treatments & Dyes Home & Garden > Household Supplies > Shoe Care & Tools > Shoe Trees & Shapers +Home & Garden > Household Supplies > Stair Treads Home & Garden > Household Supplies > Storage & Organization Home & Garden > Household Supplies > Storage & Organization > Clothing & Closet Storage +Home & Garden > Household Supplies > Storage & Organization > Clothing & Closet Storage > Charging Valets +Home & Garden > Household Supplies > Storage & Organization > Clothing & Closet Storage > Closet Organizers & Garment Racks +Home & Garden > Household Supplies > Storage & Organization > Clothing & Closet Storage > Clothes Valets Home & Garden > Household Supplies > Storage & Organization > Clothing & Closet Storage > Hangers Home & Garden > Household Supplies > Storage & Organization > Clothing & Closet Storage > Hat Boxes Home & Garden > Household Supplies > Storage & Organization > Clothing & Closet Storage > Shoe Racks & Organizers Home & Garden > Household Supplies > Storage & Organization > Flatware Chests -Home & Garden > Household Supplies > Storage & Organization > Gift Wrap Storage & Organization +Home & Garden > Household Supplies > Storage & Organization > Household Drawer Organizer Inserts Home & Garden > Household Supplies > Storage & Organization > Household Storage Bags Home & Garden > Household Supplies > Storage & Organization > Household Storage Caddies Home & Garden > Household Supplies > Storage & Organization > Household Storage Containers @@ -3860,61 +3307,59 @@ Home & Garden > Household Supplies > Storage & Organization > Household Storage Home & Garden > Household Supplies > Storage & Organization > Photo Storage Home & Garden > Household Supplies > Storage & Organization > Photo Storage > Photo Albums Home & Garden > Household Supplies > Storage & Organization > Photo Storage > Photo Storage Boxes -Home & Garden > Household Supplies > Storage & Organization > Pillboxes Home & Garden > Household Supplies > Storage & Organization > Storage Hooks & Racks -Home & Garden > Household Supplies > Storage & Organization > Storage Hooks & Racks > Garment Racks +Home & Garden > Household Supplies > Storage & Organization > Storage Hooks & Racks > Ironing Board Hooks & Racks Home & Garden > Household Supplies > Storage & Organization > Storage Hooks & Racks > Umbrella Stands & Racks Home & Garden > Household Supplies > Storage & Organization > Storage Hooks & Racks > Utility Hooks -Home & Garden > Household Supplies > Storage & Organization > Valets -Home & Garden > Household Supplies > Storage & Organization > Valets > Charging Valets -Home & Garden > Household Supplies > Storage & Organization > Valets > Clothes Valets -Home & Garden > Household Supplies > Storage & Organization > Valets > Dresser Valets Home & Garden > Household Supplies > Trash Compactor Accessories Home & Garden > Household Supplies > Waste Containment +Home & Garden > Household Supplies > Waste Containment > Dumpsters Home & Garden > Household Supplies > Waste Containment > Hazardous Waste Containers Home & Garden > Household Supplies > Waste Containment > Recycling Containers Home & Garden > Household Supplies > Waste Containment > Trash Cans & Wastebaskets Home & Garden > Household Supplies > Waste Containment Accessories Home & Garden > Household Supplies > Waste Containment Accessories > Waste Container Carts -Home & Garden > Household Supplies > Waste Containment Accessories > Waste Container Frames +Home & Garden > Household Supplies > Waste Containment Accessories > Waste Container Enclosures +Home & Garden > Household Supplies > Waste Containment Accessories > Waste Container Labels & Signs Home & Garden > Household Supplies > Waste Containment Accessories > Waste Container Lids Home & Garden > Household Supplies > Waste Containment Accessories > Waste Container Wheels Home & Garden > Kitchen & Dining Home & Garden > Kitchen & Dining > Barware Home & Garden > Kitchen & Dining > Barware > Absinthe Fountains Home & Garden > Kitchen & Dining > Barware > Beer Dispensers & Taps +Home & Garden > Kitchen & Dining > Barware > Beverage Chilling Cubes & Sticks +Home & Garden > Kitchen & Dining > Barware > Beverage Tubs & Chillers +Home & Garden > Kitchen & Dining > Barware > Bottle Caps Home & Garden > Kitchen & Dining > Barware > Bottle Stoppers & Savers Home & Garden > Kitchen & Dining > Barware > Coaster Holders Home & Garden > Kitchen & Dining > Barware > Coasters +Home & Garden > Kitchen & Dining > Barware > Cocktail & Barware Tool Sets +Home & Garden > Kitchen & Dining > Barware > Cocktail Shakers & Tools +Home & Garden > Kitchen & Dining > Barware > Cocktail Shakers & Tools > Bar Ice Picks +Home & Garden > Kitchen & Dining > Barware > Cocktail Shakers & Tools > Bottle Openers +Home & Garden > Kitchen & Dining > Barware > Cocktail Shakers & Tools > Cocktail Shakers +Home & Garden > Kitchen & Dining > Barware > Cocktail Shakers & Tools > Cocktail Strainers +Home & Garden > Kitchen & Dining > Barware > Cocktail Shakers & Tools > Muddlers Home & Garden > Kitchen & Dining > Barware > Corkscrews Home & Garden > Kitchen & Dining > Barware > Decanters -Home & Garden > Kitchen & Dining > Barware > Drink Shakers & Tools -Home & Garden > Kitchen & Dining > Barware > Drink Shakers & Tools > Bar Ice Picks -Home & Garden > Kitchen & Dining > Barware > Drink Shakers & Tools > Bottle Openers -Home & Garden > Kitchen & Dining > Barware > Drink Shakers & Tools > Drink Shakers -Home & Garden > Kitchen & Dining > Barware > Drink Shakers & Tools > Drink Tool Sets Home & Garden > Kitchen & Dining > Barware > Foil Cutters -Home & Garden > Kitchen & Dining > Barware > Ice Buckets -Home & Garden > Kitchen & Dining > Barware > Whiskey Stones Home & Garden > Kitchen & Dining > Barware > Wine Aerators -Home & Garden > Kitchen & Dining > Barware > Wine Buckets & Chillers +Home & Garden > Kitchen & Dining > Barware > Wine Bottle Holders Home & Garden > Kitchen & Dining > Barware > Wine Glass Charms Home & Garden > Kitchen & Dining > Cookware & Bakeware Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Bakeware Sets Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Baking & Cookie Sheets -Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Baking & Specialty Molds -Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Bread Pans +Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Bread Pans & Molds Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Broiling Pans Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Cake Pans & Molds -Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Muffin & Cupcake Pans +Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Muffin & Pastry Pans Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Pie & Quiche Pans Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Pizza Pans Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Pizza Stones Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Ramekins & Souffle Dishes Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware > Roasting Pans Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware Accessories -Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware Accessories > Baking Cups Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware Accessories > Baking Mats & Liners Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware Accessories > Baking Weights Home & Garden > Kitchen & Dining > Cookware & Bakeware > Bakeware Accessories > Roasting Pan Racks @@ -3922,26 +3367,25 @@ 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 Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Griddles & Grill Pans Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Grill Presses Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Paella Pans -Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Pressure Cookers -Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Pressure Cookers > Electric Pressure Cookers +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 -Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Tajines +Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Tagines & Clay Cooking Pots Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Woks Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware Accessories Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware Accessories > Pot & Pan Handles Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware Accessories > Pot & Pan Lids -Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware Accessories > Pressure Cooker Accessories +Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware Accessories > Pressure Cooker & Canner Accessories Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware Accessories > Steamer Baskets Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware Accessories > Wok Accessories Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware Accessories > Wok Accessories > Wok Brushes @@ -3950,7 +3394,6 @@ Home & Garden > Kitchen & Dining > Food & Beverage Carriers Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Airpots Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Canteens Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Coolers -Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Drink Lids Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Drink Sleeves Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Drink Sleeves > Can & Bottle Sleeves Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Drink Sleeves > Cup Sleeves @@ -3958,14 +3401,15 @@ Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Flasks Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Insulated Bags Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Lunch Boxes & Totes Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Picnic Baskets +Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Replacement Drink Lids Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Thermoses Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Water Bottles Home & Garden > Kitchen & Dining > Food & Beverage Carriers > Wine Carrier Bags Home & Garden > Kitchen & Dining > Food Storage -Home & Garden > Kitchen & Dining > Food Storage > Bowl Covers Home & Garden > Kitchen & Dining > Food Storage > Bread Boxes & Bags Home & Garden > Kitchen & Dining > Food Storage > Candy Buckets Home & Garden > Kitchen & Dining > Food Storage > Cookie Jars +Home & Garden > Kitchen & Dining > Food Storage > Food Container Covers Home & Garden > Kitchen & Dining > Food Storage > Food Storage Bags Home & Garden > Kitchen & Dining > Food Storage > Food Storage Containers Home & Garden > Kitchen & Dining > Food Storage > Food Wraps @@ -3975,50 +3419,38 @@ Home & Garden > Kitchen & Dining > Food Storage > Food Wraps > Plastic Wrap Home & Garden > Kitchen & Dining > Food Storage > Food Wraps > Wax Paper Home & Garden > Kitchen & Dining > Food Storage > Honey Jars Home & Garden > Kitchen & Dining > Food Storage Accessories +Home & Garden > Kitchen & Dining > Food Storage Accessories > Food & Beverage Labels Home & Garden > Kitchen & Dining > Food Storage Accessories > Food Wrap Dispensers Home & Garden > Kitchen & Dining > Food Storage Accessories > Oxygen Absorbers Home & Garden > Kitchen & Dining > Food Storage Accessories > Twist Ties & Bag Clips Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Blender Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Blender Accessories > Blender Jars -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Blender Accessories > Blender Lids Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Breadmaker Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories > Coffee Decanter Warmers Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories > Coffee Decanters Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories > Coffee Filter Baskets Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories > Coffee Filters +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories > Coffee Grinder Accessories +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories > Coffee Grinders +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories > Coffee Maker & Espresso Machine Replacement Parts Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories > Coffee Maker Water Filters Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories > Frothing Pitchers Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories > Portafilters -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Coffee Maker & Espresso Machine Accessories > Stovetop Espresso Pot Parts -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Cooktop Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Cooktop Accessories > Cooktop Burner Grates +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Cooktop, Oven & Range Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Cotton Candy Machine Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Deep Fryer Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Deep Fryer Accessories > Fryer Baskets -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Dishwasher Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Dishwasher Accessories > Dishwasher Glass Racks +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Dishwasher Parts & Accessories +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Electric Kettle Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Electric Skillet & Wok Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Fondue Set Accessories +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Fondue Set Accessories > Cooking Gel Fuels Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Fondue Set Accessories > Fondue Forks Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Fondue Set Accessories > Fondue Pot Stands Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Dehydrator Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Dehydrator Accessories > Food Dehydrator Sheets Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Dehydrator Accessories > Food Dehydrator Trays Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Grinder Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Grinder Accessories > Coffee Grinder Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Grinder Accessories > Meat Grinder Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Grinder Accessories > Spice Grinder Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Mixer Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Mixer Accessories > Food Mixer Attachments -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Mixer Accessories > Food Mixer Attachments > Juicer Attachments -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Mixer Accessories > Food Mixer Attachments > Meat Grinder Attachments -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Mixer Accessories > Food Mixer Attachments > Pasta Maker Attachments -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Mixer Accessories > Food Mixer Beaters -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Mixer Accessories > Food Mixer Bowls -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Mixer Accessories > Food Mixer Hooks -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Processor Accessories +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Food Mixer & Blender Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Freezer Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Garbage Disposal Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Ice Cream Maker Accessories @@ -4027,41 +3459,35 @@ Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Ice Crusher & Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Ice Maker Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Juicer Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Microwave Oven Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Microwave Oven Accessories > Microwave Drawers -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Microwave Oven Accessories > Microwave Trim Kits Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Outdoor Grill Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Outdoor Grill Accessories > Charcoal Briquettes Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Outdoor Grill Accessories > Charcoal Chimneys -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Outdoor Grill Accessories > Outdoor Grill Burners Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Outdoor Grill Accessories > Outdoor Grill Carts Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Outdoor Grill Accessories > Outdoor Grill Covers Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Outdoor Grill Accessories > Outdoor Grill Racks & Toppers +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Outdoor Grill Accessories > Outdoor Grill Replacement Parts Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Outdoor Grill Accessories > Outdoor Grill Spits & Baskets Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Outdoor Grill Accessories > Outdoor Grilling Planks Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Outdoor Grill Accessories > Smoking Chips & Pellets -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Oven Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Oven Accessories > Oven Liners -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Oven Accessories > Oven Rack Guards -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Oven Accessories > Oven Racks Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Pasta Maker Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Popcorn Maker Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Portable Cooking Stove Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Range Accessories -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Range Accessories > Range Burner Covers -Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Range Accessories > Range Drip Pans +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Range Hood Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Refrigerator Accessories +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Soda Maker Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Steam Table Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Steam Table Accessories > Steam Table Pan Covers Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Steam Table Accessories > Steam Table Pans Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Toaster Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Vacuum Sealer Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Vacuum Sealer Accessories > Vacuum Sealer Bags +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Waffle Iron Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Water Cooler Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Water Cooler Accessories > Water Cooler Bottles +Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Wine Fridge Accessories Home & Garden > Kitchen & Dining > Kitchen Appliance Accessories > Yogurt Maker Accessories Home & Garden > Kitchen & Dining > Kitchen Appliances Home & Garden > Kitchen & Dining > Kitchen Appliances > Beverage Warmers -Home & Garden > Kitchen & Dining > Kitchen Appliances > Blenders Home & Garden > Kitchen & Dining > Kitchen Appliances > Breadmakers Home & Garden > Kitchen & Dining > Kitchen Appliances > Chocolate Tempering Machines Home & Garden > Kitchen & Dining > Kitchen Appliances > Coffee Makers & Espresso Machines @@ -4073,15 +3499,13 @@ Home & Garden > Kitchen & Dining > Kitchen Appliances > Coffee Makers & Espresso Home & Garden > Kitchen & Dining > Kitchen Appliances > Coffee Makers & Espresso Machines > Vacuum Coffee Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Cooktops Home & Garden > Kitchen & Dining > Kitchen Appliances > Cotton Candy Machines -Home & Garden > Kitchen & Dining > Kitchen Appliances > Crêpe Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Deep Fryers +Home & Garden > Kitchen & Dining > Kitchen Appliances > Deli Slicers Home & Garden > Kitchen & Dining > Kitchen Appliances > Dishwashers Home & Garden > Kitchen & Dining > Kitchen Appliances > Electric Griddles & Grills -Home & Garden > Kitchen & Dining > Kitchen Appliances > Electric Griddles & Grills > Electric Griddles -Home & Garden > Kitchen & Dining > Kitchen Appliances > Electric Griddles & Grills > Electric Grills Home & Garden > Kitchen & Dining > Kitchen Appliances > Electric Kettles Home & Garden > Kitchen & Dining > Kitchen Appliances > Electric Skillets & Woks -Home & Garden > Kitchen & Dining > Kitchen Appliances > Fondue Sets +Home & Garden > Kitchen & Dining > Kitchen Appliances > Fondue Pots & Sets Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Cookers & Steamers Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Cookers & Steamers > Egg Cookers Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Cookers & Steamers > Food Steamers @@ -4091,20 +3515,15 @@ Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Cookers & Steamers Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Cookers & Steamers > Water Ovens Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Dehydrators Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Grinders & Mills -Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Grinders & Mills > Coffee Grinders -Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Grinders & Mills > Grain Mills -Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Grinders & Mills > Meat Grinders -Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Grinders & Mills > Spice Grinders -Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Mixers -Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Processors -Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Slicers -Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Slicers > Deli Slicers +Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Mixers & Blenders Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Smokers Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Warmers Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Warmers > Chafing Dishes Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Warmers > Food Heat Lamps +Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Warmers > Rice Keepers Home & Garden > Kitchen & Dining > Kitchen Appliances > Food Warmers > Steam Tables Home & Garden > Kitchen & Dining > Kitchen Appliances > Freezers +Home & Garden > Kitchen & Dining > Kitchen Appliances > Frozen Drink Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Garbage Disposals Home & Garden > Kitchen & Dining > Kitchen Appliances > Gas Griddles Home & Garden > Kitchen & Dining > Kitchen Appliances > Hot Drink Makers @@ -4113,18 +3532,12 @@ Home & Garden > Kitchen & Dining > Kitchen Appliances > Ice Cream Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Ice Crushers & Shavers Home & Garden > Kitchen & Dining > Kitchen Appliances > Ice Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Juicers -Home & Garden > Kitchen & Dining > Kitchen Appliances > Juicers > Hand Juicers Home & Garden > Kitchen & Dining > Kitchen Appliances > Knife Sharpeners Home & Garden > Kitchen & Dining > Kitchen Appliances > Microwave Ovens Home & Garden > Kitchen & Dining > Kitchen Appliances > Milk Frothers & Steamers Home & Garden > Kitchen & Dining > Kitchen Appliances > Mochi Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Outdoor Grills -Home & Garden > Kitchen & Dining > Kitchen Appliances > Outdoor Grills > Charcoal Grills -Home & Garden > Kitchen & Dining > Kitchen Appliances > Outdoor Grills > Gas Grills Home & Garden > Kitchen & Dining > Kitchen Appliances > Ovens -Home & Garden > Kitchen & Dining > Kitchen Appliances > Ovens > Conveyor Ovens -Home & Garden > Kitchen & Dining > Kitchen Appliances > Ovens > Deck Ovens -Home & Garden > Kitchen & Dining > Kitchen Appliances > Ovens > Wall Ovens Home & Garden > Kitchen & Dining > Kitchen Appliances > Pasta Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Popcorn Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Portable Cooking Stoves @@ -4132,18 +3545,17 @@ Home & Garden > Kitchen & Dining > Kitchen Appliances > Range Hoods Home & Garden > Kitchen & Dining > Kitchen Appliances > Ranges Home & Garden > Kitchen & Dining > Kitchen Appliances > Refrigerators Home & Garden > Kitchen & Dining > Kitchen Appliances > Roaster Ovens & Rotisseries -Home & Garden > Kitchen & Dining > Kitchen Appliances > Slush Machines Home & Garden > Kitchen & Dining > Kitchen Appliances > Soda Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Soy Milk Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Tea Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills +Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills > Countertop & Toaster Ovens Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills > Donut Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills > Muffin & Cupcake Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills > Pizza Makers & Ovens Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills > Pizzelle Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills > Pretzel Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills > Sandwich Makers -Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills > Toaster Ovens Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills > Toasters Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills > Tortilla & Flatbread Makers Home & Garden > Kitchen & Dining > Kitchen Appliances > Toasters & Grills > Waffle Irons @@ -4163,10 +3575,8 @@ Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Cake Decorating Su Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Cake Servers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Can Crushers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Can Openers -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Candy & Chocolate Molds Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Carving Forks Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Channel Knives -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Citrus Reamers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Colanders & Strainers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Condiment Dispensers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Cookie Cutters @@ -4177,57 +3587,50 @@ Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Cooking Timers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Cooking Torches Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Cooling Racks Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Cutting Boards -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Dicers & Choppers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Dish Racks & Drain Boards Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Dough Wheels Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Electric Knife Accessories Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Electric Knife Accessories > Electric Knife Replacement Blades Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Electric Knives Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Flour Sifters +Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Food & Drink Stencils Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Food Crackers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Food Crackers > Lobster & Crab Crackers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Food Crackers > Nutcrackers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Food Crackers > Nutcrackers > Decorative Nutcrackers -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Food Cutters & Wedgers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Food Dispensers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Food Graters & Zesters Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Food Peelers & Corers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Food Steaming Bags Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Food Sticks & Skewers +Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Funnels Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Garlic Presses Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Gelatin Molds Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Ice Cube Trays -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Ice Pop Molds Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Jerky Guns Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Knives Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Molds Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Can Organizers -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Drinkware Racks +Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Drinkware Holders Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Kitchen Cabinet Organizers -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Kitchen Storage Carousels -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Kitchen Utensil Holders +Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Kitchen Counter & Beverage Station Organizers +Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Kitchen Utensil Holders & Racks Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Knife Blocks & Holders Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Napkin Holders & Dispensers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Paper Towel Holders & Dispensers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Pot Racks -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Single Serve Beverage Organizers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Spice Organizers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Straw Holders & Dispensers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Sugar Caddies Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Toothpick Holders & Dispensers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Organizers > Utensil & Flatware Trays -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Scales Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Scrapers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Scrapers > Bench Scrapers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Scrapers > Bowl Scrapers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Scrapers > Grill Scrapers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Shears Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Slicers -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Slicers > Bread Slicers -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Slicers > Cheese Slicers -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Slicers > Egg Slicers -Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Slicers > Mandoline Slicers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Kitchen Utensil Sets Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Ladles Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Mashers @@ -4254,16 +3657,20 @@ Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Scoops Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Scoops > Ice Cream Scoops Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Scoops > Ice Scoops Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Scoops > Melon Ballers +Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Scoops > Popcorn & French Fry Scoops Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Sink Caddies Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Sink Mats & Grids Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Slotted Spoons Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Spatulas +Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Spice Grinder Accessories +Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Spice Grinders Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Spoon Rests Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Sugar Dispensers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Sushi Mats Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Tea Strainers Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Tongs Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils > Whisks +Home & Garden > Kitchen & Dining > Prefabricated Kitchens & Kitchenettes Home & Garden > Kitchen & Dining > Tableware Home & Garden > Kitchen & Dining > Tableware > Coffee & Tea Sets Home & Garden > Kitchen & Dining > Tableware > Coffee Servers & Tea Pots @@ -4273,29 +3680,15 @@ Home & Garden > Kitchen & Dining > Tableware > Dinnerware > Dinnerware Sets Home & Garden > Kitchen & Dining > Tableware > Dinnerware > Plates Home & Garden > Kitchen & Dining > Tableware > Drinkware Home & Garden > Kitchen & Dining > Tableware > Drinkware > Beer Glasses -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Beer Glasses > Pilsner Glasses -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Beer Glasses > Pint Glasses -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Beer Glasses > Tankards & Beer Steins Home & Garden > Kitchen & Dining > Tableware > Drinkware > Coffee & Tea Cups -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Coffee & Tea Cups > Coffee Cups -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Coffee & Tea Cups > Tea Cups Home & Garden > Kitchen & Dining > Tableware > Drinkware > Coffee & Tea Saucers Home & Garden > Kitchen & Dining > Tableware > Drinkware > Drinkware Sets Home & Garden > Kitchen & Dining > Tableware > Drinkware > Mugs Home & Garden > Kitchen & Dining > Tableware > Drinkware > Shot Glasses Home & Garden > Kitchen & Dining > Tableware > Drinkware > Stemware -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Stemware > Brandy Snifters -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Stemware > Champagne Glasses -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Stemware > Margarita Glasses -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Stemware > Martini Glasses -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Stemware > Wine Glasses Home & Garden > Kitchen & Dining > Tableware > Drinkware > Tumblers -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Tumblers > Highball Glasses -Home & Garden > Kitchen & Dining > Tableware > Drinkware > Tumblers > Lowball Glasses Home & Garden > Kitchen & Dining > Tableware > Flatware Home & Garden > Kitchen & Dining > Tableware > Flatware > Chopstick Accessories -Home & Garden > Kitchen & Dining > Tableware > Flatware > Chopstick Accessories > Chopstick Connectors -Home & Garden > Kitchen & Dining > Tableware > Flatware > Chopstick Accessories > Chopstick Rests Home & Garden > Kitchen & Dining > Tableware > Flatware > Chopsticks Home & Garden > Kitchen & Dining > Tableware > Flatware > Flatware Sets Home & Garden > Kitchen & Dining > Tableware > Flatware > Forks @@ -4318,6 +3711,7 @@ Home & Garden > Kitchen & Dining > Tableware > Serveware Accessories Home & Garden > Kitchen & Dining > Tableware > Serveware Accessories > Punch Bowl Stands Home & Garden > Kitchen & Dining > Tableware > Serveware Accessories > Tureen Lids Home & Garden > Kitchen & Dining > Tableware > Serveware Accessories > Tureen Stands +Home & Garden > Kitchen & Dining > Tableware > Tablecloth Clips & Weights Home & Garden > Kitchen & Dining > Tableware > Trivets Home & Garden > Lawn & Garden Home & Garden > Lawn & Garden > Gardening @@ -4327,46 +3721,46 @@ Home & Garden > Lawn & Garden > Gardening > Composting > Compost Aerators Home & Garden > Lawn & Garden > Gardening > Composting > Composters Home & Garden > Lawn & Garden > Gardening > Disease Control Home & Garden > Lawn & Garden > Gardening > Fertilizers +Home & Garden > Lawn & Garden > Gardening > Garden Pot Saucers & Trays Home & Garden > Lawn & Garden > Gardening > Gardening Accessories -Home & Garden > Lawn & Garden > Gardening > Gardening Accessories > Gardening Kneeling Cushions +Home & Garden > Lawn & Garden > Gardening > Gardening Accessories > Gardening Scooters, Seats & Kneelers Home & Garden > Lawn & Garden > Gardening > Gardening Accessories > Gardening Totes Home & Garden > Lawn & Garden > Gardening > Gardening Accessories > Potting Benches +Home & Garden > Lawn & Garden > Gardening > Gardening Tool Accessories +Home & Garden > Lawn & Garden > Gardening > Gardening Tool Accessories > Gardening Tool Handles +Home & Garden > Lawn & Garden > Gardening > Gardening Tool Accessories > Gardening Tool Heads +Home & Garden > Lawn & Garden > Gardening > Gardening Tool Accessories > Wheelbarrow Parts Home & Garden > Lawn & Garden > Gardening > Gardening Tools -Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Brush Axes Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Bulb Planting Tools Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Cultivating Tools -Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Cultivating Tools > Garden Hoes -Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Digging Bars Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Gardening Forks -Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Gardening Sickles +Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Gardening Sickles & Machetes Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Gardening Trowels -Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Grass Edgers Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Lawn & Garden Sprayers -Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Lawn Aerators -Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Machetes -Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Mattocks & Pickaxes +Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Lawn Rollers Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Pruning Saws Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Pruning Shears Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Rakes Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Shovels & Spades Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Spreaders -Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Wheelbarrow Parts Home & Garden > Lawn & Garden > Gardening > Gardening Tools > Wheelbarrows Home & Garden > Lawn & Garden > Gardening > Greenhouses Home & Garden > Lawn & Garden > Gardening > Herbicides Home & Garden > Lawn & Garden > Gardening > Landscape Fabric -Home & Garden > Lawn & Garden > Gardening > Lanscape Fabric Accessories -Home & Garden > Lawn & Garden > Gardening > Lanscape Fabric Accessories > Landscape Fabric Staples & Pins -Home & Garden > Lawn & Garden > Gardening > Lanscape Fabric Accessories > Landscape Fabric Tape +Home & Garden > Lawn & Garden > Gardening > Landscape Fabric Accessories +Home & Garden > Lawn & Garden > Gardening > Landscape Fabric Accessories > Landscape Fabric Staples & Pins +Home & Garden > Lawn & Garden > Gardening > Landscape Fabric Accessories > Landscape Fabric Tape Home & Garden > Lawn & Garden > Gardening > Mulch -Home & Garden > Lawn & Garden > Gardening > Plant Cages -Home & Garden > Lawn & Garden > Gardening > Plant Stakes & Supports +Home & Garden > Lawn & Garden > Gardening > Plant Cages & Supports Home & Garden > Lawn & Garden > Gardening > Plant Stands +Home & Garden > Lawn & Garden > Gardening > Pot & Planter Liners Home & Garden > Lawn & Garden > Gardening > Pots & Planters Home & Garden > Lawn & Garden > Gardening > Rain Barrels Home & Garden > Lawn & Garden > Gardening > Sands & Soils Home & Garden > Lawn & Garden > Outdoor Living -Home & Garden > Lawn & Garden > Outdoor Living > Hammock Accessories +Home & Garden > Lawn & Garden > Outdoor Living > Awning Accessories +Home & Garden > Lawn & Garden > Outdoor Living > Awnings +Home & Garden > Lawn & Garden > Outdoor Living > Hammock Parts & Accessories Home & Garden > Lawn & Garden > Outdoor Living > Hammocks Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Blankets Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Blankets > Beach Mats @@ -4382,17 +3776,21 @@ Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Structures > Canopy & G Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Structures > Canopy & Gazebo Accessories > Canopy Weights Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Structures > Garden Arches, Trellises, Arbors & Pergolas Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Structures > Garden Bridges -Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Structures > Sheds & Storage +Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Structures > Sheds, Garages & Carports Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Umbrella & Sunshade Accessories Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Umbrella & Sunshade Accessories > Outdoor Umbrella & Sunshade Fabric Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Umbrella & Sunshade Accessories > Outdoor Umbrella Bases Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Umbrella & Sunshade Accessories > Outdoor Umbrella Covers +Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Umbrella & Sunshade Accessories > Outdoor Umbrella Enclosure Kits Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Umbrella & Sunshade Accessories > Outdoor Umbrella Lights Home & Garden > Lawn & Garden > Outdoor Living > Outdoor Umbrellas & Sunshades +Home & Garden > Lawn & Garden > Outdoor Living > Porch Swing Accessories Home & Garden > Lawn & Garden > Outdoor Living > Porch Swings Home & Garden > Lawn & Garden > Outdoor Power Equipment Home & Garden > Lawn & Garden > Outdoor Power Equipment > Chainsaws +Home & Garden > Lawn & Garden > Outdoor Power Equipment > Grass Edgers Home & Garden > Lawn & Garden > Outdoor Power Equipment > Hedge Trimmers +Home & Garden > Lawn & Garden > Outdoor Power Equipment > Lawn Aerators & Dethatchers Home & Garden > Lawn & Garden > Outdoor Power Equipment > Lawn Mowers Home & Garden > Lawn & Garden > Outdoor Power Equipment > Lawn Mowers > Riding Mowers Home & Garden > Lawn & Garden > Outdoor Power Equipment > Lawn Mowers > Robotic Mowers @@ -4400,8 +3798,9 @@ Home & Garden > Lawn & Garden > Outdoor Power Equipment > Lawn Mowers > Tow-Behi Home & Garden > Lawn & Garden > Outdoor Power Equipment > Lawn Mowers > Walk-Behind Mowers Home & Garden > Lawn & Garden > Outdoor Power Equipment > Lawn Vacuums Home & Garden > Lawn & Garden > Outdoor Power Equipment > Leaf Blowers -Home & Garden > Lawn & Garden > Outdoor Power Equipment > Outdoor Power Equipment Heads +Home & Garden > Lawn & Garden > Outdoor Power Equipment > Outdoor Power Equipment Base Units Home & Garden > Lawn & Garden > Outdoor Power Equipment > Outdoor Power Equipment Sets +Home & Garden > Lawn & Garden > Outdoor Power Equipment > Power Sweepers Home & Garden > Lawn & Garden > Outdoor Power Equipment > Power Tillers & Cultivators Home & Garden > Lawn & Garden > Outdoor Power Equipment > Pressure Washers Home & Garden > Lawn & Garden > Outdoor Power Equipment > Snow Blowers @@ -4412,35 +3811,41 @@ Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Chainsaw A Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Chainsaw Accessories > Chainsaw Bars Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Chainsaw Accessories > Chainsaw Chains Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Grass Edger Accessories -Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Grass Edger Accessories > Grass Edger Attachments Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Hedge Trimmer Accessories -Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Hedge Trimmer Accessories > Hedge Trimmer Attachments Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Brush Mower Attachments -Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Mower Attachments Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Mower Bags Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Mower Belts Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Mower Blades Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Mower Covers +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Mower Mulch Kits +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Mower Mulch Plugs & Plates Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Mower Pulleys & Idlers +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Mower Tire Tubes Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Mower Tires Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Mower Wheels +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Striping Kits Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Lawn Mower Accessories > Lawn Sweepers Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Leaf Blower Accessories -Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Leaf Blower Accessories > Leaf Blower Extension Tubes +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Leaf Blower Accessories > Leaf Blower Tubes +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Multifunction Outdoor Power Equipment Attachments +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Multifunction Outdoor Power Equipment Attachments > Grass Edger Attachments +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Multifunction Outdoor Power Equipment Attachments > Ground & Leaf Blower Attachments +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Multifunction Outdoor Power Equipment Attachments > Hedge Trimmer Attachments +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Multifunction Outdoor Power Equipment Attachments > Pole Saw Attachments +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Multifunction Outdoor Power Equipment Attachments > Tiller & Cultivator Attachments +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Multifunction Outdoor Power Equipment Attachments > Weed Trimmer Attachments Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Outdoor Power Equipment Batteries -Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Outdoor Power Equipment Batteries > Hedge Trimmer Batteries -Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Outdoor Power Equipment Batteries > Lawn Mower Batteries -Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Outdoor Power Equipment Batteries > Weed Trimmer Batteries Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Pressure Washer Accessories Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Snow Blower Accessories Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Tractor Parts & Accessories +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Tractor Parts & Accessories > Tractor Tires +Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Tractor Parts & Accessories > Tractor Wheels Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Weed Trimmer Accessories -Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Weed Trimmer Accessories > Weed Trimmer Attachments Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Weed Trimmer Accessories > Weed Trimmer Blades & Spools Home & Garden > Lawn & Garden > Outdoor Power Equipment Accessories > Weed Trimmer Accessories > Weed Trimmer Spool Covers Home & Garden > Lawn & Garden > Snow Removal -Home & Garden > Lawn & Garden > Snow Removal > Ice Scrapers +Home & Garden > Lawn & Garden > Snow Removal > Ice Scrapers & Snow Brushes Home & Garden > Lawn & Garden > Snow Removal > Snow Shovels Home & Garden > Lawn & Garden > Watering & Irrigation Home & Garden > Lawn & Garden > Watering & Irrigation > Garden Hose Fittings & Valves @@ -4450,20 +3855,16 @@ Home & Garden > Lawn & Garden > Watering & Irrigation > Sprinkler Accessories Home & Garden > Lawn & Garden > Watering & Irrigation > Sprinkler Accessories > Sprinkler Controls Home & Garden > Lawn & Garden > Watering & Irrigation > Sprinkler Accessories > Sprinkler Valves Home & Garden > Lawn & Garden > Watering & Irrigation > Sprinklers & Sprinkler Heads -Home & Garden > Lawn & Garden > Watering & Irrigation > Sprinklers & Sprinkler Heads > Sprinkler Heads -Home & Garden > Lawn & Garden > Watering & Irrigation > Sprinklers & Sprinkler Heads > Sprinklers +Home & Garden > Lawn & Garden > Watering & Irrigation > Watering Can Accesssories Home & Garden > Lawn & Garden > Watering & Irrigation > Watering Cans +Home & Garden > Lawn & Garden > Watering & Irrigation > Watering Globes & Spikes Home & Garden > Lighting Home & Garden > Lighting > Emergency Lighting +Home & Garden > Lighting > Floating & Submersible Lights Home & Garden > Lighting > Flood & Spot Lights +Home & Garden > Lighting > In-Ground Lights Home & Garden > Lighting > Lamps -Home & Garden > Lighting > Lamps > Floor Lamps -Home & Garden > Lighting > Lamps > Table Lamps -Home & Garden > Lighting > Landscape Lighting -Home & Garden > Lighting > Landscape Lighting > Deck Lights -Home & Garden > Lighting > Landscape Lighting > Landscape Lanterns & Torches -Home & Garden > Lighting > Landscape Lighting > Landscape Pathway Lighting -Home & Garden > Lighting > Landscape Lighting > Landscape Well Lights +Home & Garden > Lighting > Landscape Pathway Lighting Home & Garden > Lighting > Light Bulbs Home & Garden > Lighting > Light Bulbs > Compact Fluorescent Lamps Home & Garden > Lighting > Light Bulbs > Fluorescent Tubes @@ -4471,13 +3872,13 @@ Home & Garden > Lighting > Light Bulbs > Incandescent Light Bulbs Home & Garden > Lighting > Light Bulbs > LED Light Bulbs Home & Garden > Lighting > Light Ropes & Strings Home & Garden > Lighting > Lighting Fixtures +Home & Garden > Lighting > Lighting Fixtures > Cabinet Light Fixtures Home & Garden > Lighting > Lighting Fixtures > Ceiling Light Fixtures Home & Garden > Lighting > Lighting Fixtures > Chandeliers -Home & Garden > Lighting > Lighting Fixtures > Under Cabinet Light Fixtures Home & Garden > Lighting > Lighting Fixtures > Wall Light Fixtures -Home & Garden > Lighting > Lighting Fixtures > Wall Light Fixtures > Wall Sconces -Home & Garden > Lighting > Night Lights +Home & Garden > Lighting > Night Lights & Ambient Lighting Home & Garden > Lighting > Picture Lights +Home & Garden > Lighting > Tiki Torches & Oil Lamps Home & Garden > Lighting > Track Lighting Home & Garden > Lighting > Track Lighting > Track Lighting Accessories Home & Garden > Lighting > Track Lighting > Track Lighting Fixtures @@ -4487,14 +3888,13 @@ Home & Garden > Lighting Accessories > Lamp Post Bases Home & Garden > Lighting Accessories > Lamp Post Mounts Home & Garden > Lighting Accessories > Lamp Shades Home & Garden > Lighting Accessories > Lighting Timers +Home & Garden > Lighting Accessories > Oil Lamp Fuel Home & Garden > Linens & Bedding Home & Garden > Linens & Bedding > Bedding +Home & Garden > Linens & Bedding > Bedding > Bed Canopies Home & Garden > Linens & Bedding > Bedding > Bed Sheets Home & Garden > Linens & Bedding > Bedding > Bedskirts Home & Garden > Linens & Bedding > Bedding > Blankets -Home & Garden > Linens & Bedding > Bedding > Blankets > Electric Blankets -Home & Garden > Linens & Bedding > Bedding > Blankets > Throw Blankets -Home & Garden > Linens & Bedding > Bedding > Comforters & Comforter Sets Home & Garden > Linens & Bedding > Bedding > Duvet Covers Home & Garden > Linens & Bedding > Bedding > Mattress Protectors Home & Garden > Linens & Bedding > Bedding > Mattress Protectors > Mattress Encasements @@ -4502,10 +3902,8 @@ Home & Garden > Linens & Bedding > Bedding > Mattress Protectors > Mattress Pads Home & Garden > Linens & Bedding > Bedding > Nap Mats Home & Garden > Linens & Bedding > Bedding > Pillowcases & Shams Home & Garden > Linens & Bedding > Bedding > Pillows -Home & Garden > Linens & Bedding > Bedding > Pillows > Body Pillows -Home & Garden > Linens & Bedding > Bedding > Quilts & Quilt Sets -Home & Garden > Linens & Bedding > Table Linen Accessories -Home & Garden > Linens & Bedding > Table Linen Accessories > Tablecloth Clips & Weights +Home & Garden > Linens & Bedding > Bedding > Quilts & Comforters +Home & Garden > Linens & Bedding > Kitchen Linens Sets Home & Garden > Linens & Bedding > Table Linens Home & Garden > Linens & Bedding > Table Linens > Cloth Napkins Home & Garden > Linens & Bedding > Table Linens > Doilies @@ -4522,24 +3920,25 @@ Home & Garden > Plants Home & Garden > Plants > Aquatic Plants Home & Garden > Plants > Flowers Home & Garden > Plants > Indoor & Outdoor Plants -Home & Garden > Plants > Indoor & Outdoor Plants > Indoor Plants -Home & Garden > Plants > Indoor & Outdoor Plants > Outdoor Plants +Home & Garden > Plants > Plant & Herb Growing Kits Home & Garden > Plants > Seeds Home & Garden > Plants > Trees Home & Garden > Pool & Spa Home & Garden > Pool & Spa > Pool & Spa Accessories -Home & Garden > Pool & Spa > Pool & Spa Accessories > Child Swimming Aids Home & Garden > Pool & Spa > Pool & Spa Accessories > Diving Boards +Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool & Spa Chlorine Generators Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool & Spa Filters -Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Brushes +Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool & Spa Maintenance Kits +Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Brushes & Brooms Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Cleaner Hoses Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Cleaners & Chemicals -Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Covers +Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Cover Accessories +Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Covers & Ground Cloths Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Deck Kits Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Floats & Loungers -Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Floats & Loungers > Baby Floats Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Heaters -Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Ladders +Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Ladders, Steps & Ramps +Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Liners Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Skimmers Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Sweeps & Vacuums Home & Garden > Pool & Spa > Pool & Spa Accessories > Pool Toys @@ -4552,28 +3951,24 @@ Home & Garden > Smoking Accessories > Ashtrays Home & Garden > Smoking Accessories > Cigar Cases Home & Garden > Smoking Accessories > Cigar Cutters & Punches Home & Garden > Smoking Accessories > Cigarette Cases +Home & Garden > Smoking Accessories > Cigarette Holders Home & Garden > Smoking Accessories > Humidor Accessories Home & Garden > Smoking Accessories > Humidors -Home & Garden > Smoking Accessories > Lighters -Home & Garden > Smoking Accessories > Matches Home & Garden > Umbrella Sleeves & Cases Home & Garden > Wood Stoves Luggage & Bags -Luggage & Bags > Backpack Accessories -Luggage & Bags > Backpack Accessories > Backpack Covers Luggage & Bags > Backpacks -Luggage & Bags > Business Bags -Luggage & Bags > Business Bags > Briefcases -Luggage & Bags > Business Bags > Electronics Bags & Cases -Luggage & Bags > Business Bags > Electronics Bags & Cases > Computer Skins & Sleeves -Luggage & Bags > Business Bags > Electronics Bags & Cases > Laptop Bags & Cases -Luggage & Bags > Business Bags > Electronics Bags & Cases > Tablet Computer Bags & Cases -Luggage & Bags > Business Bags > Garment Bags +Luggage & Bags > Briefcases Luggage & Bags > Cosmetic & Toiletry Bags Luggage & Bags > Diaper Bags +Luggage & Bags > Dry Boxes Luggage & Bags > Duffel Bags Luggage & Bags > Fanny Packs +Luggage & Bags > Garment Bags Luggage & Bags > Luggage Accessories +Luggage & Bags > Luggage Accessories > Dry Box Liners & Inserts +Luggage & Bags > Luggage Accessories > Luggage Covers +Luggage & Bags > Luggage Accessories > Luggage Racks & Stands Luggage & Bags > Luggage Accessories > Luggage Straps Luggage & Bags > Luggage Accessories > Luggage Tags Luggage & Bags > Luggage Accessories > Packing Organizers @@ -4582,12 +3977,10 @@ Luggage & Bags > Luggage Accessories > Travel Pouches Luggage & Bags > Messenger Bags Luggage & Bags > Shopping Totes Luggage & Bags > Suitcases -Luggage & Bags > Suitcases > Carry-On Luggage Luggage & Bags > Train Cases Mature Mature > Erotic Mature > Erotic > Erotic Books -Mature > Erotic > Erotic Books > Erotic Comics Mature > Erotic > Erotic Clothing Mature > Erotic > Erotic DVDs & Videos Mature > Erotic > Erotic Food & Edibles @@ -4597,17 +3990,23 @@ Mature > Erotic > Pole Dancing Kits Mature > Erotic > Sex Toys Mature > Weapons Mature > Weapons > Brass Knuckles -Mature > Weapons > Clubs +Mature > Weapons > Clubs & Batons Mature > Weapons > Combat Knives -Mature > Weapons > Gun Accessories -Mature > Weapons > Gun Accessories > Ammunition -Mature > Weapons > Gun Accessories > Ammunition Belts -Mature > Weapons > Gun Accessories > Ammunition Boxes -Mature > Weapons > Gun Accessories > Gun Bags & Cases -Mature > Weapons > Gun Accessories > Gun Grips -Mature > Weapons > Gun Accessories > Gun Holsters -Mature > Weapons > Gun Accessories > Gun Lights -Mature > Weapons > Gun Accessories > Gun Rails +Mature > Weapons > Gun Care & Accessories +Mature > Weapons > Gun Care & Accessories > Ammunition +Mature > Weapons > Gun Care & Accessories > Ammunition Cases & Holders +Mature > Weapons > Gun Care & Accessories > Gun Cases & Range Bags +Mature > Weapons > Gun Care & Accessories > Gun Cleaning +Mature > Weapons > Gun Care & Accessories > Gun Cleaning > Gun Cleaning Cloths & Swabs +Mature > Weapons > Gun Care & Accessories > Gun Cleaning > Gun Cleaning Patches +Mature > Weapons > Gun Care & Accessories > Gun Cleaning > Gun Cleaning Solvents +Mature > Weapons > Gun Care & Accessories > Gun Grips +Mature > Weapons > Gun Care & Accessories > Gun Holsters +Mature > Weapons > Gun Care & Accessories > Gun Lights +Mature > Weapons > Gun Care & Accessories > Gun Rails +Mature > Weapons > Gun Care & Accessories > Gun Slings +Mature > Weapons > Gun Care & Accessories > Reloading Supplies & Equipment +Mature > Weapons > Gun Care & Accessories > Reloading Supplies & Equipment > Ammunition Reloading Presses Mature > Weapons > Guns Mature > Weapons > Mace & Pepper Spray Mature > Weapons > Nunchucks @@ -4619,71 +4018,19 @@ Mature > Weapons > Throwing Stars Mature > Weapons > Whips Media Media > Books -Media > Books > Fiction -Media > Books > Fiction > Children's Books -Media > Books > Fiction > Comics -Media > Books > Fiction > Horror Novels -Media > Books > Fiction > Literature -Media > Books > Fiction > Mystery Novels -Media > Books > Fiction > Romance Novels -Media > Books > Fiction > Science Fiction & Fantasy Novels -Media > Books > Non-Fiction -Media > Books > Non-Fiction > Animal & Nature Books -Media > Books > Non-Fiction > Architecture Books -Media > Books > Non-Fiction > Art Books -Media > Books > Non-Fiction > Biographies -Media > Books > Non-Fiction > Body, Mind & Spirit Books -Media > Books > Non-Fiction > Business & Economics Books -Media > Books > Non-Fiction > Coloring Books -Media > Books > Non-Fiction > Computer Books -Media > Books > Non-Fiction > Cooking Books -Media > Books > Non-Fiction > Craft, Hobby & Activity Books -Media > Books > Non-Fiction > Drama Books -Media > Books > Non-Fiction > Education Books -Media > Books > Non-Fiction > Education Books > Foreign Language Books -Media > Books > Non-Fiction > Education Books > Study Aids -Media > Books > Non-Fiction > Family Relationship Books -Media > Books > Non-Fiction > Game Books -Media > Books > Non-Fiction > Gardening Books -Media > Books > Non-Fiction > Health & Fitness Books -Media > Books > Non-Fiction > History Books -Media > Books > Non-Fiction > Home Improvement Books -Media > Books > Non-Fiction > Humor Books -Media > Books > Non-Fiction > Juvenile Non-Fiction -Media > Books > Non-Fiction > Language Arts Books -Media > Books > Non-Fiction > Law Books -Media > Books > Non-Fiction > Literary Books -Media > Books > Non-Fiction > Math Books -Media > Books > Non-Fiction > Medical Books -Media > Books > Non-Fiction > Music Books -Media > Books > Non-Fiction > Performing Arts Books -Media > Books > Non-Fiction > Pet Books -Media > Books > Non-Fiction > Philosophy Books -Media > Books > Non-Fiction > Photography Books -Media > Books > Non-Fiction > Poetry -Media > Books > Non-Fiction > Political Science Books -Media > Books > Non-Fiction > Psychology Books -Media > Books > Non-Fiction > Puzzle Books -Media > Books > Non-Fiction > Reference Books -Media > Books > Non-Fiction > Religion Books -Media > Books > Non-Fiction > Science Books -Media > Books > Non-Fiction > Self Help Books -Media > Books > Non-Fiction > Social Science Books -Media > Books > Non-Fiction > Sports Books -Media > Books > Non-Fiction > Technology Books -Media > Books > Non-Fiction > Transportation Books -Media > Books > Non-Fiction > Travel Books -Media > Books > Non-Fiction > True Crime Books +Media > Carpentry & Woodworking Project Plans Media > DVDs & Videos Media > Magazines & Newspapers -Media > Music +Media > Music & Sound Recordings Media > Product Manuals +Media > Product Manuals > Camera & Optics Manuals Media > Product Manuals > Electronics Manuals +Media > Product Manuals > Exercise & Fitness Equipment Manuals Media > Product Manuals > Household Appliance Manuals Media > Product Manuals > Kitchen Appliance Manuals Media > Product Manuals > Model & Toys Manuals +Media > Product Manuals > Office Supply Manuals Media > Product Manuals > Power Tool & Equipment Manuals -Media > Product Manuals > Sports & Recreation Manuals Media > Product Manuals > Vehicle Service Manuals Media > Sheet Music Office Supplies @@ -4692,6 +4039,7 @@ Office Supplies > Book Accessories > Book Covers Office Supplies > Book Accessories > Book Lights Office Supplies > Book Accessories > Book Stands & Rests Office Supplies > Book Accessories > Bookmarks +Office Supplies > Desk Pads & Blotters Office Supplies > Filing & Organization Office Supplies > Filing & Organization > Address Books Office Supplies > Filing & Organization > Binding Supplies @@ -4704,64 +4052,55 @@ Office Supplies > Filing & Organization > Binding Supplies > Binding Combs & Spi Office Supplies > Filing & Organization > Binding Supplies > Binding Machines Office Supplies > Filing & Organization > Business Card Books Office Supplies > Filing & Organization > Business Card Stands -Office Supplies > Filing & Organization > CD/DVD Organizers -Office Supplies > Filing & Organization > CD/DVD Organizers > CD/DVD Pouches & Visors -Office Supplies > Filing & Organization > CD/DVD Organizers > CD/DVD Sleeves -Office Supplies > Filing & Organization > CD/DVD Organizers > Jewel Cases +Office Supplies > Filing & Organization > CD/DVD Cases & Organizers Office Supplies > Filing & Organization > Calendars, Organizers & Planners Office Supplies > Filing & Organization > Card Files Office Supplies > Filing & Organization > Card Sleeves Office Supplies > Filing & Organization > Cash Boxes Office Supplies > Filing & Organization > Desk Organizers -Office Supplies > Filing & Organization > Desk Organizers > Desk Trays Office Supplies > Filing & Organization > File Boxes Office Supplies > Filing & Organization > File Folders Office Supplies > Filing & Organization > Folders & Report Covers Office Supplies > Filing & Organization > Greeting Card Organizers -Office Supplies > Filing & Organization > Laminating -Office Supplies > Filing & Organization > Laminating > Laminating Film, Pouches & Sheets -Office Supplies > Filing & Organization > Laminating > Laminators Office Supplies > Filing & Organization > Mail Sorters Office Supplies > Filing & Organization > Pen & Pencil Cases Office Supplies > Filing & Organization > Portfolios & Padfolios Office Supplies > Filing & Organization > Recipe Card Boxes -Office Supplies > Filing & Organization > Storage Lockers & Cabinets -Office Supplies > General Supplies -Office Supplies > General Supplies > Adhesives -Office Supplies > General Supplies > Adhesives > Office Tape -Office Supplies > General Supplies > Brass Fasteners -Office Supplies > General Supplies > Correction -Office Supplies > General Supplies > Correction > Correction Fluids, Pens & Tapes -Office Supplies > General Supplies > Correction > Erasers -Office Supplies > General Supplies > Labels & Tags -Office Supplies > General Supplies > Labels & Tags > Address Labels -Office Supplies > General Supplies > Labels & Tags > Folder Tabs -Office Supplies > General Supplies > Labels & Tags > Label Clips -Office Supplies > General Supplies > Labels & Tags > Label Maker Tape -Office Supplies > General Supplies > Labels & Tags > Shipping Labels -Office Supplies > General Supplies > Labels & Tags > Shipping Tags -Office Supplies > General Supplies > Paper Clips & Clamps -Office Supplies > General Supplies > Paper Products -Office Supplies > General Supplies > Paper Products > Binder Paper -Office Supplies > General Supplies > Paper Products > Blank ID Cards -Office Supplies > General Supplies > Paper Products > Business Cards -Office Supplies > General Supplies > Paper Products > Business Forms & Receipts -Office Supplies > General Supplies > Paper Products > Checks -Office Supplies > General Supplies > Paper Products > Cover Paper -Office Supplies > General Supplies > Paper Products > Envelopes -Office Supplies > General Supplies > Paper Products > Index Cards -Office Supplies > General Supplies > Paper Products > Notebooks & Notepads -Office Supplies > General Supplies > Paper Products > Post Cards -Office Supplies > General Supplies > Paper Products > Printer & Copier Paper -Office Supplies > General Supplies > Paper Products > Printer & Copier Paper > Photo Printer Paper -Office Supplies > General Supplies > Paper Products > Receipt Paper Rolls -Office Supplies > General Supplies > Paper Products > Stationery -Office Supplies > General Supplies > Paper Products > Sticky Notes -Office Supplies > General Supplies > Rubber Bands -Office Supplies > General Supplies > Stamp Pads & Inks -Office Supplies > General Supplies > Staples -Office Supplies > General Supplies > Tacks & Pushpins +Office Supplies > General Office Supplies +Office Supplies > General Office Supplies > Brass Fasteners +Office Supplies > General Office Supplies > Correction Fluids, Pens & Tapes +Office Supplies > General Office Supplies > Erasers +Office Supplies > General Office Supplies > Labels & Tags +Office Supplies > General Office Supplies > Labels & Tags > Address Labels +Office Supplies > General Office Supplies > Labels & Tags > Folder Tabs +Office Supplies > General Office Supplies > Labels & Tags > Label Clips +Office Supplies > General Office Supplies > Labels & Tags > Label Tapes & Refill Rolls +Office Supplies > General Office Supplies > Labels & Tags > Shipping Labels +Office Supplies > General Office Supplies > Labels & Tags > Shipping Tags +Office Supplies > General Office Supplies > Laminating Film, Pouches & Sheets +Office Supplies > General Office Supplies > Mounting Putty +Office Supplies > General Office Supplies > Office Tape +Office Supplies > General Office Supplies > Paper Clips & Clamps +Office Supplies > General Office Supplies > Paper Products +Office Supplies > General Office Supplies > Paper Products > Binder Paper +Office Supplies > General Office Supplies > Paper Products > Blank ID Cards +Office Supplies > General Office Supplies > Paper Products > Business Cards +Office Supplies > General Office Supplies > Paper Products > Business Forms & Receipts +Office Supplies > General Office Supplies > Paper Products > Checks +Office Supplies > General Office Supplies > Paper Products > Cover Paper +Office Supplies > General Office Supplies > Paper Products > Envelopes +Office Supplies > General Office Supplies > Paper Products > Index Cards +Office Supplies > General Office Supplies > Paper Products > Notebooks & Notepads +Office Supplies > General Office Supplies > Paper Products > Post Cards +Office Supplies > General Office Supplies > Paper Products > Printer & Copier Paper +Office Supplies > General Office Supplies > Paper Products > Receipt & Adding Machine Paper Rolls +Office Supplies > General Office Supplies > Paper Products > Stationery +Office Supplies > General Office Supplies > Paper Products > Sticky Notes +Office Supplies > General Office Supplies > Rubber Bands +Office Supplies > General Office Supplies > Staples +Office Supplies > General Office Supplies > Tacks & Pushpins Office Supplies > Impulse Sealers +Office Supplies > Lap Desks Office Supplies > Name Plates Office Supplies > Office & Chair Mats Office Supplies > Office & Chair Mats > Anti-Fatigue Mats @@ -4774,9 +4113,11 @@ Office Supplies > Office Carts > File Carts Office Supplies > Office Carts > Mail Carts Office Supplies > Office Carts > Utility Carts Office Supplies > Office Equipment +Office Supplies > Office Equipment > Calculator Accessories Office Supplies > Office Equipment > Calculators Office Supplies > Office Equipment > Electronic Dictionaries & Translators Office Supplies > Office Equipment > Label Makers +Office Supplies > Office Equipment > Laminators Office Supplies > Office Equipment > Office Shredders Office Supplies > Office Equipment > Postage Meters Office Supplies > Office Equipment > Time & Attendance Clocks @@ -4785,43 +4126,33 @@ Office Supplies > Office Equipment > Typewriters Office Supplies > Office Instruments Office Supplies > Office Instruments > Call Bells Office Supplies > Office Instruments > Clipboards +Office Supplies > Office Instruments > Letter Openers Office Supplies > Office Instruments > Magnifiers -Office Supplies > Office Instruments > Office Rulers +Office Supplies > Office Instruments > Office Rubber Stamps Office Supplies > Office Instruments > Pencil Sharpeners -Office Supplies > Office Instruments > Rubber Stamps -Office Supplies > Office Instruments > Rubber Stamps > Decorative Rubber Stamps -Office Supplies > Office Instruments > Rubber Stamps > Office Rubber Stamps -Office Supplies > Office Instruments > Scissors Office Supplies > Office Instruments > Staple Removers Office Supplies > Office Instruments > Staplers +Office Supplies > Office Instruments > Tape Dispensers Office Supplies > Office Instruments > Writing & Drawing Instrument Accessories Office Supplies > Office Instruments > Writing & Drawing Instrument Accessories > Marker & Highlighter Ink Refills Office Supplies > Office Instruments > Writing & Drawing Instrument Accessories > Pen Ink & Refills -Office Supplies > Office Instruments > Writing & Drawing Instrument Accessories > Pen Replacement Tips Office Supplies > Office Instruments > Writing & Drawing Instrument Accessories > Pencil Lead & Refills Office Supplies > Office Instruments > Writing & Drawing Instruments Office Supplies > Office Instruments > Writing & Drawing Instruments > Art Charcoals Office Supplies > Office Instruments > Writing & Drawing Instruments > Chalk Office Supplies > Office Instruments > Writing & Drawing Instruments > Crayons Office Supplies > Office Instruments > Writing & Drawing Instruments > Markers & Highlighters +Office Supplies > Office Instruments > Writing & Drawing Instruments > Multifunction Writing Instruments Office Supplies > Office Instruments > Writing & Drawing Instruments > Pastels Office Supplies > Office Instruments > Writing & Drawing Instruments > Pens & Pencils -Office Supplies > Office Instruments > Writing & Drawing Instruments > Pens & Pencils > Multifunction Writing Instruments Office Supplies > Office Instruments > Writing & Drawing Instruments > Pens & Pencils > Pen & Pencil Sets Office Supplies > Office Instruments > Writing & Drawing Instruments > Pens & Pencils > Pencils Office Supplies > Office Instruments > Writing & Drawing Instruments > Pens & Pencils > Pencils > Art Pencils Office Supplies > Office Instruments > Writing & Drawing Instruments > Pens & Pencils > Pencils > Writing Pencils Office Supplies > Office Instruments > Writing & Drawing Instruments > Pens & Pencils > Pens Office Supplies > Paper Handling -Office Supplies > Paper Handling > Craft Knives -Office Supplies > Paper Handling > Cutting Mats Office Supplies > Paper Handling > Fingertip Grips Office Supplies > Paper Handling > Hole Punches -Office Supplies > Paper Handling > Hole Punches > Decorative Hole Punches -Office Supplies > Paper Handling > Hole Punches > Office Hole Punches -Office Supplies > Paper Handling > Paper Cutter Accessories -Office Supplies > Paper Handling > Paper Cutter Accessories > Paper Cutter Stands -Office Supplies > Paper Handling > Paper Cutters Office Supplies > Paper Handling > Paper Folding Machines Office Supplies > Paper Handling > Paper Joggers Office Supplies > Paper Handling > Paperweights @@ -4836,36 +4167,40 @@ Office Supplies > Presentation Supplies > Display Boards > Foam Boards Office Supplies > Presentation Supplies > Display Boards > Mounting Boards Office Supplies > Presentation Supplies > Display Boards > Poster Boards Office Supplies > Presentation Supplies > Document Cameras +Office Supplies > Presentation Supplies > Dry-Erase Boards Office Supplies > Presentation Supplies > Easel Pads Office Supplies > Presentation Supplies > Easels Office Supplies > Presentation Supplies > Laser Pointers Office Supplies > Presentation Supplies > Lecterns Office Supplies > Presentation Supplies > Transparencies -Office Supplies > Presentation Supplies > Whiteboards Office Supplies > Presentation Supplies > Wireless Presenters Office Supplies > Shipping Supplies +Office Supplies > Shipping Supplies > Moving & Shipping Boxes Office Supplies > Shipping Supplies > Packing Materials Office Supplies > Shipping Supplies > Packing Tape -Office Supplies > Shipping Supplies > Postal Scales -Office Supplies > Shipping Supplies > Shipping Boxes Religious & Ceremonial Religious & Ceremonial > Memorial Ceremony Supplies Religious & Ceremonial > Memorial Ceremony Supplies > Memorial Urns Religious & Ceremonial > Religious Items Religious & Ceremonial > Religious Items > Prayer Beads +Religious & Ceremonial > Religious Items > Prayer Cards Religious & Ceremonial > Religious Items > Religious Altars Religious & Ceremonial > Religious Items > Religious Veils +Religious & Ceremonial > Religious Items > Tarot Cards Religious & Ceremonial > Wedding Ceremony Supplies +Religious & Ceremonial > Wedding Ceremony Supplies > Aisle Runners Religious & Ceremonial > Wedding Ceremony Supplies > Flower Girl Baskets -Religious & Ceremonial > Wedding Ceremony Supplies > Ring Pillows +Religious & Ceremonial > Wedding Ceremony Supplies > Ring Pillows & Holders Software Software > Computer Software Software > Computer Software > Antivirus & Security Software Software > Computer Software > Business & Productivity Software Software > Computer Software > Compilers & Programming Tools -Software > Computer Software > Computer Utilities & Maintenance +Software > Computer Software > Computer Utilities & Maintenance Software +Software > Computer Software > Dictionary & Translation Software Software > Computer Software > Educational Software Software > Computer Software > Financial, Tax & Accounting Software +Software > Computer Software > GPS Map Data & Software Software > Computer Software > Handheld & PDA Software Software > Computer Software > Multimedia & Design Software Software > Computer Software > Multimedia & Design Software > 3D Modeling Software @@ -4881,147 +4216,324 @@ Software > Computer Software > Multimedia & Design Software > Web Design Softwar Software > Computer Software > Network Software Software > Computer Software > Office Application Software Software > Computer Software > Operating Systems -Software > Computer Software > Reference Software -Software > Computer Software > Reference Software > Dictionary & Translation Software -Software > Computer Software > Reference Software > GPS Map Data & Software Software > Computer Software > Restore Disks Software > Digital Goods & Currency -Software > Digital Goods & Currency > Clip Art Software > Digital Goods & Currency > Computer Icons Software > Digital Goods & Currency > Desktop Wallpaper +Software > Digital Goods & Currency > Digital Artwork Software > Digital Goods & Currency > Document Templates Software > Digital Goods & Currency > Fonts +Software > Digital Goods & Currency > Stock Photographs & Video Footage Software > Digital Goods & Currency > Virtual Currency Software > Video Game Software -Software > Video Game Software > All Game Boy Games -Software > Video Game Software > All Game Boy Games > Game Boy Advance Games -Software > Video Game Software > All Game Boy Games > Game Boy Games -Software > Video Game Software > All PlayStation Games -Software > Video Game Software > All PlayStation Games > PlayStation (original) Games -Software > Video Game Software > All PlayStation Games > PlayStation 2 Games -Software > Video Game Software > All PlayStation Games > PlayStation 3 Games -Software > Video Game Software > All XBox Games -Software > Video Game Software > All XBox Games > XBox (original) Games -Software > Video Game Software > All XBox Games > XBox 360 Games -Software > Video Game Software > Atari 2600 Games -Software > Video Game Software > Atari 5200 Games -Software > Video Game Software > Atari Lynx Games -Software > Video Game Software > ColecoVision Games -Software > Video Game Software > Computer Games -Software > Video Game Software > Computer Games > Mac Games -Software > Video Game Software > Computer Games > PC Games -Software > Video Game Software > Dreamcast Games -Software > Video Game Software > Game Gear Games -Software > Video Game Software > GameCube Games -Software > Video Game Software > Intellivision Games -Software > Video Game Software > Jaguar Games -Software > Video Game Software > LeapFrog Games -Software > Video Game Software > N-Gage Games -Software > Video Game Software > Neo Geo Games -Software > Video Game Software > Neo Geo Pocket Games -Software > Video Game Software > Nintendo 64 Games -Software > Video Game Software > Nintendo DS Games -Software > Video Game Software > Nintendo Entertainment System Games -Software > Video Game Software > PlayStation Portable Games -Software > Video Game Software > PlayStation Vita Games -Software > Video Game Software > Sega Genesis Games -Software > Video Game Software > Sega Master System Games -Software > Video Game Software > Super Nintendo Games -Software > Video Game Software > Turbografx 16 Games -Software > Video Game Software > Wii Games -Software > Video Game Software > Wii U Games Sporting Goods -Sporting Goods > Air Sports -Sporting Goods > Air Sports > Air Suits -Sporting Goods > Air Sports > Hang Gliding -Sporting Goods > Air Sports > Hang Gliding > Hang Gliders -Sporting Goods > Air Sports > Parachutes -Sporting Goods > Combat Sports -Sporting Goods > Combat Sports > Boxing -Sporting Goods > Combat Sports > Boxing & Martial Arts Protective Gear -Sporting Goods > Combat Sports > Boxing > Boxing Gloves & Mitts -Sporting Goods > Combat Sports > Boxing > Boxing Ring Parts -Sporting Goods > Combat Sports > Boxing > Boxing Rings -Sporting Goods > Combat Sports > Boxing > Punching Bag Accessories -Sporting Goods > Combat Sports > Fencing -Sporting Goods > Combat Sports > Fencing > Fencing Protective Gear -Sporting Goods > Combat Sports > Fencing > Fencing Protective Gear > Fencing Gloves -Sporting Goods > Combat Sports > Fencing > Fencing Protective Gear > Fencing Masks -Sporting Goods > Combat Sports > Fencing > Fencing Weapons -Sporting Goods > Combat Sports > Grappling Dummies -Sporting Goods > Combat Sports > Martial Arts -Sporting Goods > Combat Sports > Martial Arts > Martial Arts Belts -Sporting Goods > Combat Sports > Martial Arts > Martial Arts Weapons -Sporting Goods > Combat Sports > Punching & Training Bag Accessories -Sporting Goods > Combat Sports > Punching & Training Bags -Sporting Goods > Combat Sports > Wrestling -Sporting Goods > Combat Sports > Wrestling > Wrestling Mats -Sporting Goods > Combat Sports > Wrestling > Wrestling Protective Gear -Sporting Goods > Dancing -Sporting Goods > Dancing > Ballet Bars +Sporting Goods > Athletics +Sporting Goods > Athletics > Baseball & Softball +Sporting Goods > Athletics > Baseball & Softball > Baseball & Softball Bases & Plates +Sporting Goods > Athletics > Baseball & Softball > Baseball & Softball Batting Gloves +Sporting Goods > Athletics > Baseball & Softball > Baseball & Softball Gloves & Mitts +Sporting Goods > Athletics > Baseball & Softball > Baseball & Softball Pitching Mats +Sporting Goods > Athletics > Baseball & Softball > Baseball & Softball Pitching Mounds +Sporting Goods > Athletics > Baseball & Softball > Baseball & Softball Protective Gear +Sporting Goods > Athletics > Baseball & Softball > Baseball & Softball Protective Gear > Baseball & Softball Batting Helmets +Sporting Goods > Athletics > Baseball & Softball > Baseball & Softball Protective Gear > Baseball & Softball Chest Protectors +Sporting Goods > Athletics > Baseball & Softball > Baseball & Softball Protective Gear > Baseball & Softball Leg Guards +Sporting Goods > Athletics > Baseball & Softball > Baseball & Softball Protective Gear > Catchers Equipment Sets +Sporting Goods > Athletics > Baseball & Softball > Baseball & Softball Protective Gear > Catchers Helmets & Masks +Sporting Goods > Athletics > Baseball & Softball > Baseball Bats +Sporting Goods > Athletics > Baseball & Softball > Baseballs +Sporting Goods > Athletics > Baseball & Softball > Pitching Machines +Sporting Goods > Athletics > Baseball & Softball > Softball Bats +Sporting Goods > Athletics > Baseball & Softball > Softballs +Sporting Goods > Athletics > Basketball +Sporting Goods > Athletics > Basketball > Basketball Hoop Parts & Accessories +Sporting Goods > Athletics > Basketball > Basketball Hoop Parts & Accessories > Basketball Backboards +Sporting Goods > Athletics > Basketball > Basketball Hoop Parts & Accessories > Basketball Hoop Padding +Sporting Goods > Athletics > Basketball > Basketball Hoop Parts & Accessories > Basketball Hoop Posts +Sporting Goods > Athletics > Basketball > Basketball Hoop Parts & Accessories > Basketball Nets +Sporting Goods > Athletics > Basketball > Basketball Hoop Parts & Accessories > Basketball Rims +Sporting Goods > Athletics > Basketball > Basketball Hoops +Sporting Goods > Athletics > Basketball > Basketball Training Aids +Sporting Goods > Athletics > Basketball > Basketballs +Sporting Goods > Athletics > Boxing & Martial Arts +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Protective Gear +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Protective Gear > Boxing & MMA Hand Wraps +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Protective Gear > Boxing & Martial Arts Arm Guards +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Protective Gear > Boxing & Martial Arts Body Protectors +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Protective Gear > Boxing & Martial Arts Headgear +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Protective Gear > Boxing Gloves & Mitts +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Protective Gear > MMA Shin Guards +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Training Equipment +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Training Equipment > Boxing & MMA Punch Mitts +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Training Equipment > Grappling Dummies +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Training Equipment > Punching & Training Bag Accessories +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Training Equipment > Punching & Training Bags +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing & Martial Arts Training Equipment > Strike Shields +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing Ring Parts +Sporting Goods > Athletics > Boxing & Martial Arts > Boxing Rings +Sporting Goods > Athletics > Boxing & Martial Arts > Martial Arts Belts +Sporting Goods > Athletics > Boxing & Martial Arts > Martial Arts Weapons +Sporting Goods > Athletics > Broomball Equipment +Sporting Goods > Athletics > Cheerleading +Sporting Goods > Athletics > Cheerleading > Cheerleading Pom Poms +Sporting Goods > Athletics > Coaching & Officiating +Sporting Goods > Athletics > Coaching & Officiating > Captains Armbands +Sporting Goods > Athletics > Coaching & Officiating > Field & Court Boundary Markers +Sporting Goods > Athletics > Coaching & Officiating > Flip Coins & Discs +Sporting Goods > Athletics > Coaching & Officiating > Linesman Flags +Sporting Goods > Athletics > Coaching & Officiating > Penalty Cards & Flags +Sporting Goods > Athletics > Coaching & Officiating > Pitch Counters +Sporting Goods > Athletics > Coaching & Officiating > Referee Stands & Chairs +Sporting Goods > Athletics > Coaching & Officiating > Referee Wallets +Sporting Goods > Athletics > Coaching & Officiating > Scoreboards +Sporting Goods > Athletics > Coaching & Officiating > Sport & Safety Whistles +Sporting Goods > Athletics > Coaching & Officiating > Umpire Indicators +Sporting Goods > Athletics > Cricket +Sporting Goods > Athletics > Cricket > Cricket Balls +Sporting Goods > Athletics > Cricket > Cricket Bat Accessories +Sporting Goods > Athletics > Cricket > Cricket Bat Accessories > Cricket Bat Grips +Sporting Goods > Athletics > Cricket > Cricket Bats +Sporting Goods > Athletics > Cricket > Cricket Equipment Sets +Sporting Goods > Athletics > Cricket > Cricket Protective Gear +Sporting Goods > Athletics > Cricket > Cricket Protective Gear > Cricket Gloves +Sporting Goods > Athletics > Cricket > Cricket Protective Gear > Cricket Helmets +Sporting Goods > Athletics > Cricket > Cricket Protective Gear > Cricket Leg Guards +Sporting Goods > Athletics > Cricket > Cricket Stumps +Sporting Goods > Athletics > Dancing +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 Masks +Sporting Goods > Athletics > Fencing > Fencing Weapons +Sporting Goods > Athletics > Field Hockey & Lacrosse +Sporting Goods > Athletics > Field Hockey & Lacrosse > Field Hockey & Lacrosse Protective Gear +Sporting Goods > Athletics > Field Hockey & Lacrosse > Field Hockey & Lacrosse Protective Gear > Field Hockey & Lacrosse Gloves +Sporting Goods > Athletics > Field Hockey & Lacrosse > Field Hockey & Lacrosse Protective Gear > Field Hockey & Lacrosse Helmets +Sporting Goods > Athletics > Field Hockey & Lacrosse > Field Hockey & Lacrosse Protective Gear > Field Hockey & Lacrosse Masks & Goggles +Sporting Goods > Athletics > Field Hockey & Lacrosse > Field Hockey & Lacrosse Protective Gear > Field Hockey & Lacrosse Pads +Sporting Goods > Athletics > Field Hockey & Lacrosse > Field Hockey Balls +Sporting Goods > Athletics > Field Hockey & Lacrosse > Field Hockey Goals +Sporting Goods > Athletics > Field Hockey & Lacrosse > Field Hockey Sticks +Sporting Goods > Athletics > Field Hockey & Lacrosse > Lacrosse Balls +Sporting Goods > Athletics > Field Hockey & Lacrosse > Lacrosse Equipment Sets +Sporting Goods > Athletics > Field Hockey & Lacrosse > Lacrosse Goals +Sporting Goods > Athletics > Field Hockey & Lacrosse > Lacrosse Stick Parts +Sporting Goods > Athletics > Field Hockey & Lacrosse > Lacrosse Stick Parts > Lacrosse Mesh & String +Sporting Goods > Athletics > Field Hockey & Lacrosse > Lacrosse Stick Parts > Lacrosse Stick Heads +Sporting Goods > Athletics > Field Hockey & Lacrosse > Lacrosse Stick Parts > Lacrosse Stick Shafts +Sporting Goods > Athletics > Field Hockey & Lacrosse > Lacrosse Sticks +Sporting Goods > Athletics > Field Hockey & Lacrosse > Lacrosse Training Aids +Sporting Goods > Athletics > Figure Skating & Hockey +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Balls & Pucks +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Goals +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Protective Gear +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Protective Gear > Hockey Elbow Pads +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Protective Gear > Hockey Gloves +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Protective Gear > Hockey Goalie Equipment Sets +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Protective Gear > Hockey Helmets +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Protective Gear > Hockey Pants +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Protective Gear > Hockey Shin Guards & Leg Pads +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Protective Gear > Hockey Shoulder Pads & Chest Protectors +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Protective Gear > Hockey Suspenders & Belts +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Sledges +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Stick Care +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Stick Parts +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Stick Parts > Hockey Stick Blades +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Stick Parts > Hockey Stick Shafts +Sporting Goods > Athletics > Figure Skating & Hockey > Hockey Sticks +Sporting Goods > Athletics > Figure Skating & Hockey > Ice Skate Parts & Accessories +Sporting Goods > Athletics > Figure Skating & Hockey > Ice Skate Parts & Accessories > Figure Skate Boots +Sporting Goods > Athletics > Figure Skating & Hockey > Ice Skate Parts & Accessories > Ice Skate Blades +Sporting Goods > Athletics > Figure Skating & Hockey > Ice Skate Parts & Accessories > Ice Skate Sharpeners +Sporting Goods > Athletics > Figure Skating & Hockey > Ice Skate Parts & Accessories > Skate Blade Guards +Sporting Goods > Athletics > Figure Skating & Hockey > Ice Skate Parts & Accessories > Skate Lace Tighteners +Sporting Goods > Athletics > Figure Skating & Hockey > Ice Skates +Sporting Goods > Athletics > Football +Sporting Goods > Athletics > Football > Football Gloves +Sporting Goods > Athletics > Football > Football Goal Posts +Sporting Goods > Athletics > Football > Football Kicking Tees & Holders +Sporting Goods > Athletics > Football > Football Protective Gear +Sporting Goods > Athletics > Football > Football Protective Gear > Football Girdles +Sporting Goods > Athletics > Football > Football Protective Gear > Football Helmet Accessories +Sporting Goods > Athletics > Football > Football Protective Gear > Football Helmet Accessories > Football Chin Straps +Sporting Goods > Athletics > Football > Football Protective Gear > Football Helmet Accessories > Football Face Masks +Sporting Goods > Athletics > Football > Football Protective Gear > Football Helmet Accessories > Football Helmet Padding +Sporting Goods > Athletics > Football > Football Protective Gear > Football Helmet Accessories > Football Helmet Visors +Sporting Goods > Athletics > Football > Football Protective Gear > Football Helmets +Sporting Goods > Athletics > Football > Football Protective Gear > Football Neck Rolls +Sporting Goods > Athletics > Football > Football Protective Gear > Football Rib Protection Shirts & Vests +Sporting Goods > Athletics > Football > Football Protective Gear > Football Shoulder Pads +Sporting Goods > Athletics > Football > Football Training Equipment +Sporting Goods > Athletics > Football > Football Training Equipment > Football Dummies & Sleds +Sporting Goods > Athletics > Football > Footballs +Sporting Goods > Athletics > General Purpose Athletic Equipment +Sporting Goods > Athletics > General Purpose Athletic Equipment > Altitude Training Masks +Sporting Goods > Athletics > General Purpose Athletic Equipment > Athletic Cups +Sporting Goods > Athletics > General Purpose Athletic Equipment > Ball Carrying Bags & Carts +Sporting Goods > Athletics > General Purpose Athletic Equipment > Ball Pump Accessories +Sporting Goods > Athletics > General Purpose Athletic Equipment > Ball Pump Accessories > Ball Pump Needles +Sporting Goods > Athletics > General Purpose Athletic Equipment > Ball Pumps +Sporting Goods > Athletics > General Purpose Athletic Equipment > Exercise & Gym Mat Storage Racks & Carts +Sporting Goods > Athletics > General Purpose Athletic Equipment > Grip Spray & Chalk +Sporting Goods > Athletics > General Purpose Athletic Equipment > Gym Mats +Sporting Goods > Athletics > General Purpose Athletic Equipment > Practice Nets & Screens +Sporting Goods > Athletics > General Purpose Athletic Equipment > Speed & Agility Ladders & Hurdles +Sporting Goods > Athletics > General Purpose Athletic Equipment > Sports & Agility Cones +Sporting Goods > Athletics > General Purpose Athletic Equipment > Sports Megaphones +Sporting Goods > Athletics > General Purpose Athletic Equipment > Sports Mouthguards +Sporting Goods > Athletics > General Purpose Athletic Equipment > Stadium Seats & Cushions +Sporting Goods > Athletics > Gymnastics +Sporting Goods > Athletics > Gymnastics > Gymnastics Bars & Balance Beams +Sporting Goods > Athletics > Gymnastics > Gymnastics Protective Gear +Sporting Goods > Athletics > Gymnastics > Gymnastics Protective Gear > Gymnastics Grips +Sporting Goods > Athletics > Gymnastics > Gymnastics Rings +Sporting Goods > Athletics > Gymnastics > Gymnastics Springboards +Sporting Goods > Athletics > Gymnastics > Pommel Horses +Sporting Goods > Athletics > Gymnastics > Vaulting Horses +Sporting Goods > Athletics > Racquetball & Squash +Sporting Goods > Athletics > Racquetball & Squash > Racquetball & Squash Balls +Sporting Goods > Athletics > Racquetball & Squash > Racquetball & Squash Eyewear +Sporting Goods > Athletics > Racquetball & Squash > Racquetball & Squash Gloves +Sporting Goods > Athletics > Racquetball & Squash > Racquetball Racquets +Sporting Goods > Athletics > Racquetball & Squash > Squash Racquets +Sporting Goods > Athletics > Rounders +Sporting Goods > Athletics > Rounders > Rounders Bats +Sporting Goods > Athletics > Rounders > Rounders Gloves +Sporting Goods > Athletics > Rugby +Sporting Goods > Athletics > Rugby > Rugby Balls +Sporting Goods > Athletics > Rugby > Rugby Gloves +Sporting Goods > Athletics > Rugby > Rugby Posts +Sporting Goods > Athletics > Rugby > Rugby Protective Gear +Sporting Goods > Athletics > Rugby > Rugby Protective Gear > Rugby Headgear +Sporting Goods > Athletics > Rugby > Rugby Training Aids +Sporting Goods > Athletics > Soccer +Sporting Goods > Athletics > Soccer > Soccer Balls +Sporting Goods > Athletics > Soccer > Soccer Corner Flags +Sporting Goods > Athletics > Soccer > Soccer Gloves +Sporting Goods > Athletics > Soccer > Soccer Goal Accessories +Sporting Goods > Athletics > Soccer > Soccer Goals +Sporting Goods > Athletics > Soccer > Soccer Protective Gear +Sporting Goods > Athletics > Soccer > Soccer Protective Gear > Soccer Shin Guards +Sporting Goods > Athletics > Team Handball +Sporting Goods > Athletics > Team Handball > Handballs +Sporting Goods > Athletics > Tennis +Sporting Goods > Athletics > Tennis > Tennis Ball Hoppers & Carts +Sporting Goods > Athletics > Tennis > Tennis Ball Machines +Sporting Goods > Athletics > Tennis > Tennis Ball Savers +Sporting Goods > Athletics > Tennis > Tennis Balls +Sporting Goods > Athletics > Tennis > Tennis Nets +Sporting Goods > Athletics > Tennis > Tennis Racquet Accessories +Sporting Goods > Athletics > Tennis > Tennis Racquet Accessories > Racquet Vibration Dampeners +Sporting Goods > Athletics > Tennis > Tennis Racquet Accessories > Tennis Racquet Bags +Sporting Goods > Athletics > Tennis > Tennis Racquet Accessories > Tennis Racquet Grips & Tape +Sporting Goods > Athletics > Tennis > Tennis Racquet Accessories > Tennis Racquet Grommets +Sporting Goods > Athletics > Tennis > Tennis Racquet Accessories > Tennis Racquet String +Sporting Goods > Athletics > Tennis > Tennis Racquets +Sporting Goods > Athletics > Track & Field +Sporting Goods > Athletics > Track & Field > Discus +Sporting Goods > Athletics > Track & Field > High Jump Crossbars +Sporting Goods > Athletics > Track & Field > High Jump Pits +Sporting Goods > Athletics > Track & Field > Javelins +Sporting Goods > Athletics > Track & Field > Pole Vault Pits +Sporting Goods > Athletics > Track & Field > Relay Batons +Sporting Goods > Athletics > Track & Field > Shot Puts +Sporting Goods > Athletics > Track & Field > Starter Pistols +Sporting Goods > Athletics > Track & Field > Throwing Hammers +Sporting Goods > Athletics > Track & Field > Track Hurdles +Sporting Goods > Athletics > Track & Field > Track Starting Blocks +Sporting Goods > Athletics > Track & Field > Vaulting Poles +Sporting Goods > Athletics > Volleyball +Sporting Goods > Athletics > Volleyball > Volleyball Nets +Sporting Goods > Athletics > Volleyball > Volleyball Protective Gear +Sporting Goods > Athletics > Volleyball > Volleyball Protective Gear > Volleyball Knee Pads +Sporting Goods > Athletics > Volleyball > Volleyball Training Aids +Sporting Goods > Athletics > Volleyball > Volleyballs +Sporting Goods > Athletics > Wallyball Equipment +Sporting Goods > Athletics > Water Polo +Sporting Goods > Athletics > Water Polo > Water Polo Balls +Sporting Goods > Athletics > Water Polo > Water Polo Caps +Sporting Goods > Athletics > Water Polo > Water Polo Goals +Sporting Goods > Athletics > Wrestling +Sporting Goods > Athletics > Wrestling > Wrestling Protective Gear +Sporting Goods > Athletics > Wrestling > Wrestling Protective Gear > Wrestling Headgear +Sporting Goods > Athletics > Wrestling > Wrestling Protective Gear > Wrestling Knee Pads Sporting Goods > Exercise & Fitness -Sporting Goods > Exercise & Fitness > Abdominal Equipment -Sporting Goods > Exercise & Fitness > Cardio Machine Accessories -Sporting Goods > Exercise & Fitness > Cardio Machines -Sporting Goods > Exercise & Fitness > Cardio Machines > Elliptical Trainers -Sporting Goods > Exercise & Fitness > Cardio Machines > Exercise Bikes -Sporting Goods > Exercise & Fitness > Cardio Machines > Rowing Machines -Sporting Goods > Exercise & Fitness > Cardio Machines > Stair Climbers -Sporting Goods > Exercise & Fitness > Cardio Machines > Treadmills +Sporting Goods > Exercise & Fitness > Ab Wheels & Rollers +Sporting Goods > Exercise & Fitness > Aerobic Steps +Sporting Goods > Exercise & Fitness > Balance Trainers +Sporting Goods > Exercise & Fitness > Cardio +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machine Accessories +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machine Accessories > Elliptical Trainer Accessories +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machine Accessories > Exercise Bike Accessories +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machine Accessories > Rowing Machine Accessories +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machine Accessories > Stair Climber & Stepper Accessories +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machine Accessories > Treadmill Accessories +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machines +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machines > Elliptical Trainers +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machines > Exercise Bikes +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machines > Rowing Machines +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machines > Stair Climbers & Steppers +Sporting Goods > Exercise & Fitness > Cardio > Cardio Machines > Treadmills +Sporting Goods > Exercise & Fitness > Cardio > Jump Ropes Sporting Goods > Exercise & Fitness > Exercise Balls Sporting Goods > Exercise & Fitness > Exercise Bands +Sporting Goods > Exercise & Fitness > Exercise Benches +Sporting Goods > Exercise & Fitness > Exercise Equipment Mats +Sporting Goods > Exercise & Fitness > Exercise Machine & Equipment Sets +Sporting Goods > Exercise & Fitness > Exercise Wedges Sporting Goods > Exercise & Fitness > Foam Roller Accessories Sporting Goods > Exercise & Fitness > Foam Roller Accessories > Foam Roller Storage Bags Sporting Goods > Exercise & Fitness > Foam Rollers -Sporting Goods > Exercise & Fitness > Free Weight Accessories -Sporting Goods > Exercise & Fitness > Free Weight Bars -Sporting Goods > Exercise & Fitness > Free Weights -Sporting Goods > Exercise & Fitness > Gym Mats -Sporting Goods > Exercise & Fitness > Gym Mats > Exercise Equipment Mats -Sporting Goods > Exercise & Fitness > Jump Ropes +Sporting Goods > Exercise & Fitness > Hand Exercisers +Sporting Goods > Exercise & Fitness > Inversion Tables & Systems Sporting Goods > Exercise & Fitness > Medicine Balls +Sporting Goods > Exercise & Fitness > Power Towers +Sporting Goods > Exercise & Fitness > Push Up & Pull Up Bars Sporting Goods > Exercise & Fitness > Reaction Balls +Sporting Goods > Exercise & Fitness > Speed & Resistance Parachutes +Sporting Goods > Exercise & Fitness > Sport Safety Lights & Reflectors Sporting Goods > Exercise & Fitness > Stopwatches -Sporting Goods > Exercise & Fitness > Weight Benches -Sporting Goods > Exercise & Fitness > Weight Lifting Belts -Sporting Goods > Exercise & Fitness > Weight Lifting Gloves & Hand Supports -Sporting Goods > Exercise & Fitness > Weight Lifting Machine Accessories -Sporting Goods > Exercise & Fitness > Weight Lifting Machines +Sporting Goods > Exercise & Fitness > Suspension Trainers +Sporting Goods > Exercise & Fitness > Vibration Exercise Machines +Sporting Goods > Exercise & Fitness > Weight Lifting +Sporting Goods > Exercise & Fitness > Weight Lifting > Free Weight Accessories +Sporting Goods > Exercise & Fitness > Weight Lifting > Free Weight Accessories > Free Weight Storage Racks +Sporting Goods > Exercise & Fitness > Weight Lifting > Free Weight Accessories > Weight Bar Collars +Sporting Goods > Exercise & Fitness > Weight Lifting > Free Weight Accessories > Weight Bars +Sporting Goods > Exercise & Fitness > Weight Lifting > Free Weights +Sporting Goods > Exercise & Fitness > Weight Lifting > Weight Lifting Belts +Sporting Goods > Exercise & Fitness > Weight Lifting > Weight Lifting Gloves & Hand Supports +Sporting Goods > Exercise & Fitness > Weight Lifting > Weight Lifting Machine & Exercise Bench Accessories +Sporting Goods > Exercise & Fitness > Weight Lifting > Weight Lifting Machines & Racks Sporting Goods > Exercise & Fitness > Weighted Clothing Sporting Goods > Exercise & Fitness > Yoga & Pilates Sporting Goods > Exercise & Fitness > Yoga & Pilates > Pilates Machines Sporting Goods > Exercise & Fitness > Yoga & Pilates > Yoga & Pilates Blocks Sporting Goods > Exercise & Fitness > Yoga & Pilates > Yoga & Pilates Mats +Sporting Goods > Exercise & Fitness > Yoga & Pilates > Yoga & Pilates Towels Sporting Goods > Exercise & Fitness > Yoga & Pilates > Yoga Mat Bags & Straps -Sporting Goods > Exercise & Fitness > Yoga & Pilates Accessories -Sporting Goods > Exercise & Fitness > Yoga & Pilates Accessories > Yoga & Pilates Towels -Sporting Goods > Gymnastics -Sporting Goods > Gymnastics > Balance Beams -Sporting Goods > Gymnastics > Gymnastics Bars -Sporting Goods > Gymnastics > Gymnastics Mats -Sporting Goods > Gymnastics > Gymnastics Protective Gear -Sporting Goods > Gymnastics > Gymnastics Rings -Sporting Goods > Gymnastics > Pommel Horses -Sporting Goods > Gymnastics > Springboards -Sporting Goods > Gymnastics > Vaulting Horses Sporting Goods > Indoor Games Sporting Goods > Indoor Games > Air Hockey -Sporting Goods > Indoor Games > Air Hockey > Air Hockey Pucks -Sporting Goods > Indoor Games > Air Hockey > Air Hockey Strikers +Sporting Goods > Indoor Games > Air Hockey > Air Hockey Equipment Sporting Goods > Indoor Games > Air Hockey > Air Hockey Table Parts Sporting Goods > Indoor Games > Air Hockey > Air Hockey Tables Sporting Goods > Indoor Games > Billiards Sporting Goods > Indoor Games > Billiards > Billiard Ball Racks Sporting Goods > Indoor Games > Billiards > Billiard Balls Sporting Goods > Indoor Games > Billiards > Billiard Cue Accessories +Sporting Goods > Indoor Games > Billiards > Billiard Cue Accessories > Billiard Cue Cases +Sporting Goods > Indoor Games > Billiards > Billiard Cue Accessories > Billiard Cue Chalk Sporting Goods > Indoor Games > Billiards > Billiard Cue Accessories > Billiard Cue Racks Sporting Goods > Indoor Games > Billiards > Billiard Cues & Bridges Sporting Goods > Indoor Games > Billiards > Billiard Gloves Sporting Goods > Indoor Games > Billiards > Billiard Table Lights Sporting Goods > Indoor Games > Billiards > Billiard Table Parts & Accessories -Sporting Goods > Indoor Games > Billiards > Billiard Table Parts & Accessories > Billiard Cloth Sporting Goods > Indoor Games > Billiards > Billiard Table Parts & Accessories > Billiard Pockets Sporting Goods > Indoor Games > Billiards > Billiard Table Parts & Accessories > Billiard Table Brushes +Sporting Goods > Indoor Games > Billiards > Billiard Table Parts & Accessories > Billiard Table Cloth +Sporting Goods > Indoor Games > Billiards > Billiard Table Parts & Accessories > Billiard Table Covers Sporting Goods > Indoor Games > Billiards > Billiard Tables Sporting Goods > Indoor Games > Bowling Sporting Goods > Indoor Games > Bowling > Bowling Ball Bags @@ -5029,24 +4541,16 @@ Sporting Goods > Indoor Games > Bowling > Bowling Balls Sporting Goods > Indoor Games > Bowling > Bowling Gloves Sporting Goods > Indoor Games > Bowling > Bowling Pins Sporting Goods > Indoor Games > Bowling > Bowling Wrist Supports -Sporting Goods > Indoor Games > Darts -Sporting Goods > Indoor Games > Darts > Dart Backboards -Sporting Goods > Indoor Games > Darts > Dart Parts -Sporting Goods > Indoor Games > Darts > Dart Parts > Dart Flights -Sporting Goods > Indoor Games > Darts > Dart Parts > Dart Shafts -Sporting Goods > Indoor Games > Darts > Dart Parts > Dart Tips -Sporting Goods > Indoor Games > Darts > Dartboards -Sporting Goods > Indoor Games > Darts > Single Darts Sporting Goods > Indoor Games > Foosball Sporting Goods > Indoor Games > Foosball > Foosball Balls -Sporting Goods > Indoor Games > Foosball > Foosball Table Parts +Sporting Goods > Indoor Games > Foosball > Foosball Table Parts & Accessories Sporting Goods > Indoor Games > Foosball > Foosball Tables Sporting Goods > Indoor Games > Multi-Game Tables Sporting Goods > Indoor Games > Ping Pong Sporting Goods > Indoor Games > Ping Pong > Ping Pong Balls Sporting Goods > Indoor Games > Ping Pong > Ping Pong Nets & Posts Sporting Goods > Indoor Games > Ping Pong > Ping Pong Paddle Accessories -Sporting Goods > Indoor Games > Ping Pong > Ping Pong Paddles +Sporting Goods > Indoor Games > Ping Pong > Ping Pong Paddles & Sets Sporting Goods > Indoor Games > Ping Pong > Ping Pong Robot Accessories Sporting Goods > Indoor Games > Ping Pong > Ping Pong Robots Sporting Goods > Indoor Games > Ping Pong > Ping Pong Tables @@ -5054,92 +4558,168 @@ Sporting Goods > Indoor Games > Table Shuffleboard Sporting Goods > Indoor Games > Table Shuffleboard > Shuffleboard Tables Sporting Goods > Indoor Games > Table Shuffleboard > Table Shuffleboard Powder Sporting Goods > Indoor Games > Table Shuffleboard > Table Shuffleboard Pucks -Sporting Goods > Jumping -Sporting Goods > Jumping > Bungee Jumping -Sporting Goods > Jumping > Jumping Stilts -Sporting Goods > Jumping > Pogo Sticks -Sporting Goods > Jumping > Trampoline Accessories -Sporting Goods > Jumping > Trampoline Accessories > Trampoline Anchors -Sporting Goods > Jumping > Trampoline Accessories > Trampoline Ladders -Sporting Goods > Jumping > Trampoline Accessories > Trampoline Mats -Sporting Goods > Jumping > Trampoline Accessories > Trampoline Nets -Sporting Goods > Jumping > Trampoline Accessories > Trampoline Safety Pads -Sporting Goods > Jumping > Trampoline Accessories > Trampoline Springs -Sporting Goods > Jumping > Trampolines +Sporting Goods > Indoor Games > Throwing Darts +Sporting Goods > Indoor Games > Throwing Darts > Dart Backboards +Sporting Goods > Indoor Games > Throwing Darts > Dart Parts +Sporting Goods > Indoor Games > Throwing Darts > Dart Parts > Dart Flights +Sporting Goods > Indoor Games > Throwing Darts > Dart Parts > Dart Shafts +Sporting Goods > Indoor Games > Throwing Darts > Dart Parts > Dart Tips +Sporting Goods > Indoor Games > Throwing Darts > Dartboards +Sporting Goods > Indoor Games > Throwing Darts > Darts Sporting Goods > Outdoor Recreation -Sporting Goods > Outdoor Recreation > Archery -Sporting Goods > Outdoor Recreation > Archery > Archery Armguards -Sporting Goods > Outdoor Recreation > Archery > Archery Bow Cases -Sporting Goods > Outdoor Recreation > Archery > Archery Targets -Sporting Goods > Outdoor Recreation > Archery > Arrow Parts & Accessories -Sporting Goods > Outdoor Recreation > Archery > Arrows -Sporting Goods > Outdoor Recreation > Archery > Compound Bows -Sporting Goods > Outdoor Recreation > Archery > Crossbows -Sporting Goods > Outdoor Recreation > Archery > Quivers -Sporting Goods > Outdoor Recreation > Archery > Recurve & Longbows -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Camp Furniture -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Camp Furniture > Air Mattress Accessories -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Camp Furniture > Air Mattresses -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Camp Furniture > Cots -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Camping Cookware & Dinnerware -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Camping Lights & Lanterns -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Camping Tools -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Camping Tools > Knives & Blades -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Camping Tools > Knives & Blades > Hunting & Survival Knives -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Camping Tools > Multifunction Tools & Knives -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Chemical Hand Warmers -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Compression Sacks -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Crampons -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Emergency Blankets -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Freeze-Dried Food -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Hiking Pole Accessories -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Hiking Poles -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Mosquito Nets -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Navigational Compasses -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Portable Toilets -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Portable Urination Devices -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Portable Water Filters & Purifiers -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Sleeping Bag Liners -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Sleeping Bags -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Sleeping Pads -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Tent Accessories -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Tent Accessories > Tent Footprints -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Tent Accessories > Tent Poles & Stakes -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Tent Accessories > Tent Vestibules -Sporting Goods > Outdoor Recreation > Camping, Backpacking & Hiking > Tents +Sporting Goods > Outdoor Recreation > Boating & Water Sports +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Rafting +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Rafting > Boating Gloves +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Rafting > Canoe Accessories +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Rafting > Canoes +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Rafting > Kayak Accessories +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Rafting > Kayaks +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Rafting > Paddle Leashes +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Rafting > Paddles & Oars +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Rafting > Pedal Boats +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Rafting > Row Boats +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Rafting > Whitewater Rafts +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Water Sport Apparel +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Water Sport Apparel > Drysuits +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Water Sport Apparel > Life Jacket Accessories +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Water Sport Apparel > Life Jackets +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Water Sport Apparel > Rash Guards & Swim Shirts +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Water Sport Apparel > Water Sport Helmets +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Water Sport Apparel > Wetsuit Pieces +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Water Sport Apparel > Wetsuit Pieces > Wetsuit Bottoms +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Water Sport Apparel > Wetsuit Pieces > Wetsuit Hoods, Gloves & Boots +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Water Sport Apparel > Wetsuit Pieces > Wetsuit Tops +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Boating & Water Sport Apparel > Wetsuits +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Diving & Snorkeling +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Diving & Snorkeling > Buoyancy Compensators +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Diving & Snorkeling > Dive Computers +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Diving & Snorkeling > Diving & Snorkeling Equipment Sets +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Diving & Snorkeling > Diving & Snorkeling Fins +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Diving & Snorkeling > Diving & Snorkeling Masks +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Diving & Snorkeling > Diving Belts +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Diving & Snorkeling > Diving Knives & Shears +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Diving & Snorkeling > Diving Regulators +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Diving & Snorkeling > Snorkels +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Kitesurfing +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Kitesurfing > Kiteboard Cases +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Kitesurfing > Kiteboard Parts +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Kitesurfing > Kiteboards +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Kitesurfing > Kitesurfing & Windsurfing Harnesses +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Kitesurfing > Kitesurfing Kites +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Surfing +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Surfing > Bodyboards +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Surfing > Paddleboards +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Surfing > Skimboards +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Surfing > Surf Leashes +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Surfing > Surfboard Cases & Bags +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Surfing > Surfboard Fins +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Surfing > Surfboard Wax +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Surfing > Surfboards +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Surfing > Surfing Gloves +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Surfing > Surfing Tail Pads +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Child Swimming Aids +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Hand Paddles +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Kickboards +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Pull Buoys +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Swim Belts +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Swim Caps +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Swim Gloves +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Swim Goggle & Mask Accessories +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Swim Goggles & Masks +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Swim Weights +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Swimming Fins +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Swimming Fins > Monofins +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Swimming Fins > Training Fins +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Swimming Machines +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Swimming > Swimming Nose Clips +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Kneeboarding +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Kneeboarding > Kneeboards +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Towable Rafts & Tubes +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Towed Water Sport Gloves +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Wakeboarding +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Wakeboarding > Kiteboard & Wakeboard Bindings +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Wakeboarding > Wakeboard Parts +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Wakeboarding > Wakeboards +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Water Skiing +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Water Skiing > Sit-Down Hydrofoils +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Water Skiing > Water Ski Bindings +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Water Skiing > Water Ski Cases & Bags +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Water Skiing > Water Skis +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Towed Water Sports > Water Sport Tow Cables +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Watercraft Storage Racks +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Watercraft Storage Racks > Boat Storage Racks +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Watercraft Storage Racks > Water Sport Board Storage Racks +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Windsurfing +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Windsurfing > Windsurfing Board Parts +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Windsurfing > Windsurfing Board Parts > Windsurfing Board Fins +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Windsurfing > Windsurfing Board Parts > Windsurfing Board Masts +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Windsurfing > Windsurfing Boards +Sporting Goods > Outdoor Recreation > Boating & Water Sports > Windsurfing > Windsurfing Sails +Sporting Goods > Outdoor Recreation > Camping & Hiking +Sporting Goods > Outdoor Recreation > Camping & Hiking > Camp Furniture +Sporting Goods > Outdoor Recreation > Camping & Hiking > Camp Furniture > Air Mattress & Sleeping Pad Accessories +Sporting Goods > Outdoor Recreation > Camping & Hiking > Camp Furniture > Air Mattresses +Sporting Goods > Outdoor Recreation > Camping & Hiking > Camp Furniture > Cots +Sporting Goods > Outdoor Recreation > Camping & Hiking > Camping Cookware & Dinnerware +Sporting Goods > Outdoor Recreation > Camping & Hiking > Camping Lights & Lanterns +Sporting Goods > Outdoor Recreation > Camping & Hiking > Camping Tools +Sporting Goods > Outdoor Recreation > Camping & Hiking > Camping Tools > Hunting & Survival Knives +Sporting Goods > Outdoor Recreation > Camping & Hiking > Camping Tools > Multifunction Tools & Knives +Sporting Goods > Outdoor Recreation > Camping & Hiking > Chemical Hand Warmers +Sporting Goods > Outdoor Recreation > Camping & Hiking > Compression Sacks +Sporting Goods > Outdoor Recreation > Camping & Hiking > Hiking Pole Accessories +Sporting Goods > Outdoor Recreation > Camping & Hiking > Hiking Poles +Sporting Goods > Outdoor Recreation > Camping & Hiking > Mosquito Nets & Insect Screens +Sporting Goods > Outdoor Recreation > Camping & Hiking > Navigational Compasses +Sporting Goods > Outdoor Recreation > Camping & Hiking > Portable Toilets & Showers +Sporting Goods > Outdoor Recreation > Camping & Hiking > Portable Toilets & Showers > Portable Showers & Privacy Enclosures +Sporting Goods > Outdoor Recreation > Camping & Hiking > Portable Toilets & Showers > Portable Toilets & Urination Devices +Sporting Goods > Outdoor Recreation > Camping & Hiking > Portable Water Filters & Purifiers +Sporting Goods > Outdoor Recreation > Camping & Hiking > Sleeping Bag Liners +Sporting Goods > Outdoor Recreation > Camping & Hiking > Sleeping Bags +Sporting Goods > Outdoor Recreation > Camping & Hiking > Sleeping Pads +Sporting Goods > Outdoor Recreation > Camping & Hiking > Tent Accessories +Sporting Goods > Outdoor Recreation > Camping & Hiking > Tent Accessories > Inner Tents +Sporting Goods > Outdoor Recreation > Camping & Hiking > Tent Accessories > Tent Footprints +Sporting Goods > Outdoor Recreation > Camping & Hiking > Tent Accessories > Tent Poles & Stakes +Sporting Goods > Outdoor Recreation > Camping & Hiking > Tent Accessories > Tent Vestibules +Sporting Goods > Outdoor Recreation > Camping & Hiking > Tents +Sporting Goods > Outdoor Recreation > Camping & Hiking > Windbreaks Sporting Goods > Outdoor Recreation > Climbing -Sporting Goods > Outdoor Recreation > Climbing > Ice Climbing -Sporting Goods > Outdoor Recreation > Climbing > Ice Climbing > Ice Climbing Tools -Sporting Goods > Outdoor Recreation > Climbing > Ice Climbing > Ice Screws -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Ascenders & Descenders -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Belay Devices -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Carabiners -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Climbing Chalk -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Climbing Chalk Bags -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Climbing Harnesses -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Climbing Helmets -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Climbing Protection Devices -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Climbing Rope -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Climbing Rope Bags -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Climbing Webbing -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Crash Pads -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Indoor Climbing Holds -Sporting Goods > Outdoor Recreation > Climbing > Rock Climbing > Quickdraws +Sporting Goods > Outdoor Recreation > Climbing > Belay Devices +Sporting Goods > Outdoor Recreation > Climbing > Carabiners +Sporting Goods > Outdoor Recreation > Climbing > Climbing Apparel & Accessories +Sporting Goods > Outdoor Recreation > Climbing > Climbing Apparel & Accessories > Climbing Gloves +Sporting Goods > Outdoor Recreation > Climbing > Climbing Apparel & Accessories > Climbing Helmets +Sporting Goods > Outdoor Recreation > Climbing > Climbing Apparel & Accessories > Crampons +Sporting Goods > Outdoor Recreation > Climbing > Climbing Ascenders & Descenders +Sporting Goods > Outdoor Recreation > Climbing > Climbing Chalk Bags +Sporting Goods > Outdoor Recreation > Climbing > Climbing Crash Pads +Sporting Goods > Outdoor Recreation > Climbing > Climbing Harnesses +Sporting Goods > Outdoor Recreation > Climbing > Climbing Protection Devices +Sporting Goods > Outdoor Recreation > Climbing > Climbing Rope +Sporting Goods > Outdoor Recreation > Climbing > Climbing Rope Bags +Sporting Goods > Outdoor Recreation > Climbing > Climbing Webbing +Sporting Goods > Outdoor Recreation > Climbing > Ice Climbing Tools +Sporting Goods > Outdoor Recreation > Climbing > Ice Screws +Sporting Goods > Outdoor Recreation > Climbing > Indoor Climbing Holds +Sporting Goods > Outdoor Recreation > Climbing > Quickdraws Sporting Goods > Outdoor Recreation > Cycling Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Bags & Panniers Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Baskets Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Bells & Horns Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Cages +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Child Seat Accessories Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Child Seats Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Computer Accessories Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Computers Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Covers Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Fenders Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Front & Rear Racks -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Lights & Reflectors +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Handlebar Grips & Decor Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Locks Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Mirrors Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Pumps @@ -5147,34 +4727,26 @@ Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Sa Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Shock Pumps Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Spoke Beads Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Stands & Storage -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Surfboard Racks +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Tire Repair Supplies & Kits Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Toe Straps & Clips -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Tools, Cleaners & Lubricants -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Tools, Cleaners & Lubricants > Bicycle Lubrication -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Tools, Cleaners & Lubricants > Bicycle Spoke Wrenches +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Tools Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Trailers Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Trainers Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Training Wheels Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Transport Bags & Cases -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Gear -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Gear > Bicycle Cleat Accessories -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Gear > Bicycle Cleat Accessories > Bicycle Cleat Bolts -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Gear > Bicycle Cleat Accessories > Bicycle Cleat Covers -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Gear > Bicycle Cleat Accessories > Bicycle Cleat Shims & Wedges -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Gear > Bicycle Cleats -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Gear > Bicycle Gloves -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Gear > Bicycle Helmet Covers -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Gear > Bicycle Helmets -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Gear > Bicycle Shoe Covers +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Bicycle Water Sport Board Racks +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Accessories > Electric Bicycle Conversion Kits Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Brake Parts Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Brake Parts > Bicycle Brake Calipers Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Brake Parts > Bicycle Brake Levers Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Brake Parts > Bicycle Brake Rotors Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Brake Parts > Bicycle Brake Sets +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Cable Housings +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Cables Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Drivetrain Parts Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Drivetrain Parts > Bicycle Bottom Brackets -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Drivetrain Parts > Bicycle Cassettes +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Drivetrain Parts > Bicycle Cassettes & Freewheels Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Drivetrain Parts > Bicycle Chainrings Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Drivetrain Parts > Bicycle Chains Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Drivetrain Parts > Bicycle Cranks @@ -5183,102 +4755,117 @@ Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Drivetra Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Drivetrain Parts > Bicycle Shifters Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Forks Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Frames -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Grips & Handlebar Tape +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Groupsets Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Handlebar Extensions Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Handlebars Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Headset Parts Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Headset Parts > Bicycle Headset Bearings Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Headset Parts > Bicycle Headset Spacers Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Headsets +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Kickstands Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Saddles Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Seatpost Clamps Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Seatposts Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Small Parts -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Small Parts > Bicycle Accessory Mounts & Brackets -Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Small Parts > Bicycle Seatpost Shims Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Stems +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Tire Valve Adapters +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Tire Valve Caps +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Tire Valves Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Tires Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Tubes Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Wheel Parts Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Wheel Parts > Bicycle Foot Pegs +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Wheel Parts > Bicycle Hub Parts Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Wheel Parts > Bicycle Hubs +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Wheel Parts > Bicycle Rim Strips Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Wheel Parts > Bicycle Spokes +Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Wheel Parts > Bicycle Wheel Axles & Skewers Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Wheel Parts > Bicycle Wheel Nipples Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Wheel Parts > Bicycle Wheel Rims Sporting Goods > Outdoor Recreation > Cycling > Bicycle Parts > Bicycle Wheels Sporting Goods > Outdoor Recreation > Cycling > Bicycles -Sporting Goods > Outdoor Recreation > Cycling > Bicycles > Cruisers -Sporting Goods > Outdoor Recreation > Cycling > Bicycles > Electric Bicycles -Sporting Goods > Outdoor Recreation > Cycling > Bicycles > Hybrid Bicycles -Sporting Goods > Outdoor Recreation > Cycling > Bicycles > Mountain Bicycles -Sporting Goods > Outdoor Recreation > Cycling > Bicycles > Recumbent Bicycles -Sporting Goods > Outdoor Recreation > Cycling > Bicycles > Road Bicycles -Sporting Goods > Outdoor Recreation > Cycling > Bicycles > Triathlon Bicycles +Sporting Goods > Outdoor Recreation > Cycling > Cycling Apparel & Accessories +Sporting Goods > Outdoor Recreation > Cycling > Cycling Apparel & Accessories > Bicycle Cleat Accessories +Sporting Goods > Outdoor Recreation > Cycling > Cycling Apparel & Accessories > Bicycle Cleat Accessories > Bicycle Cleat Bolts +Sporting Goods > Outdoor Recreation > Cycling > Cycling Apparel & Accessories > Bicycle Cleat Accessories > Bicycle Cleat Covers +Sporting Goods > Outdoor Recreation > Cycling > Cycling Apparel & Accessories > Bicycle Cleat Accessories > Bicycle Cleat Shims & Wedges +Sporting Goods > Outdoor Recreation > Cycling > Cycling Apparel & Accessories > Bicycle Cleats +Sporting Goods > Outdoor Recreation > Cycling > Cycling Apparel & Accessories > Bicycle Gloves +Sporting Goods > Outdoor Recreation > Cycling > Cycling Apparel & Accessories > Bicycle Helmet Parts & Accessories +Sporting Goods > Outdoor Recreation > Cycling > Cycling Apparel & Accessories > Bicycle Helmets +Sporting Goods > Outdoor Recreation > Cycling > Cycling Apparel & Accessories > Bicycle Protective Pads +Sporting Goods > Outdoor Recreation > Cycling > Cycling Apparel & Accessories > Bicycle Shoe Covers Sporting Goods > Outdoor Recreation > Cycling > Tricycle Accessories Sporting Goods > Outdoor Recreation > Cycling > Tricycles -Sporting Goods > Outdoor Recreation > Cycling > Tricycles > Recumbent Tricycles Sporting Goods > Outdoor Recreation > Cycling > Unicycle Accessories Sporting Goods > Outdoor Recreation > Cycling > Unicycles -Sporting Goods > Outdoor Recreation > Deck Shuffleboard -Sporting Goods > Outdoor Recreation > Deck Shuffleboard > Deck Shuffleboard Cues -Sporting Goods > Outdoor Recreation > Deck Shuffleboard > Deck Shuffleboard Pucks -Sporting Goods > Outdoor Recreation > Disc Golf -Sporting Goods > Outdoor Recreation > Disc Golf > Disc Golf Bags -Sporting Goods > Outdoor Recreation > Disc Golf > Disc Golf Baskets -Sporting Goods > Outdoor Recreation > Disc Golf > Disc Golf Discs Sporting Goods > Outdoor Recreation > Equestrian -Sporting Goods > Outdoor Recreation > Equestrian > Horse Boots & Leg Wraps -Sporting Goods > Outdoor Recreation > Equestrian > Horse Boots & Leg Wraps > Bell Boots -Sporting Goods > Outdoor Recreation > Equestrian > Horse Boots & Leg Wraps > Splint Boots -Sporting Goods > Outdoor Recreation > Equestrian > Horse Supplies -Sporting Goods > Outdoor Recreation > Equestrian > Horse Supplies > Horse Blankets & Sheets -Sporting Goods > Outdoor Recreation > Equestrian > Horse Supplies > Horse Feed -Sporting Goods > Outdoor Recreation > Equestrian > Horse Supplies > Horse Grooming -Sporting Goods > Outdoor Recreation > Equestrian > Horse Supplies > Horse Grooming > Horse Clippers & Trimmers -Sporting Goods > Outdoor Recreation > Equestrian > Horse Supplies > Horse Treats -Sporting Goods > Outdoor Recreation > Equestrian > Horse Supplies > Horse Vitamins & Supplements +Sporting Goods > Outdoor Recreation > Equestrian > Horse Care +Sporting Goods > Outdoor Recreation > Equestrian > Horse Care > Horse Blankets & Sheets +Sporting Goods > Outdoor Recreation > Equestrian > Horse Care > Horse Boots & Leg Wraps +Sporting Goods > Outdoor Recreation > Equestrian > Horse Care > Horse Feed +Sporting Goods > Outdoor Recreation > Equestrian > Horse Care > Horse Fly Masks +Sporting Goods > Outdoor Recreation > Equestrian > Horse Care > Horse Grooming +Sporting Goods > Outdoor Recreation > Equestrian > Horse Care > Horse Grooming > Horse Clippers & Trimmers +Sporting Goods > Outdoor Recreation > Equestrian > Horse Care > Horse Grooming > Horse Grooming Combs, Brushes & Mitts +Sporting Goods > Outdoor Recreation > Equestrian > Horse Care > Horse Treats +Sporting Goods > Outdoor Recreation > Equestrian > Horse Care > Horse Vitamins & Supplements +Sporting Goods > Outdoor Recreation > Equestrian > Horse Care > Horse Wormers Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack +Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack > Bridle Bits Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack > Bridles Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack > Cinches -Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack > Horse Bits +Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack > Horse Halters Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack > Horse Harnesses +Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack > Horse Leads Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack > Reins Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack > Saddles +Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack > Stirrups Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack Accessories -Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack Accessories > Saddle Racks -Sporting Goods > Outdoor Recreation > Equestrian > Riding Gear -Sporting Goods > Outdoor Recreation > Equestrian > Riding Gear > Equestrian Gloves -Sporting Goods > Outdoor Recreation > Equestrian > Riding Gear > Equestrian Helmets -Sporting Goods > Outdoor Recreation > Equestrian > Riding Gear > Riding Crops -Sporting Goods > Outdoor Recreation > Equestrian > Riding Gear > Riding Pants +Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack Accessories > Horse Tack Boxes +Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack Accessories > Saddle Accessories +Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack Accessories > Saddle Accessories > Saddle Bags & Panniers +Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack Accessories > Saddle Accessories > Saddle Covers & Cases +Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack Accessories > Saddle Accessories > Saddle Pads & Blankets +Sporting Goods > Outdoor Recreation > Equestrian > Horse Tack Accessories > Saddle Accessories > Saddle Racks +Sporting Goods > Outdoor Recreation > Equestrian > Riding Apparel & Accessories +Sporting Goods > Outdoor Recreation > Equestrian > Riding Apparel & Accessories > Equestrian Gloves +Sporting Goods > Outdoor Recreation > Equestrian > Riding Apparel & Accessories > Equestrian Helmets +Sporting Goods > Outdoor Recreation > Equestrian > Riding Apparel & Accessories > Riding Crops & Whips +Sporting Goods > Outdoor Recreation > Equestrian > Riding Apparel & Accessories > Riding Pants Sporting Goods > Outdoor Recreation > Fishing +Sporting Goods > Outdoor Recreation > Fishing > Bite Alarms Sporting Goods > Outdoor Recreation > Fishing > Fishing & Hunting Waders Sporting Goods > Outdoor Recreation > Fishing > Fishing Bait & Chum Containers -Sporting Goods > Outdoor Recreation > Fishing > Fishing Floats Sporting Goods > Outdoor Recreation > Fishing > Fishing Gaffs Sporting Goods > Outdoor Recreation > Fishing > Fishing Hook Removal Tools -Sporting Goods > Outdoor Recreation > Fishing > Fishing Hooks Sporting Goods > Outdoor Recreation > Fishing > Fishing Lines & Leaders -Sporting Goods > Outdoor Recreation > Fishing > Fishing Lures -Sporting Goods > Outdoor Recreation > Fishing > Fishing Lures > Fishing Flies Sporting Goods > Outdoor Recreation > Fishing > Fishing Nets +Sporting Goods > Outdoor Recreation > Fishing > Fishing Reel Accessories +Sporting Goods > Outdoor Recreation > Fishing > Fishing Reel Accessories > Fishing Reel Bags & Cases +Sporting Goods > Outdoor Recreation > Fishing > Fishing Reel Accessories > Fishing Reel Lubricants +Sporting Goods > Outdoor Recreation > Fishing > Fishing Reel Accessories > Fishing Reel Replacement Spools Sporting Goods > Outdoor Recreation > Fishing > Fishing Reels +Sporting Goods > Outdoor Recreation > Fishing > Fishing Rod Accessories +Sporting Goods > Outdoor Recreation > Fishing > Fishing Rod Accessories > Fishing Rod Bags & Cases +Sporting Goods > Outdoor Recreation > Fishing > Fishing Rod Accessories > Fishing Rod Holders & Storage Racks Sporting Goods > Outdoor Recreation > Fishing > Fishing Rods -Sporting Goods > Outdoor Recreation > Fishing > Fishing Sinkers -Sporting Goods > Outdoor Recreation > Fishing > Fishing Snaps & Swivels Sporting Goods > Outdoor Recreation > Fishing > Fishing Spears +Sporting Goods > Outdoor Recreation > Fishing > Fishing Tackle +Sporting Goods > Outdoor Recreation > Fishing > Fishing Tackle > Fishing Baits & Lures +Sporting Goods > Outdoor Recreation > Fishing > Fishing Tackle > Fishing Floats +Sporting Goods > Outdoor Recreation > Fishing > Fishing Tackle > Fishing Hooks +Sporting Goods > Outdoor Recreation > Fishing > Fishing Tackle > Fishing Sinkers +Sporting Goods > Outdoor Recreation > Fishing > Fishing Tackle > Fishing Snaps & Swivels Sporting Goods > Outdoor Recreation > Fishing > Fishing Traps Sporting Goods > Outdoor Recreation > Fishing > Fly Tying Materials Sporting Goods > Outdoor Recreation > Fishing > Fly Tying Materials > Fishing Beads Sporting Goods > Outdoor Recreation > Fishing > Fly Tying Materials > Fishing Yarn Sporting Goods > Outdoor Recreation > Fishing > Live Bait Sporting Goods > Outdoor Recreation > Fishing > Tackle Bags & Boxes -Sporting Goods > Outdoor Recreation > Geocaching -Sporting Goods > Outdoor Recreation > Geocaching > Geocaches -Sporting Goods > Outdoor Recreation > Geocaching > Geocaching Log Sheets -Sporting Goods > Outdoor Recreation > Geocaching > Geocaching Trackables Sporting Goods > Outdoor Recreation > Golf Sporting Goods > Outdoor Recreation > Golf > Divot Tools +Sporting Goods > Outdoor Recreation > Golf > Golf Accessory Sets Sporting Goods > Outdoor Recreation > Golf > Golf Bag Accessories Sporting Goods > Outdoor Recreation > Golf > Golf Bag Accessories > Golf Bag Carts Sporting Goods > Outdoor Recreation > Golf > Golf Bag Accessories > Golf Bag Covers & Cases @@ -5287,410 +4874,157 @@ Sporting Goods > Outdoor Recreation > Golf > Golf Ball Markers Sporting Goods > Outdoor Recreation > Golf > Golf Balls Sporting Goods > Outdoor Recreation > Golf > Golf Club Parts & Accessories Sporting Goods > Outdoor Recreation > Golf > Golf Club Parts & Accessories > Golf Club Grips -Sporting Goods > Outdoor Recreation > Golf > Golf Club Parts & Accessories > Golf Club Head Covers +Sporting Goods > Outdoor Recreation > Golf > Golf Club Parts & Accessories > Golf Club Headcovers +Sporting Goods > Outdoor Recreation > Golf > Golf Club Parts & Accessories > Golf Club Shafts Sporting Goods > Outdoor Recreation > Golf > Golf Clubs Sporting Goods > Outdoor Recreation > Golf > Golf Flags Sporting Goods > Outdoor Recreation > Golf > Golf Gloves -Sporting Goods > Outdoor Recreation > Golf > Golf Kits Sporting Goods > Outdoor Recreation > Golf > Golf Tees Sporting Goods > Outdoor Recreation > Golf > Golf Towels Sporting Goods > Outdoor Recreation > Golf > Golf Training Aids -Sporting Goods > Outdoor Recreation > Hunting -Sporting Goods > Outdoor Recreation > Hunting & Shooting Protective Gear -Sporting Goods > Outdoor Recreation > Hunting & Shooting Protective Gear > Hunting & Shooting Gloves -Sporting Goods > Outdoor Recreation > Hunting & Shooting Protective Gear > Hunting & Shooting Jackets -Sporting Goods > Outdoor Recreation > Hunting > Animal Traps -Sporting Goods > Outdoor Recreation > Hunting > Game Feeders -Sporting Goods > Outdoor Recreation > Hunting > Hunting Blinds & Screens -Sporting Goods > Outdoor Recreation > Hunting > Hunting Calls -Sporting Goods > Outdoor Recreation > Hunting > Hunting Decoys -Sporting Goods > Outdoor Recreation > Hunting > Hunting Dog Equipment -Sporting Goods > Outdoor Recreation > Hunting > Hunting Shelters -Sporting Goods > Outdoor Recreation > Hunting > Tree Stands -Sporting Goods > Outdoor Recreation > Hunting > Wildlife Attractant +Sporting Goods > Outdoor Recreation > Hang Gliding & Skydiving +Sporting Goods > Outdoor Recreation > Hang Gliding & Skydiving > Air Suits +Sporting Goods > Outdoor Recreation > Hang Gliding & Skydiving > Hang Gliders +Sporting Goods > Outdoor Recreation > Hang Gliding & Skydiving > Parachutes +Sporting Goods > Outdoor Recreation > Hunting & Shooting +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Archery Armguards +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Archery Gloves & Releases +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Archery Targets +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Arrow Parts & Accessories +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Arrow Parts & Accessories > Arrow Fletchings +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Arrow Parts & Accessories > Arrow Nocks +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Arrow Parts & Accessories > Broadheads & Field Points +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Arrows & Bolts +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Bow & Crossbow Accessories +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Bows & Crossbows +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Bows & Crossbows > Compound Bows +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Bows & Crossbows > Crossbows +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Bows & Crossbows > Recurve & Longbows +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Archery > Quivers +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Clay Pigeon Shooting +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Clay Pigeon Shooting > Clay Pigeon Throwers +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Clay Pigeon Shooting > Clay Pigeons +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting & Shooting Protective Gear +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting & Shooting Protective Gear > Hunting & Shooting Gloves +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting & Shooting Protective Gear > Hunting & Shooting Jackets +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting > Animal Traps +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting > Hearing Enhancers +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting > Hunting Blinds & Screens +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting > Hunting Dog Equipment +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting > Tree Stands +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting > Wild Game Feeders +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting > Wildlife Attractants +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting > Wildlife Attractants > Cover Scents & Scent Attractants +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting > Wildlife Attractants > Hunting & Wildlife Calls +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting > Wildlife Attractants > Hunting & Wildlife Decoys +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Hunting > Wildlife Attractants > Wildlife Bait, Feed & Minerals +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Airsoft +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Airsoft > Airsoft Gun Parts & Accessories +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Airsoft > Airsoft Gun Parts & Accessories > Airsoft Gun Batteries +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Airsoft > Airsoft Guns +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Airsoft > Airsoft Pellets +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball & Airsoft Protective Gear +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball & Airsoft Protective Gear > Paintball & Airsoft Gloves +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball & Airsoft Protective Gear > Paintball & Airsoft Goggles & Masks +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball & Airsoft Protective Gear > Paintball & Airsoft Pads +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball & Airsoft Protective Gear > Paintball & Airsoft Vests +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball > Paintball Grenade Launchers +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball > Paintball Grenades +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball > Paintball Gun Parts & Accessories +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball > Paintball Gun Parts & Accessories > Paintball Air Tanks +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball > Paintball Gun Parts & Accessories > Paintball Gun Barrels +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball > Paintball Gun Parts & Accessories > Paintball Gun Drop Forwards +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball > Paintball Gun Parts & Accessories > Paintball Hoppers +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball > Paintball Guns +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball > Paintball Harnesses & Packs +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Paintball & Airsoft > Paintball > Paintballs +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Shooting & Range Accessories +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Shooting & Range Accessories > Shooting Rests +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Shooting & Range Accessories > Shooting Sticks & Bipods +Sporting Goods > Outdoor Recreation > Hunting & Shooting > Shooting & Range Accessories > Shooting Targets Sporting Goods > Outdoor Recreation > Hydration System Accessories Sporting Goods > Outdoor Recreation > Hydration Systems +Sporting Goods > Outdoor Recreation > Inline & Roller Skating +Sporting Goods > Outdoor Recreation > Inline & Roller Skating > Inline & Roller Skating Protective Gear +Sporting Goods > Outdoor Recreation > Inline & Roller Skating > Inline & Roller Skating Protective Gear > Roller Skating Pads +Sporting Goods > Outdoor Recreation > Inline & Roller Skating > Inline Skate Parts +Sporting Goods > Outdoor Recreation > Inline & Roller Skating > Inline Skates +Sporting Goods > Outdoor Recreation > Inline & Roller Skating > Roller Skate Parts +Sporting Goods > Outdoor Recreation > Inline & Roller Skating > Roller Skates +Sporting Goods > Outdoor Recreation > Inline & Roller Skating > Roller Skis Sporting Goods > Outdoor Recreation > Kite Buggying Sporting Goods > Outdoor Recreation > Kite Buggying > Kite Buggies -Sporting Goods > Outdoor Recreation > Kite Buggying Accessories -Sporting Goods > Outdoor Recreation > Lawn Games -Sporting Goods > Outdoor Recreation > Lawn Games > Bean Bag Toss -Sporting Goods > Outdoor Recreation > Lawn Games > Bocce -Sporting Goods > Outdoor Recreation > Lawn Games > Croquet -Sporting Goods > Outdoor Recreation > Lawn Games > Horseshoe Toss -Sporting Goods > Outdoor Recreation > Lawn Games > Ladder Golf -Sporting Goods > Outdoor Recreation > Lawn Games > Lawn Bowling -Sporting Goods > Outdoor Recreation > Lawn Games > Lawn Bowling > Lawn Bowling Bowls -Sporting Goods > Outdoor Recreation > Lawn Games > Lawn Bowling > Lawn Bowling Jacks -Sporting Goods > Outdoor Recreation > Lawn Games > Lawn Darts -Sporting Goods > Outdoor Recreation > Lawn Games > Ring Toss -Sporting Goods > Outdoor Recreation > Lawn Games > Washer Pitching -Sporting Goods > Outdoor Recreation > Motorsports -Sporting Goods > Outdoor Recreation > Motorsports > Auto Racing -Sporting Goods > Outdoor Recreation > Reflective Fitness Clothing -Sporting Goods > Outdoor Recreation > Reflective Fitness Clothing > Reflective Belts +Sporting Goods > Outdoor Recreation > Kite Buggying > Kite Buggy Accessories +Sporting Goods > Outdoor Recreation > Outdoor Games +Sporting Goods > Outdoor Recreation > Outdoor Games > Badminton +Sporting Goods > Outdoor Recreation > Outdoor Games > Badminton > Badminton Nets +Sporting Goods > Outdoor Recreation > Outdoor Games > Badminton > Badminton Racquets & Sets +Sporting Goods > Outdoor Recreation > Outdoor Games > Badminton > Shuttlecocks +Sporting Goods > Outdoor Recreation > Outdoor Games > Deck Shuffleboard +Sporting Goods > Outdoor Recreation > Outdoor Games > Deck Shuffleboard > Deck Shuffleboard Cues +Sporting Goods > Outdoor Recreation > Outdoor Games > Deck Shuffleboard > Deck Shuffleboard Pucks +Sporting Goods > Outdoor Recreation > Outdoor Games > Disc Golf +Sporting Goods > Outdoor Recreation > Outdoor Games > Disc Golf > Disc Golf Bags +Sporting Goods > Outdoor Recreation > Outdoor Games > Disc Golf > Disc Golf Baskets +Sporting Goods > Outdoor Recreation > Outdoor Games > Lawn Games +Sporting Goods > Outdoor Recreation > Outdoor Games > Paddle Ball Sets +Sporting Goods > Outdoor Recreation > Outdoor Games > Pickleball +Sporting Goods > Outdoor Recreation > Outdoor Games > Pickleball > Pickleball Paddles +Sporting Goods > Outdoor Recreation > Outdoor Games > Pickleball > Pickleballs +Sporting Goods > Outdoor Recreation > Outdoor Games > Platform & Paddle Tennis +Sporting Goods > Outdoor Recreation > Outdoor Games > Platform & Paddle Tennis > Platform & Paddle Tennis Paddles +Sporting Goods > Outdoor Recreation > Outdoor Games > Platform & Paddle Tennis > Platform Tennis Balls +Sporting Goods > Outdoor Recreation > Outdoor Games > Tetherball +Sporting Goods > Outdoor Recreation > Outdoor Games > Tetherball > Tetherball Poles +Sporting Goods > Outdoor Recreation > Outdoor Games > Tetherball > Tetherball Sets +Sporting Goods > Outdoor Recreation > Outdoor Games > Tetherball > Tetherballs Sporting Goods > Outdoor Recreation > Riding Scooters -Sporting Goods > Outdoor Recreation > Riding Scooters > Kick Scooters -Sporting Goods > Outdoor Recreation > Riding Scooters > Powered Scooters -Sporting Goods > Outdoor Recreation > Running -Sporting Goods > Outdoor Recreation > Shooting Games -Sporting Goods > Outdoor Recreation > Shooting Games > Airsoft -Sporting Goods > Outdoor Recreation > Shooting Games > Airsoft > Airsoft Gun Parts & Accessories -Sporting Goods > Outdoor Recreation > Shooting Games > Airsoft > Airsoft Gun Parts & Accessories > Airsoft Gun Batteries -Sporting Goods > Outdoor Recreation > Shooting Games > Airsoft > Airsoft Guns -Sporting Goods > Outdoor Recreation > Shooting Games > Airsoft > Airsoft Pellets -Sporting Goods > Outdoor Recreation > Shooting Games > Airsoft > Airsoft Protective Gear -Sporting Goods > Outdoor Recreation > Shooting Games > Clay Pigeon Shooting -Sporting Goods > Outdoor Recreation > Shooting Games > Clay Pigeon Shooting > Clay Pigeon Throwers -Sporting Goods > Outdoor Recreation > Shooting Games > Clay Pigeon Shooting > Clay Pigeons -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Air Tanks -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Grenade Launchers -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Grenades -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Gun Parts -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Gun Parts > Paintball Gun Barrels -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Gun Parts > Paintball Hoppers -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Guns -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Harnesses & Packs -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Protective Gear -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Protective Gear > Paintball Gloves -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Protective Gear > Paintball Goggles & Masks -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Protective Gear > Paintball Pads -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintball Protective Gear > Paintball Vests -Sporting Goods > Outdoor Recreation > Shooting Games > Paintball > Paintballs -Sporting Goods > Outdoor Recreation > Shooting Games > Shooting Targets Sporting Goods > Outdoor Recreation > Skateboarding Sporting Goods > Outdoor Recreation > Skateboarding > Skate Rails Sporting Goods > Outdoor Recreation > Skateboarding > Skate Ramps Sporting Goods > Outdoor Recreation > Skateboarding > Skateboard Parts -Sporting Goods > Outdoor Recreation > Skateboarding > Skateboard Parts > Skateboard Bearings Sporting Goods > Outdoor Recreation > Skateboarding > Skateboard Parts > Skateboard Decks +Sporting Goods > Outdoor Recreation > Skateboarding > Skateboard Parts > Skateboard Small Parts Sporting Goods > Outdoor Recreation > Skateboarding > Skateboard Parts > Skateboard Trucks Sporting Goods > Outdoor Recreation > Skateboarding > Skateboard Parts > Skateboard Wheels -Sporting Goods > Outdoor Recreation > Skateboarding > Skateboard Protective Gear -Sporting Goods > Outdoor Recreation > Skateboarding > Skateboard Protective Gear > Skateboard Helmets -Sporting Goods > Outdoor Recreation > Skateboarding > Skateboard Protective Gear > Skateboard Pads -Sporting Goods > Outdoor Recreation > Skateboarding > Skateboard Protective Gear > Skateboarding Gloves +Sporting Goods > Outdoor Recreation > Skateboarding > Skateboarding Protective Gear +Sporting Goods > Outdoor Recreation > Skateboarding > Skateboarding Protective Gear > Skate Helmets +Sporting Goods > Outdoor Recreation > Skateboarding > Skateboarding Protective Gear > Skateboarding Gloves +Sporting Goods > Outdoor Recreation > Skateboarding > Skateboarding Protective Gear > Skateboarding Pads Sporting Goods > Outdoor Recreation > Skateboarding > Skateboards -Sporting Goods > Outdoor Recreation > Skating -Sporting Goods > Outdoor Recreation > Skating > Figure Skate Boots -Sporting Goods > Outdoor Recreation > Skating > Ice Skate Blades -Sporting Goods > Outdoor Recreation > Skating > Ice Skate Sharpeners -Sporting Goods > Outdoor Recreation > Skating > Ice Skates -Sporting Goods > Outdoor Recreation > Skating > Ice Skates > Figure Skates -Sporting Goods > Outdoor Recreation > Skating > Ice Skates > Ice Hockey Skates -Sporting Goods > Outdoor Recreation > Skating > Inline Skates -Sporting Goods > Outdoor Recreation > Skating > Lace Tighteners -Sporting Goods > Outdoor Recreation > Skating > Roller Skates -Sporting Goods > Outdoor Recreation > Skating > Skate Blade Guards -Sporting Goods > Outdoor Recreation > Skating > Skate Parts -Sporting Goods > Outdoor Recreation > Skating > Skating Helmets & Pads -Sporting Goods > Outdoor Recreation > Stadium Seats & Cushions -Sporting Goods > Outdoor Recreation > Tetherball -Sporting Goods > Outdoor Recreation > Track & Field -Sporting Goods > Outdoor Recreation > Track & Field > Discuses -Sporting Goods > Outdoor Recreation > Track & Field > High Jump Bars -Sporting Goods > Outdoor Recreation > Track & Field > High Jump Pits -Sporting Goods > Outdoor Recreation > Track & Field > Hurdles -Sporting Goods > Outdoor Recreation > Track & Field > Javelins -Sporting Goods > Outdoor Recreation > Track & Field > Pole Vault Pits -Sporting Goods > Outdoor Recreation > Track & Field > Relay Batons -Sporting Goods > Outdoor Recreation > Track & Field > Shot Puts -Sporting Goods > Outdoor Recreation > Track & Field > Starter Pistols -Sporting Goods > Outdoor Recreation > Track & Field > Throwing Hammers -Sporting Goods > Outdoor Recreation > Track & Field > Vaulting Poles -Sporting Goods > Racquet Sports -Sporting Goods > Racquet Sports > Badminton -Sporting Goods > Racquet Sports > Badminton > Badminton Nets -Sporting Goods > Racquet Sports > Badminton > Badminton Rackets -Sporting Goods > Racquet Sports > Badminton > Shuttlecocks -Sporting Goods > Racquet Sports > Paddle Ball Sets -Sporting Goods > Racquet Sports > Paddle Tennis -Sporting Goods > Racquet Sports > Pickleball -Sporting Goods > Racquet Sports > Platform Tennis -Sporting Goods > Racquet Sports > Racquetball -Sporting Goods > Racquet Sports > Racquetball > Racquetball Protective Gear -Sporting Goods > Racquet Sports > Racquetball > Racquetball Protective Gear > Squash & Racquetball Goggles -Sporting Goods > Racquet Sports > Racquetball > Racquetball Rackets -Sporting Goods > Racquet Sports > Racquetball > Racquetballs -Sporting Goods > Racquet Sports > Squash -Sporting Goods > Racquet Sports > Squash > Squash Balls -Sporting Goods > Racquet Sports > Squash > Squash Protective Gear -Sporting Goods > Racquet Sports > Squash > Squash Rackets -Sporting Goods > Racquet Sports > Tennis -Sporting Goods > Racquet Sports > Tennis > Tennis Ball Hoppers -Sporting Goods > Racquet Sports > Tennis > Tennis Ball Machines -Sporting Goods > Racquet Sports > Tennis > Tennis Ball Savers -Sporting Goods > Racquet Sports > Tennis > Tennis Balls -Sporting Goods > Racquet Sports > Tennis > Tennis Nets -Sporting Goods > Racquet Sports > Tennis > Tennis Racket Accessories -Sporting Goods > Racquet Sports > Tennis > Tennis Racket Accessories > Racket Grip Tape -Sporting Goods > Racquet Sports > Tennis > Tennis Racket Accessories > Racket Vibration Dampeners -Sporting Goods > Racquet Sports > Tennis > Tennis Racket Accessories > Tennis Racket Bags -Sporting Goods > Racquet Sports > Tennis > Tennis Racket Accessories > Tennis Racket Grommets -Sporting Goods > Racquet Sports > Tennis > Tennis Racket Accessories > Tennis Racket String -Sporting Goods > Racquet Sports > Tennis > Tennis Rackets -Sporting Goods > Team Sports -Sporting Goods > Team Sports > Ball Carrying Bags -Sporting Goods > Team Sports > Ball Pump Accessories -Sporting Goods > Team Sports > Ball Pump Accessories > Ball Pump Needles -Sporting Goods > Team Sports > Ball Pumps -Sporting Goods > Team Sports > Baseball -Sporting Goods > Team Sports > Baseball > Baseball & Softball Gloves -Sporting Goods > Team Sports > Baseball > Baseball & Softball Gloves > Baseball Gloves & Mitts -Sporting Goods > Team Sports > Baseball > Baseball & Softball Gloves > Softball Gloves & Mitts -Sporting Goods > Team Sports > Baseball > Baseball Bats -Sporting Goods > Team Sports > Baseball > Baseball Diamond Bases -Sporting Goods > Team Sports > Baseball > Baseball Protective Gear -Sporting Goods > Team Sports > Baseball > Baseball Protective Gear > Batting Helmets -Sporting Goods > Team Sports > Baseball > Baseball Screens -Sporting Goods > Team Sports > Baseball > Baseballs -Sporting Goods > Team Sports > Baseball > Batting Gloves -Sporting Goods > Team Sports > Baseball > Pitching Machines -Sporting Goods > Team Sports > Basketball -Sporting Goods > Team Sports > Basketball > Basketball Hoop Parts & Accessories -Sporting Goods > Team Sports > Basketball > Basketball Hoop Parts & Accessories > Basketball Backboards -Sporting Goods > Team Sports > Basketball > Basketball Hoop Parts & Accessories > Basketball Hoop Padding -Sporting Goods > Team Sports > Basketball > Basketball Hoop Parts & Accessories > Basketball Nets -Sporting Goods > Team Sports > Basketball > Basketball Hoop Parts & Accessories > Basketball Poles -Sporting Goods > Team Sports > Basketball > Basketball Hoop Parts & Accessories > Basketball Rims -Sporting Goods > Team Sports > Basketball > Basketball Hoops -Sporting Goods > Team Sports > Basketball > Basketballs -Sporting Goods > Team Sports > Beach Volleyball -Sporting Goods > Team Sports > Beach Volleyball > Beach Volleyball Balls -Sporting Goods > Team Sports > Beach Volleyball > Beach Volleyball Nets -Sporting Goods > Team Sports > Broomball -Sporting Goods > Team Sports > Broomball > Broomball Sticks -Sporting Goods > Team Sports > Cheerleading -Sporting Goods > Team Sports > Cheerleading > Cheerleading Megaphones -Sporting Goods > Team Sports > Cheerleading > Pom-Poms -Sporting Goods > Team Sports > Cricket -Sporting Goods > Team Sports > Cricket > Cricket Balls -Sporting Goods > Team Sports > Cricket > Cricket Bats -Sporting Goods > Team Sports > Cricket > Cricket Gloves -Sporting Goods > Team Sports > Cricket > Cricket Helmets -Sporting Goods > Team Sports > Curling -Sporting Goods > Team Sports > Field Hockey -Sporting Goods > Team Sports > Field Hockey > Field Hockey Balls -Sporting Goods > Team Sports > Field Hockey > Field Hockey Gloves -Sporting Goods > Team Sports > Field Hockey > Field Hockey Goals -Sporting Goods > Team Sports > Field Hockey > Field Hockey Sticks -Sporting Goods > Team Sports > Football -Sporting Goods > Team Sports > Football > Football Gloves -Sporting Goods > Team Sports > Football > Football Goal Posts -Sporting Goods > Team Sports > Football > Football Kicking Tees -Sporting Goods > Team Sports > Football > Football Protective Gear -Sporting Goods > Team Sports > Football > Football Protective Gear > Football Girdles -Sporting Goods > Team Sports > Football > Football Protective Gear > Football Helmet Accessories -Sporting Goods > Team Sports > Football > Football Protective Gear > Football Helmet Accessories > Football Chin Straps -Sporting Goods > Team Sports > Football > Football Protective Gear > Football Helmet Accessories > Football Face Masks -Sporting Goods > Team Sports > Football > Football Protective Gear > Football Helmet Accessories > Football Helmet Padding -Sporting Goods > Team Sports > Football > Football Protective Gear > Football Helmet Accessories > Football Helmet Visors -Sporting Goods > Team Sports > Football > Football Protective Gear > Football Helmets -Sporting Goods > Team Sports > Football > Football Protective Gear > Football Neck Rolls -Sporting Goods > Team Sports > Football > Football Protective Gear > Football Shoulder Pads -Sporting Goods > Team Sports > Football > Football Training Equipment -Sporting Goods > Team Sports > Football > Footballs -Sporting Goods > Team Sports > Hockey -Sporting Goods > Team Sports > Hockey > Hockey Balls & Pucks -Sporting Goods > Team Sports > Hockey > Hockey Balls & Pucks > Ice Hockey Pucks -Sporting Goods > Team Sports > Hockey > Hockey Balls & Pucks > Roller Hockey Balls -Sporting Goods > Team Sports > Hockey > Hockey Goals -Sporting Goods > Team Sports > Hockey > Hockey Goals > Ice Hockey Goals -Sporting Goods > Team Sports > Hockey > Hockey Protective Gear -Sporting Goods > Team Sports > Hockey > Hockey Protective Gear > Hockey Body Pads -Sporting Goods > Team Sports > Hockey > Hockey Protective Gear > Hockey Gloves -Sporting Goods > Team Sports > Hockey > Hockey Protective Gear > Hockey Gloves > Ice Hockey Gloves -Sporting Goods > Team Sports > Hockey > Hockey Protective Gear > Hockey Helmets -Sporting Goods > Team Sports > Hockey > Hockey Protective Gear > Hockey Helmets > Ice Hockey Helmets -Sporting Goods > Team Sports > Hockey > Hockey Protective Gear > Hockey Pants -Sporting Goods > Team Sports > Hockey > Hockey Sledges -Sporting Goods > Team Sports > Hockey > Hockey Stick Care -Sporting Goods > Team Sports > Hockey > Hockey Stick Parts -Sporting Goods > Team Sports > Hockey > Hockey Stick Parts > Hockey Shafts -Sporting Goods > Team Sports > Hockey > Hockey Stick Parts > Hockey Stick Blades -Sporting Goods > Team Sports > Hockey > Hockey Sticks -Sporting Goods > Team Sports > Hockey > Hockey Sticks > Ice Hockey Sticks -Sporting Goods > Team Sports > Hockey > Hockey Sticks > Roller & Street Hockey Sticks -Sporting Goods > Team Sports > Indoor Volleyball -Sporting Goods > Team Sports > Indoor Volleyball > Indoor Volleyball Balls -Sporting Goods > Team Sports > Indoor Volleyball > Indoor Volleyball Nets -Sporting Goods > Team Sports > Indoor Volleyball > Volleyball Bags -Sporting Goods > Team Sports > Lacrosse -Sporting Goods > Team Sports > Lacrosse > Lacrosse Balls -Sporting Goods > Team Sports > Lacrosse > Lacrosse Goals -Sporting Goods > Team Sports > Lacrosse > Lacrosse Protective Gear -Sporting Goods > Team Sports > Lacrosse > Lacrosse Protective Gear > Lacrosse Arm Guards -Sporting Goods > Team Sports > Lacrosse > Lacrosse Protective Gear > Lacrosse Gloves -Sporting Goods > Team Sports > Lacrosse > Lacrosse Protective Gear > Lacrosse Helmets -Sporting Goods > Team Sports > Lacrosse > Lacrosse Protective Gear > Lacrosse Masks & Goggles -Sporting Goods > Team Sports > Lacrosse > Lacrosse Protective Gear > Lacrosse Shoulder Pads -Sporting Goods > Team Sports > Lacrosse > Lacrosse Stick Parts -Sporting Goods > Team Sports > Lacrosse > Lacrosse Stick Parts > Lacrosse Mesh & String -Sporting Goods > Team Sports > Lacrosse > Lacrosse Stick Parts > Lacrosse Stick Heads -Sporting Goods > Team Sports > Lacrosse > Lacrosse Stick Parts > Lacrosse Stick Shafts -Sporting Goods > Team Sports > Lacrosse > Lacrosse Sticks -Sporting Goods > Team Sports > Lacrosse > Lacrosse Training Equipment -Sporting Goods > Team Sports > Referee Accessories -Sporting Goods > Team Sports > Referee Accessories > Linesman Flags -Sporting Goods > Team Sports > Referee Accessories > Penalty Cards -Sporting Goods > Team Sports > Referee Accessories > Referee Whistles -Sporting Goods > Team Sports > Rounders -Sporting Goods > Team Sports > Rounders > Rounders Bats -Sporting Goods > Team Sports > Rounders > Rounders Gloves -Sporting Goods > Team Sports > Rugby -Sporting Goods > Team Sports > Rugby > Rugby Balls -Sporting Goods > Team Sports > Rugby > Rugby Gloves -Sporting Goods > Team Sports > Rugby > Rugby Helmets -Sporting Goods > Team Sports > Rugby > Rugby Posts -Sporting Goods > Team Sports > Rugby > Rugby Training Equipment -Sporting Goods > Team Sports > Soccer -Sporting Goods > Team Sports > Soccer > Soccer Balls -Sporting Goods > Team Sports > Soccer > Soccer Balls > Futsal Balls -Sporting Goods > Team Sports > Soccer > Soccer Captain Armbands -Sporting Goods > Team Sports > Soccer > Soccer Corner Flags -Sporting Goods > Team Sports > Soccer > Soccer Gloves -Sporting Goods > Team Sports > Soccer > Soccer Goal Accessories -Sporting Goods > Team Sports > Soccer > Soccer Goals -Sporting Goods > Team Sports > Soccer > Soccer Shin Guards -Sporting Goods > Team Sports > Softball -Sporting Goods > Team Sports > Softball > Softball Bats -Sporting Goods > Team Sports > Softball > Softball Protective Gear -Sporting Goods > Team Sports > Softball > Softballs -Sporting Goods > Team Sports > Team Handball -Sporting Goods > Water Sports -Sporting Goods > Water Sports > Boating -Sporting Goods > Water Sports > Boating > Boating Gloves -Sporting Goods > Water Sports > Boating > Canoeing -Sporting Goods > Water Sports > Boating > Canoeing > Canoeing Accessories -Sporting Goods > Water Sports > Boating > Canoeing > Canoes -Sporting Goods > Water Sports > Boating > Kayaking -Sporting Goods > Water Sports > Boating > Kayaking > Kayaking Accessories -Sporting Goods > Water Sports > Boating > Kayaking > Kayaking Accessories > Spray Skirts -Sporting Goods > Water Sports > Boating > Kayaking > Kayaks -Sporting Goods > Water Sports > Boating > Pedal Boats -Sporting Goods > Water Sports > Boating > Rafts -Sporting Goods > Water Sports > Boating > Rowing -Sporting Goods > Water Sports > Boating > Rowing > Rowing Boats -Sporting Goods > Water Sports > Boating > Rowing > Rowing Seat Pads -Sporting Goods > Water Sports > Boating > Sailing & Yachting -Sporting Goods > Water Sports > Kitesurfing -Sporting Goods > Water Sports > Kitesurfing > Kiteboard Cases -Sporting Goods > Water Sports > Kitesurfing > Kiteboard Parts -Sporting Goods > Water Sports > Kitesurfing > Kiteboards -Sporting Goods > Water Sports > Kitesurfing > Kitesurfing Harnesses -Sporting Goods > Water Sports > Kitesurfing > Kitesurfing Kites -Sporting Goods > Water Sports > Life Jacket Accessories -Sporting Goods > Water Sports > Life Jackets -Sporting Goods > Water Sports > Paddles & Oars -Sporting Goods > Water Sports > Rash Guards -Sporting Goods > Water Sports > Scuba Diving & Snorkeling -Sporting Goods > Water Sports > Scuba Diving & Snorkeling > Buoyancy Compensators -Sporting Goods > Water Sports > Scuba Diving & Snorkeling > Dive Computers -Sporting Goods > Water Sports > Scuba Diving & Snorkeling > Diving & Snorkeling Fins -Sporting Goods > Water Sports > Scuba Diving & Snorkeling > Diving & Snorkeling Masks -Sporting Goods > Water Sports > Scuba Diving & Snorkeling > Diving Belts -Sporting Goods > Water Sports > Scuba Diving & Snorkeling > Diving Knives & Shears -Sporting Goods > Water Sports > Scuba Diving & Snorkeling > Drysuits -Sporting Goods > Water Sports > Scuba Diving & Snorkeling > Regulators -Sporting Goods > Water Sports > Scuba Diving & Snorkeling > Scuba Diving & Snorkeling Gloves -Sporting Goods > Water Sports > Scuba Diving & Snorkeling > Snorkels -Sporting Goods > Water Sports > Surfing -Sporting Goods > Water Sports > Surfing > Bodyboards -Sporting Goods > Water Sports > Surfing > Paddleboards -Sporting Goods > Water Sports > Surfing > Skimboards -Sporting Goods > Water Sports > Surfing > Surfboard Cases & Bags -Sporting Goods > Water Sports > Surfing > Surfboard Fins -Sporting Goods > Water Sports > Surfing > Surfboard Leashes -Sporting Goods > Water Sports > Surfing > Surfboard Wax -Sporting Goods > Water Sports > Surfing > Surfboards -Sporting Goods > Water Sports > Surfing > Surfing Gloves -Sporting Goods > Water Sports > Surfing > Surfing Tail Pads -Sporting Goods > Water Sports > Swimming -Sporting Goods > Water Sports > Swimming > Hand Paddles -Sporting Goods > Water Sports > Swimming > Kickboards -Sporting Goods > Water Sports > Swimming > Pull Buoys -Sporting Goods > Water Sports > Swimming > Swim Belts -Sporting Goods > Water Sports > Swimming > Swim Caps -Sporting Goods > Water Sports > Swimming > Swim Gloves -Sporting Goods > Water Sports > Swimming > Swim Goggle & Mask Accessories -Sporting Goods > Water Sports > Swimming > Swim Goggles & Masks -Sporting Goods > Water Sports > Swimming > Swim Weights -Sporting Goods > Water Sports > Swimming > Swimming Fins -Sporting Goods > Water Sports > Swimming > Swimming Fins > Monofins -Sporting Goods > Water Sports > Swimming > Swimming Fins > Training Fins -Sporting Goods > Water Sports > Swimming > Swimming Machines -Sporting Goods > Water Sports > Swimming > Swimming Nose Clips -Sporting Goods > Water Sports > Towed Water Sports -Sporting Goods > Water Sports > Towed Water Sports > Kneeboarding -Sporting Goods > Water Sports > Towed Water Sports > Kneeboarding > Kneeboards -Sporting Goods > Water Sports > Towed Water Sports > Parasailing -Sporting Goods > Water Sports > Towed Water Sports > Tow Cables -Sporting Goods > Water Sports > Towed Water Sports > Towable Rafts & Tubes -Sporting Goods > Water Sports > Towed Water Sports > Towed Water Sport Gloves -Sporting Goods > Water Sports > Towed Water Sports > Wakeboarding -Sporting Goods > Water Sports > Towed Water Sports > Wakeboarding > Wakeboard Bindings -Sporting Goods > Water Sports > Towed Water Sports > Wakeboarding > Wakeboards -Sporting Goods > Water Sports > Towed Water Sports > Water Skiing -Sporting Goods > Water Sports > Towed Water Sports > Water Skiing > Sit-Down Hydrofoils -Sporting Goods > Water Sports > Towed Water Sports > Water Skiing > Water Ski Bindings -Sporting Goods > Water Sports > Towed Water Sports > Water Skiing > Water Skiing Cases & Bags -Sporting Goods > Water Sports > Towed Water Sports > Water Skiing > Water Skis -Sporting Goods > Water Sports > Water Polo -Sporting Goods > Water Sports > Water Polo > Water Polo Balls -Sporting Goods > Water Sports > Water Polo > Water Polo Caps -Sporting Goods > Water Sports > Water Polo > Water Polo Goals -Sporting Goods > Water Sports > Water Tubing -Sporting Goods > Water Sports > Watercraft Storage Racks -Sporting Goods > Water Sports > Wetsuits -Sporting Goods > Water Sports > Wetsuits > Wetsuit Accessories -Sporting Goods > Water Sports > Wetsuits > Wetsuit Bottoms -Sporting Goods > Water Sports > Wetsuits > Wetsuit Tops -Sporting Goods > Water Sports > Whitewater Rafting -Sporting Goods > Water Sports > Whitewater Rafting > Whitewater Rafting Helmets -Sporting Goods > Water Sports > Whitewater Rafting > Whitewater Rafts -Sporting Goods > Water Sports > Windsurfing -Sporting Goods > Water Sports > Windsurfing > Windsurfing Board Parts -Sporting Goods > Water Sports > Windsurfing > Windsurfing Board Parts > Windsurfing Board Fins -Sporting Goods > Water Sports > Windsurfing > Windsurfing Board Parts > Windsurfing Board Masts -Sporting Goods > Water Sports > Windsurfing > Windsurfing Board Parts > Windsurfing Sails -Sporting Goods > Water Sports > Windsurfing > Windsurfing Boards -Sporting Goods > Winter Sports -Sporting Goods > Winter Sports > Bobsledding -Sporting Goods > Winter Sports > Luge -Sporting Goods > Winter Sports > Skiing -Sporting Goods > Winter Sports > Skiing > Ski & Snowboard Bags -Sporting Goods > Winter Sports > Skiing > Ski & Snowboard Bags > Ski Bags -Sporting Goods > Winter Sports > Skiing > Ski & Snowboard Bags > Snowboard Bags -Sporting Goods > Winter Sports > Skiing > Ski & Snowboard Goggle Lenses -Sporting Goods > Winter Sports > Skiing > Ski & Snowboard Goggles -Sporting Goods > Winter Sports > Skiing > Ski & Snowboard Helmets -Sporting Goods > Winter Sports > Skiing > Ski & Snowboard Storage Racks -Sporting Goods > Winter Sports > Skiing > Ski Bindings -Sporting Goods > Winter Sports > Skiing > Ski Bindings > Cross-Country Ski Bindings -Sporting Goods > Winter Sports > Skiing > Ski Bindings > Downhill Ski Bindings -Sporting Goods > Winter Sports > Skiing > Ski Boots -Sporting Goods > Winter Sports > Skiing > Ski Boots > Cross-Country Ski Boots -Sporting Goods > Winter Sports > Skiing > Ski Boots > Downhill Ski Boots -Sporting Goods > Winter Sports > Skiing > Ski Poles -Sporting Goods > Winter Sports > Skiing > Skis -Sporting Goods > Winter Sports > Skiing > Skis > Cross-Country Skis -Sporting Goods > Winter Sports > Skiing > Skis > Downhill Skis -Sporting Goods > Winter Sports > Sledding -Sporting Goods > Winter Sports > Sledding > Sleds -Sporting Goods > Winter Sports > Sledding > Sleds > Runner Sleds -Sporting Goods > Winter Sports > Sledding > Sleds > Saucer Sleds -Sporting Goods > Winter Sports > Sledding > Sleds > Toboggans -Sporting Goods > Winter Sports > Sledding > Snow Tubes -Sporting Goods > Winter Sports > Snowboarding -Sporting Goods > Winter Sports > Snowboarding > Snowboard Binding Parts -Sporting Goods > Winter Sports > Snowboarding > Snowboard Bindings -Sporting Goods > Winter Sports > Snowboarding > Snowboard Boots -Sporting Goods > Winter Sports > Snowboarding > Snowboards -Sporting Goods > Winter Sports > Snowmobiling -Sporting Goods > Winter Sports > Snowshoeing -Sporting Goods > Winter Sports > Snowshoeing > Snowshoe Bindings -Sporting Goods > Winter Sports > Snowshoeing > Snowshoes +Sporting Goods > Outdoor Recreation > Winter Sports & Activities +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Avalanche Safety +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Avalanche Safety > Avalanche Probes +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Avalanche Safety > Avalanche Safety Airbags +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski & Snowboard Bags +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski & Snowboard Goggle Accessories +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski & Snowboard Goggle Accessories > Ski & Snowboard Goggle Lenses +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski & Snowboard Goggles +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski & Snowboard Helmets +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski & Snowboard Leashes +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski & Snowboard Storage Racks +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski & Snowboard Tuning Tools +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski & Snowboard Wax +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski Binding Parts +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski Bindings +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski Boots +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Ski Poles +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Skis +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Skis > Cross-Country Skis +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Skis > Downhill Skis +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Snowboard Binding Parts +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Snowboard Bindings +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Snowboard Boots +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Skiing & Snowboarding > Snowboards +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Sleds +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Snowshoeing +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Snowshoeing > Snowshoe Bindings +Sporting Goods > Outdoor Recreation > Winter Sports & Activities > Snowshoeing > Snowshoes Toys & Games Toys & Games > Game Timers Toys & Games > Games @@ -5699,50 +5033,40 @@ Toys & Games > Games > Battle Tops Toys & Games > Games > Bingo Sets Toys & Games > Games > Blackjack & Craps Sets Toys & Games > Games > Board Games -Toys & Games > Games > Board Games > Backgammon Sets -Toys & Games > Games > Board Games > Checkers Sets -Toys & Games > Games > Board Games > Chess Sets -Toys & Games > Games > Board Games > Parcheesi Toys & Games > Games > Card Game Accessories Toys & Games > Games > Card Games -Toys & Games > Games > Card Games > Trading Card Games -Toys & Games > Games > Card Games > Traditional Playing Cards +Toys & Games > Games > Dexterity Games Toys & Games > Games > Dice Sets & Games -Toys & Games > Games > Dice Sets & Games > Dice & Dice Sets -Toys & Games > Games > Dice Sets & Games > Dice Games Toys & Games > Games > Poker Chip Accessories Toys & Games > Games > Poker Chip Accessories > Poker Chip Carriers & Trays Toys & Games > Games > Poker Chips & Sets Toys & Games > Games > Portable Electronic Games Toys & Games > Games > Roulette Wheels & Sets -Toys & Games > Games > Stacking Games +Toys & Games > Games > Slot Machines Toys & Games > Games > Tile Games -Toys & Games > Games > Tile Games > Dominoes -Toys & Games > Games > Tile Games > Mahjong Toys & Games > Outdoor Play Equipment -Toys & Games > Outdoor Play Equipment > Bounce House Accessories -Toys & Games > Outdoor Play Equipment > Bounce Houses -Toys & Games > Outdoor Play Equipment > Play Parachutes +Toys & Games > Outdoor Play Equipment > Inflatable Bouncer Accessories +Toys & Games > Outdoor Play Equipment > Inflatable Bouncers +Toys & Games > Outdoor Play Equipment > Play Swings Toys & Games > Outdoor Play Equipment > Play Tents & Tunnels Toys & Games > Outdoor Play Equipment > Playhouses +Toys & Games > Outdoor Play Equipment > Pogo Sticks Toys & Games > Outdoor Play Equipment > Sandboxes Toys & Games > Outdoor Play Equipment > See Saws Toys & Games > Outdoor Play Equipment > Slides +Toys & Games > Outdoor Play Equipment > Stilts Toys & Games > Outdoor Play Equipment > Swing Set & Playset Accessories Toys & Games > Outdoor Play Equipment > Swing Sets & Playsets -Toys & Games > Outdoor Play Equipment > Swings & Gliders +Toys & Games > Outdoor Play Equipment > Trampoline Accessories +Toys & Games > Outdoor Play Equipment > Trampolines Toys & Games > Outdoor Play Equipment > Water Play Equipment -Toys & Games > Outdoor Play Equipment > Water Play Equipment > Lawn Water Slides Toys & Games > Outdoor Play Equipment > Water Play Equipment > Play Sprinkers -Toys & Games > Outdoor Play Equipment > Water Play Equipment > Water Parks +Toys & Games > Outdoor Play Equipment > Water Play Equipment > Water Parks & Slides Toys & Games > Outdoor Play Equipment > Water Play Equipment > Water Tables Toys & Games > Puzzles Toys & Games > Puzzles > Jigsaw Puzzle Accessories Toys & Games > Puzzles > Jigsaw Puzzles Toys & Games > Puzzles > Mechanical Puzzles -Toys & Games > Puzzles > Mechanical Puzzles > Assembly & Disassembly Puzzles -Toys & Games > Puzzles > Mechanical Puzzles > Dexterity Puzzles -Toys & Games > Puzzles > Mechanical Puzzles > Tavern Puzzles Toys & Games > Puzzles > Wooden & Pegged Puzzles Toys & Games > Toys Toys & Games > Toys > Activity Toys @@ -5757,18 +5081,15 @@ Toys & Games > Toys > Activity Toys > Ribbon & Streamer Toys Toys & Games > Toys > Activity Toys > Spinning Tops Toys & Games > Toys > Activity Toys > Toy Jacks Toys & Games > Toys > Activity Toys > Yo-Yo Parts & Accessories -Toys & Games > Toys > Activity Toys > Yo-Yo Parts & Accessories > Yo-Yo Gloves Toys & Games > Toys > Activity Toys > Yo-Yos Toys & Games > Toys > Art & Drawing Toys -Toys & Games > Toys > Art & Drawing Toys > Toy Craft Kits +Toys & Games > Toys > Art & Drawing Toys > Play Dough & Putty Toys & Games > Toys > Art & Drawing Toys > Toy Drawing Tablets Toys & Games > Toys > Ball Pit Accessories Toys & Games > Toys > Ball Pit Accessories > Ball Pit Balls Toys & Games > Toys > Ball Pits Toys & Games > Toys > Bath Toys Toys & Games > Toys > Beach & Sand Toys -Toys & Games > Toys > Beach & Sand Toys > Beach Balls -Toys & Games > Toys > Beach & Sand Toys > Sand Pails Toys & Games > Toys > Building Toys Toys & Games > Toys > Building Toys > Construction Set Toys Toys & Games > Toys > Building Toys > Foam Blocks @@ -5777,11 +5098,6 @@ Toys & Games > Toys > Building Toys > Marble Track Sets Toys & Games > Toys > Building Toys > Wooden Blocks Toys & Games > Toys > Dolls, Playsets & Toy Figures Toys & Games > Toys > Dolls, Playsets & Toy Figures > Action & Toy Figures -Toys & Games > Toys > Dolls, Playsets & Toy Figures > Action & Toy Figures > Action Figures -Toys & Games > Toys > Dolls, Playsets & Toy Figures > Action & Toy Figures > Animal Figures -Toys & Games > Toys > Dolls, Playsets & Toy Figures > Action & Toy Figures > Animal Figures > Dinosaur Figures -Toys & Games > Toys > Dolls, Playsets & Toy Figures > Action & Toy Figures > Animal Figures > Horse & Pony Figures -Toys & Games > Toys > Dolls, Playsets & Toy Figures > Action & Toy Figures > Army Men Toys & Games > Toys > Dolls, Playsets & Toy Figures > Bobblehead Figures Toys & Games > Toys > Dolls, Playsets & Toy Figures > Doll & Action Figure Accessories Toys & Games > Toys > Dolls, Playsets & Toy Figures > Dollhouse Accessories @@ -5795,12 +5111,12 @@ Toys & Games > Toys > Dolls, Playsets & Toy Figures > Stuffed Animals Toys & Games > Toys > Dolls, Playsets & Toy Figures > Toy Playsets Toys & Games > Toys > Educational Toys Toys & Games > Toys > Educational Toys > Ant Farms +Toys & Games > Toys > Educational Toys > Astronomy Toys & Models Toys & Games > Toys > Educational Toys > Bug Collecting Kits +Toys & Games > Toys > Educational Toys > Educational Flash Cards Toys & Games > Toys > Educational Toys > Reading Toys -Toys & Games > Toys > Educational Toys > Science Sets +Toys & Games > Toys > Educational Toys > Science & Exploration Sets Toys & Games > Toys > Educational Toys > Toy Abacuses -Toys & Games > Toys > Electronic Toys -Toys & Games > Toys > Electronic Toys > Robotic Toys Toys & Games > Toys > Executive Toys Toys & Games > Toys > Executive Toys > Magnet Toys Toys & Games > Toys > Flying Toy Accessories @@ -5832,46 +5148,49 @@ Toys & Games > Toys > Pretend Play > Pretend Electronics Toys & Games > Toys > Pretend Play > Pretend Housekeeping Toys & Games > Toys > Pretend Play > Pretend Lawn & Garden Toys & Games > Toys > Pretend Play > Pretend Professions & Role Playing +Toys & Games > Toys > Pretend Play > Pretend Shopping & Grocery Toys & Games > Toys > Pretend Play > Toy Kitchens & Play Food Toys & Games > Toys > Pretend Play > Toy Tools Toys & Games > Toys > Remote Control Toy Accessories Toys & Games > Toys > Remote Control Toys Toys & Games > Toys > Remote Control Toys > Remote Control Airships & Blimps Toys & Games > Toys > Remote Control Toys > Remote Control Boats & Watercraft -Toys & Games > Toys > Remote Control Toys > Remote Control Boats & Watercraft > Remote Control Submarines Toys & Games > Toys > Remote Control Toys > Remote Control Cars & Trucks -Toys & Games > Toys > Remote Control Toys > Remote Control Figures Toys & Games > Toys > Remote Control Toys > Remote Control Helicopters Toys & Games > Toys > Remote Control Toys > Remote Control Motorcycles Toys & Games > Toys > Remote Control Toys > Remote Control Planes Toys & Games > Toys > Remote Control Toys > Remote Control Robots Toys & Games > Toys > Remote Control Toys > Remote Control Tanks Toys & Games > Toys > Riding Toy Accessories -Toys & Games > Toys > Riding Toy Accessories > Electric Riding Vehicle Accessories -Toys & Games > Toys > Riding Toy Accessories > Electric Riding Vehicle Accessories > Electric Riding Vehicle Chargers -Toys & Games > Toys > Riding Toy Accessories > Push & Pedal Riding Vehicle Accessories -Toys & Games > Toys > Riding Toy Accessories > Wagon Parts & Accessories Toys & Games > Toys > Riding Toys Toys & Games > Toys > Riding Toys > Electric Riding Vehicles Toys & Games > Toys > Riding Toys > Hobby Horses Toys & Games > Toys > Riding Toys > Push & Pedal Riding Vehicles Toys & Games > Toys > Riding Toys > Rocking & Spring Riding Toys Toys & Games > Toys > Riding Toys > Wagons +Toys & Games > Toys > Robotic Toys +Toys & Games > Toys > Sports Toy Accessories +Toys & Games > Toys > Sports Toy Accessories > Fitness Toy Accessories +Toys & Games > Toys > Sports Toy Accessories > Fitness Toy Accessories > Hula Hoop Accessories Toys & Games > Toys > Sports Toys +Toys & Games > Toys > Sports Toys > Baseball Toys +Toys & Games > Toys > Sports Toys > Basketball Toys Toys & Games > Toys > Sports Toys > Boomerangs +Toys & Games > Toys > Sports Toys > Bowling Toys +Toys & Games > Toys > Sports Toys > Fingerboards & Fingerboard Sets +Toys & Games > Toys > Sports Toys > Fishing Toys Toys & Games > Toys > Sports Toys > Fitness Toys Toys & Games > Toys > Sports Toys > Fitness Toys > Hula Hoops Toys & Games > Toys > Sports Toys > Flying Discs Toys & Games > Toys > Sports Toys > Footbags -Toys & Games > Toys > Sports Toys > Toy Baseball -Toys & Games > Toys > Sports Toys > Toy Basketball -Toys & Games > Toys > Sports Toys > Toy Bowling -Toys & Games > Toys > Sports Toys > Toy Football -Toys & Games > Toys > Sports Toys > Toy Football > Foam Footballs -Toys & Games > Toys > Sports Toys > Toy Golf -Toys & Games > Toys > Sports Toys > Toy Hockey -Toys & Games > Toys > Toy Weapons -Toys & Games > Toys > Toy Weapons > Water Guns +Toys & Games > Toys > Sports Toys > Golf Toys +Toys & Games > Toys > Sports Toys > Hockey Toys +Toys & Games > Toys > Sports Toys > Playground Balls +Toys & Games > Toys > Sports Toys > Racquet Sport Toys +Toys & Games > Toys > Sports Toys > Toy Footballs +Toys & Games > Toys > Toy Gift Baskets +Toys & Games > Toys > Toy Weapon & Gadget Accessories +Toys & Games > Toys > Toy Weapons & Gadgets Toys & Games > Toys > Visual Toys Toys & Games > Toys > Visual Toys > Kaleidoscopes Toys & Games > Toys > Visual Toys > Prisms @@ -5879,304 +5198,180 @@ Toys & Games > Toys > Wind-Up Toys Vehicles & Parts Vehicles & Parts > Vehicle Parts & Accessories Vehicles & Parts > Vehicle Parts & Accessories > Aircraft Parts & Accessories -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio & Video Cables -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio > Car Amplifiers -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio > Car Audio Receivers -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio > Car Audio Receivers > Car Satellite Radios -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio > Car CD Changers -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio > Car Cassette Players -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio > Car Equalizers & Crossovers -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio > Car Speakerphones -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio > Car Speakers -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio > Car Subwoofers -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Audio > Cassette Adapters -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Surge Protection -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Video -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Video > Car DVD Players -Vehicles & Parts > Vehicle Parts & Accessories > Car Audio & Video > Car Video > Car Video Monitors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Jump Starters -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Jumper Cables -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Tire Care -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Tire Care > Hubcaps -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Tire Care > Tire Covers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Tire Care > Tire Markers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Tire Care > Tire Pressure Sensors & Gauges -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Tire Care > Tire Valve Caps -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Battery Chargers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Cleaning -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Cleaning > Car Wash Brushes -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Cleaning > Car Wash Solutions -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Cleaning > Tire Wash & Shine -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Cleaning > Vehicle Aerosol Cleaners -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Cleaning > Vehicle Carpet Cleaners -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Cleaning > Vehicle Glass Cleaners -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Cleaning > Vehicle Upholstery Cleaners -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Cleaning > Vehicle Waxes, Polishes & Protectants -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Covers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > Antenna Balls -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > Bumper Stickers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > Car Seat Covers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > Car Seat Organizers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > License Plate Covers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > License Plate Frames -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > Vehicle Air Fresheners -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > Vehicle Decals -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > Vehicle Display Flags -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > Vehicle Emblems & Hood Ornaments -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > Vehicle Magnets -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Decor > Vehicle Window Films -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Diagnostic Scanners -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Antifreeze -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Brake Fluid -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Cooling System Additives -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Engine Degreasers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Fuel System Cleaners -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Hydraulic Clutch Fluid -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Motor Oil -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Power Steering Fluid -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Transmission Fluid -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Vehicle Greases -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Vehicle Performance Additives -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Fluids > Windshield Fluid -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Vehicle Paint -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Care > Windshield Repair Kits -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Bumper Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Bumpers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Hardtops -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > License Plates -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Body Kits -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Cargo Nets -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Rack Accessories -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Rack Accessories > Vehicle Bicycle Rack Accessories -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Rack Accessories > Vehicle Bicycle Rack Accessories > Bicycle Fork Mount Adapters -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Rack Accessories > Vehicle Bicycle Rack Accessories > Bicycle Frame Adapters -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Rack Accessories > Vehicle Bicycle Rack Accessories > Bicycle Wheel Straps -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Racks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Racks > Vehicle Base Rack Systems -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Racks > Vehicle Bicycle Racks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Racks > Vehicle Canoe & Kayak Racks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Racks > Vehicle Cargo Racks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Racks > Vehicle Fishing Pole Racks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Racks > Vehicle Gun Racks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Racks > Vehicle Motorcycle & Scooter Racks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Racks > Vehicle Ski & Snowboard Racks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Carrying Racks > Vehicle Water Sport Board Racks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Diffusers & Splitters -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Door Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Doors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Grilles -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Hitches -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Running Boards -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Tow Hooks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Trailer Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motor Vehicle Warning Whips -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motorcycle Fairings -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motorcycle Fenders -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motorcycle Foot Pegs -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motorcycle Handlebars & Grips -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motorcycle Seats -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Motorcycle Shift Levers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Soft Tops -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Spoilers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Sunroofs -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Truck Bed Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Truck Bed Parts > Truck Bed Bars -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Truck Beds -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Winch Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Exterior > Winches -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Automotive Fuses -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Car Seat Cushions -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Car Seats -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Cigar Lighter Receptacles -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Cup Holders -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Dashboard Accessories -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Headliners -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Power Seats -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Sun Shields -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Vehicle Armrests -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Vehicle Backrests -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Vehicle Carpets -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Interior > Vehicle Floor Mats -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Monitoring -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Monitoring > Engine Control Unit Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Monitoring > Fuel Meters -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Monitoring > Motor Vehicle Trip Computers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Monitoring > Odometers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Monitoring > Radar Detectors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Monitoring > Speedometers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Monitoring > Tachometers +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Electronics +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Electronics > Motor Vehicle A/V Players & In-Dash Systems +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Electronics > Motor Vehicle Amplifiers +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Electronics > Motor Vehicle Cassette Adapters +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Electronics > Motor Vehicle Cassette Players +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Electronics > Motor Vehicle Equalizers & Crossovers +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Electronics > Motor Vehicle Parking Cameras +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Electronics > Motor Vehicle Speakerphones +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Electronics > Motor Vehicle Speakers +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Electronics > Motor Vehicle Subwoofers +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Electronics > Motor Vehicle Video Monitor Mounts Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Air Intake Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Air Intake Parts > Vehicle Air Filters -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Air Intake Parts > Vehicle Air Manifolds -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Air Intake Parts > Vehicle Air Pump Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Air Intake Parts > Vehicle Air Pumps -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Brake & Clutch Levers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Batteries -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Batteries > Hybrid Batteries -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Belts Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Braking -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Braking > Parking Brake Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Braking > Vehicle Brake Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Braking > Vehicle Manual Brake Parts +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Carpet & Upholstery Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Climate Control -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Climate Control > Vehicle Air Conditioner Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Climate Control > Vehicle Blower Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Climate Control > Vehicle Defroster Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Climate Control > Vehicle Heater Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Climate Control > Vehicle Temperature Controllers +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Controls +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Oil Circulation Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Alternator Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Alternators -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Camshaft Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Camshafts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Carburetor Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Carburetors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Compressor Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Compressors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Crankshaft Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Crankshafts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Cylinder Heads & Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Engine Gaskets -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Engine Timing Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Engine Timing Parts > Vehicle Engine Timing Belts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Flywheels & Flywheel Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Generator Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Ignition Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Ignition Parts > Capacitor Discharge Ignition Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Ignition Parts > Distributor Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Ignition Parts > Ignition Coils -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Ignition Parts > Spark Plugs -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Piston Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Piston Parts > Gudgeon Pins -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Piston Parts > Piston Rings -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Pistons -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Radiator Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Radiators -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Starter Motor Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Starter Motors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Super Charger Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engine Parts > Vehicle Vacuum Pump Parts +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Engines Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Exhaust -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Exhaust > Catalytic Converters -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Exhaust > Exhaust Gas Recirculation Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Exhaust > Exhaust Manifolds -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Exhaust > Mufflers & Muffler Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Exhaust > Oxygen Sensors & Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Exhaust > Tailpipes & Tailpipe Parts +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Frame & Body Parts Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Fuel Systems -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Fuel Systems > Fuel Injection Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Fuel Systems > Fuel Injectors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Fuel Systems > Fuel Lines & Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Fuel Systems > Fuel Pump Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Fuel Systems > Fuel Pumps -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Fuel Systems > Fuel Screens & Filters -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Fuel Systems > Fuel Tank Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Fuel Systems > Fuel Tanks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Fuel Systems > Nitrous Systems -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Fuel Systems > Vehicle Flow Meters +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Interior Fittings Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Lighting -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Lighting > Exterior Vehicle Light Covers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Lighting > Vehicle Fog Lights -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Lighting > Vehicle Headlights -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Lighting > Vehicle Interior Lighting -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Lighting > Vehicle Light Bulbs -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Lighting > Vehicle Parking Lights -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Lighting > Vehicle Reverse Lights -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Lighting > Vehicle Side Marker Lights -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Lighting > Vehicle Tail & Brake Lights -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Lighting > Vehicle Turn Signal Lights Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Mirrors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Mirrors > Vehicle Rear-View Mirrors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Mirrors > Vehicle Side-View Mirrors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Mirrors > Vehicle Towing Mirrors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Pedals -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Valves & Valve Parts +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Power & Electrical Systems +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Seating +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Sensors & Gauges +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Suspension Parts +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Towing +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Transmission & Drivetrain Parts Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Rims & Wheels -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Rims & Wheels > ATV & UTV Rims & Wheels -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Rims & Wheels > ATV & UTV Rims & Wheels > ATV Rims & Wheels -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Rims & Wheels > ATV & UTV Rims & Wheels > UTV Rims & Wheels Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Rims & Wheels > Automotive Rims & Wheels Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Rims & Wheels > Motorcycle Rims & Wheels +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Rims & Wheels > Off-Road and All-Terrain Vehicle Rims & Wheels +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Tire Accessories Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Tires -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Tires > ATV & UTV Tires -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Tires > ATV & UTV Tires > ATV Tires -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Tires > ATV & UTV Tires > UTV Tires Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Tires > Automotive Tires Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Tires > Motorcycle Tires +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Tires > Off-Road and All-Terrain Vehicle Tires Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Motor Vehicle Wheel Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Tire Accessories -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Wheel Systems > Tire Accessories > Tire Chains -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Window Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Window Parts > Power Window Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Window Parts > Window & Windshield Glass -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Oil Circulation -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Oil Circulation > Oil Filters -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Oil Circulation > Oil Pump Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Oil Circulation > Oil Pumps -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Oil Circulation > Oil Sumps -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Steering Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Steering Parts > King Pins -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Steering Parts > Power Steering Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Steering Parts > Steering Wheel Controls -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Steering Parts > Steering Wheel Controls > Cruise Control Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Steering Parts > Steering Wheel Controls > Headlight Control Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Steering Parts > Steering Wheel Controls > Vehicle Light Dimmers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Steering Parts > Steering Wheel Controls > Wiper Speed Control Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Steering Parts > Steering Wheels -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Suspension Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Suspension Parts > Shock Absorbers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Axle Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Ball Bearings -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Clutch Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Connecting Rods -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Differentials -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Driveshaft Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Driveshafts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Gearbox Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Gearboxes -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Rocker Arm Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Rocker Arm Parts > Rocker Covers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Rocker Arms -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Transfer Case Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Transmission Parts > Transmission Gears -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Vehicle Battery Parts -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Windshield Wipers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Safety Equipment -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Safety Equipment > Vehicle Parking Sensors -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Security & Locking Systems -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Security & Locking Systems > Car Alarm Accessories -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Security & Locking Systems > Car Alarm Systems -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Security & Locking Systems > Child Safety Locks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Security & Locking Systems > Immobilizers -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Security & Locking Systems > Locking Hubs -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Security & Locking Systems > Motor Vehicle Security Cameras -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Security & Locking Systems > Motorcycle Alarms & Locks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Security & Locking Systems > Power Door Locks -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Security & Locking Systems > Remote Keyless Systems -Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Security & Locking Systems > Steering Wheel Locks -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Bags & Panniers -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Protective Gear -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Protective Gear > Motorcycle Chest & Back Protectors -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Protective Gear > Motorcycle Elbow & Wrist Guards -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Protective Gear > Motorcycle Gloves -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Protective Gear > Motorcycle Goggles -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Protective Gear > Motorcycle Helmet Parts & Accessories -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Protective Gear > Motorcycle Helmet Parts & Accessories > Motorcycle Helmet Visors -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Protective Gear > Motorcycle Helmets -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Protective Gear > Motorcycle Kidney Belts -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Protective Gear > Motorcycle Knee & Shin Guards -Vehicles & Parts > Vehicle Parts & Accessories > Motorcycle Accessories > Motorcycle Protective Gear > Motorcycle Neck Braces +Vehicles & Parts > Vehicle Parts & Accessories > Motor Vehicle Parts > Motor Vehicle Window Parts & Accessories +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Portable Fuel Cans +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Cleaning +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Cleaning > Car Wash Brushes +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Cleaning > Car Wash Solutions +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Cleaning > Vehicle Carpet & Upholstery Cleaners +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Cleaning > Vehicle Fuel Injection Cleaning Kits +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Cleaning > Vehicle Glass Cleaners +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Cleaning > Vehicle Waxes, Polishes & Protectants +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers > Golf Cart Enclosures +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers > Motor Vehicle Windshield Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers > Tonneau Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers > Vehicle Hardtops +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers > Vehicle Soft Tops +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers > Vehicle Storage Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers > Vehicle Storage Covers > Automotive Storage Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers > Vehicle Storage Covers > Golf Cart Storage Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers > Vehicle Storage Covers > Motorcycle Storage Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers > Vehicle Storage Covers > Recreational Vehicle Storage Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Covers > Vehicle Storage Covers > Watercraft Storage Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Bumper Stickers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Air Fresheners +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Antenna Balls +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Dashboard Accessories +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Decals +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Decor Accessory Sets +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Display Flags +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Emblems & Hood Ornaments +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Hitch Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle License Plate Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle License Plate Frames +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle License Plate Mounts & Holders +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle License Plates +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Magnets +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Rear View Mirror Ornaments +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Shift Boots +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Shift Knobs +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Steering Wheel Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Decor > Vehicle Wraps +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Antifreeze +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Brake Fluid +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Cooling System Additives +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Engine Degreasers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Fuel System Cleaners +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Greases +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Hydraulic Clutch Fluid +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Motor Oil +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Performance Additives +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Power Steering Fluid +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Transmission Fluid +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Fluids > Vehicle Windshield Fluid +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Paint +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Paint > Motor Vehicle Body Paint +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Paint > Motor Vehicle Brake Caliper Paint +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Repair & Specialty Tools +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Repair & Specialty Tools > Motor Vehicle Brake Service Kits +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Repair & Specialty Tools > Motor Vehicle Clutch Alignment & Removal Tools +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Repair & Specialty Tools > Vehicle Battery Chargers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Repair & Specialty Tools > Vehicle Battery Testers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Repair & Specialty Tools > Vehicle Body Filler +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Repair & Specialty Tools > Vehicle Diagnostic Scanners +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Repair & Specialty Tools > Vehicle Jump Starters +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Repair & Specialty Tools > Vehicle Jumper Cables +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Repair & Specialty Tools > Vehicle Tire Repair & Tire Changing Tools +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Maintenance, Care & Decor > Vehicle Repair & Specialty Tools > Windshield Repair Kits +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Motorcycle Protective Gear +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Motorcycle Protective Gear > Motorcycle Chest & Back Protectors +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Motorcycle Protective Gear > Motorcycle Elbow & Wrist Guards +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Motorcycle Protective Gear > Motorcycle Gloves +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Motorcycle Protective Gear > Motorcycle Goggles +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Motorcycle Protective Gear > Motorcycle Hand Guards +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Motorcycle Protective Gear > Motorcycle Helmet Parts & Accessories +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Motorcycle Protective Gear > Motorcycle Helmets +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Motorcycle Protective Gear > Motorcycle Kidney Belts +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Motorcycle Protective Gear > Motorcycle Knee & Shin Guards +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Motorcycle Protective Gear > Motorcycle Neck Braces +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Off-Road & All-Terrain Vehicle Protective Gear +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Off-Road & All-Terrain Vehicle Protective Gear > ATV & UTV Bar Pads +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Automotive Alarm Accessories +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Automotive Alarm Systems +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Motorcycle Alarms & Locks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Vehicle Door Locks & Parts +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Vehicle Door Locks & Parts > Vehicle Door Lock Actuators +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Vehicle Door Locks & Parts > Vehicle Door Lock Knobs +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Vehicle Door Locks & Parts > Vehicle Door Locks & Locking Systems +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Vehicle Hitch Locks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Vehicle Immobilizers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Vehicle Remote Keyless Systems +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Vehicle Steering Wheel Locks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Alarms & Locks > Vehicle Wheel Clamps +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Safety Equipment +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Safety Equipment > Car Window Nets +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Safety Equipment > Emergency Road Flares +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Safety Equipment > Motor Vehicle Airbag Parts +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Safety Equipment > Motor Vehicle Roll Cages & Bars +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Safety Equipment > Vehicle Seat Belt Buckles +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Safety Equipment > Vehicle Seat Belt Covers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Safety Equipment > Vehicle Seat Belt Straps +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Safety Equipment > Vehicle Seat Belts +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Safety Equipment > Vehicle Warning Whips +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Safety & Security > Vehicle Safety Equipment > Vehicle Wheel Chocks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Cargo Nets +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Rack Accessories +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Rack Accessories > Vehicle Bicycle Rack Accessories +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Rack Accessories > Vehicle Ski & Snowboard Rack Accessories +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Racks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Racks > Vehicle Base Rack Systems +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Racks > Vehicle Bicycle Racks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Racks > Vehicle Boat Racks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Racks > Vehicle Cargo Racks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Racks > Vehicle Fishing Rod Racks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Racks > Vehicle Gun Racks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Racks > Vehicle Motorcycle & Scooter Racks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Racks > Vehicle Ski & Snowboard Racks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Carrying Racks > Vehicle Water Sport Board Racks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Loading Ramps +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Trailers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Trailers > Boat Trailers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Trailers > Horse & Livestock Trailers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Trailers > Travel Trailers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motor Vehicle Trailers > Utility & Cargo Trailers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Motorcycle Bags & Panniers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Truck Bed Storage Boxes & Organizers +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Vehicle Headrest Hangers & Hooks +Vehicles & Parts > Vehicle Parts & Accessories > Vehicle Storage & Cargo > Vehicle Organizers Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Docking & Anchoring Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Docking & Anchoring > Anchor Chains @@ -6190,7 +5385,6 @@ Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Sailboat Parts Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Watercraft Care Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Watercraft Care > Watercraft Cleaners -Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Watercraft Care > Watercraft Covers Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Watercraft Care > Watercraft Polishes Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Watercraft Engine Parts Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Watercraft Engine Parts > Watercraft Alternators @@ -6215,3 +5409,19 @@ Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Watercraft Steering Parts Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Watercraft Steering Parts > Watercraft Steering Cables Vehicles & Parts > Vehicle Parts & Accessories > Watercraft Parts & Accessories > Watercraft Steering Parts > Watercraft Steering Wheels +Vehicles & Parts > Vehicles +Vehicles & Parts > Vehicles > Aircraft +Vehicles & Parts > Vehicles > Motor Vehicles +Vehicles & Parts > Vehicles > Motor Vehicles > Cars, Trucks & Vans +Vehicles & Parts > Vehicles > Motor Vehicles > Golf Carts +Vehicles & Parts > Vehicles > Motor Vehicles > Motorcycles & Scooters +Vehicles & Parts > Vehicles > Motor Vehicles > Off-Road and All-Terrain Vehicles +Vehicles & Parts > Vehicles > Motor Vehicles > Off-Road and All-Terrain Vehicles > ATVs & UTVs +Vehicles & Parts > Vehicles > Motor Vehicles > Off-Road and All-Terrain Vehicles > Go Karts & Dune Buggies +Vehicles & Parts > Vehicles > Motor Vehicles > Recreational Vehicles +Vehicles & Parts > Vehicles > Motor Vehicles > Snowmobiles +Vehicles & Parts > Vehicles > Watercraft +Vehicles & Parts > Vehicles > Watercraft > Motor Boats +Vehicles & Parts > Vehicles > Watercraft > Personal Watercraft +Vehicles & Parts > Vehicles > Watercraft > Sailboats +Vehicles & Parts > Vehicles > Watercraft > Yachts \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/FroogleFeedPlugin.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/FroogleFeedPlugin.cs deleted file mode 100644 index 478cd9cd2d..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/FroogleFeedPlugin.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Data.Entity.Migrations; -using System.Web.Routing; -using SmartStore.Core.Plugins; -using SmartStore.GoogleMerchantCenter.Data.Migrations; -using SmartStore.GoogleMerchantCenter.Services; -using SmartStore.Services.Configuration; -using SmartStore.Services.Localization; - -namespace SmartStore.GoogleMerchantCenter -{ - public class FroogleFeedPlugin : BasePlugin, IConfigurable - { - private readonly IGoogleFeedService _googleService; - private readonly ISettingService _settingService; - private readonly ILocalizationService _localizationService; - - public FroogleFeedPlugin( - IGoogleFeedService googleService, - ISettingService settingService, - ILocalizationService localizationService) - { - _googleService = googleService; - _settingService = settingService; - _localizationService = localizationService; - } - - /// - /// 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 = "FeedFroogle"; - routeValues = new RouteValueDictionary() { { "Namespaces", "SmartStore.GoogleMerchantCenter.Controllers" }, { "area", "SmartStore.GoogleMerchantCenter" } }; - } - - /// - /// Install plugin - /// - public override void Install() - { - var settings = new FroogleSettings(); - settings.CurrencyId = _googleService.Helper.CurrencyID; - - _settingService.SaveSetting(settings); - - _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); - - _googleService.Helper.InsertScheduleTask(); - - base.Install(); - } - - /// - /// Uninstall plugin - /// - public override void Uninstall() - { - _googleService.Helper.DeleteFeedFiles(); - _googleService.Helper.DeleteScheduleTask(); - - _settingService.DeleteSetting(); - - _localizationService.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); - - var migrator = new DbMigrator(new Configuration()); - migrator.Update(DbMigrator.InitialDatabase); - - base.Uninstall(); - } - } -} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/FroogleSettings.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/FroogleSettings.cs deleted file mode 100644 index f65f63ecef..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/FroogleSettings.cs +++ /dev/null @@ -1,39 +0,0 @@ -using SmartStore.Core.Configuration; -using SmartStore.Utilities; -using SmartStore.Web.Framework.Plugins; - -namespace SmartStore.GoogleMerchantCenter -{ - public class FroogleSettings : PromotionFeedSettings, ISettings - { - public FroogleSettings() - { - ProductPictureSize = 125; - StaticFileName = "google_merchant_center_{0}.xml".FormatWith(CommonHelper.GenerateRandomDigitCode(10)); - Condition = "new"; - OnlineOnly = true; - AdditionalImages = true; - SpecialPrice = true; - } - - public string DefaultGoogleCategory { get; set; } - public string Condition { get; set; } - public bool SpecialPrice { get; set; } - public string Gender { get; set; } - public string AgeGroup { get; set; } - public string Color { get; set; } - public string Size { get; set; } - public string Material { get; set; } - public string Pattern { get; set; } - public bool OnlineOnly { get; set; } - public int ExpirationDays { get; set; } - public bool ExportShipping { get; set; } - public bool ExportBasePrice { get; set; } - - public string AppendDescriptionText1 { get; set; } - public string AppendDescriptionText2 { get; set; } - public string AppendDescriptionText3 { get; set; } - public string AppendDescriptionText4 { get; set; } - public string AppendDescriptionText5 { get; set; } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs new file mode 100644 index 0000000000..3808bca487 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs @@ -0,0 +1,64 @@ +using System.Data.Entity.Migrations; +using System.Web.Routing; +using SmartStore.Core.Plugins; +using SmartStore.GoogleMerchantCenter.Data.Migrations; +using SmartStore.GoogleMerchantCenter.Services; +using SmartStore.Services; + +namespace SmartStore.GoogleMerchantCenter +{ + public class GoogleMerchantCenterFeedPlugin : BasePlugin, IConfigurable + { + private readonly IGoogleFeedService _googleFeedService; + private readonly ICommonServices _services; + + public GoogleMerchantCenterFeedPlugin( + IGoogleFeedService googleFeedService, + ICommonServices services) + { + _googleFeedService = googleFeedService; + _services = services; + } + + public static string SystemName + { + get { return "SmartStore.GoogleMerchantCenter"; } + } + + /// + /// 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 = "FeedGoogleMerchantCenter"; + routeValues = new RouteValueDictionary() { { "Namespaces", "SmartStore.GoogleMerchantCenter.Controllers" }, { "area", SystemName } }; + } + + /// + /// Install plugin + /// + public override void Install() + { + _services.Localization.ImportPluginResourcesFromXml(this.PluginDescriptor); + + base.Install(); + } + + /// + /// Uninstall plugin + /// + public override void Uninstall() + { + _services.Localization.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); + + 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 b72e213a61..17b90e856c 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml @@ -1,128 +1,50 @@  - Google Merchant Center (GMC) Feed + Google Merchant Center Feed - + + Ermöglicht den Export von Produktdaten im Feed-Format von Google Merchant Center (GMC). + + + Google Merchant Center XML Feed + + + Ermöglicht den Export von Produktdaten im XML Feed-Format von Google Merchant Center (GMC). + + +
  • 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.
  • ]]>
    - - - hier.]]> - - Standard-Google-Kategorie fehlt - - Währung - - - Standardwährung fürs Erstellen des Feeds. - Standard-Google-Kategorie Wird verwendet, falls auf Produktebene keine Kategorie angegeben ist. - - Allgemein - - - Produktspezifische Daten - - - Feed erstellen - - - Thumbnail-Größe Produktbild - - - Die Standardgröße (in Pixel) für das Produkt-Thumbnail. - Produkt Google-Kategorie - - Geschlecht - - - Altersgruppe - - - Farbe - - - Größe - - - Material - - - Muster - - - Feed automatisch erstellen - - - Aktivieren Sie diese Option, falls die Feed-Datei automatisch erstellt werden soll. - - - Zeitspanne (in Minuten) - - - Die Zeitspanne nach der die Feed-Datei automatisch erstellt werden soll. - - - Einstellungen wurden erfolgreich gespeichert. Starten Sie die Anwendung bitte neu, falls "Feed automatisch erstellen" geändert wurde. - - - Artikelbeschreibung - - - Legen Sie fest, welche Informationen zur Beschreibung des Artikel wie verwendet werden sollen. - - - Automatisch - - - Kurzbeschreibung - - - Detailbeschreibung - - - Produktname + Kurzbeschreibung - - - Produktname + Detailbeschreibung - - - Hersteller + Produktname + Kurzbeschreibung - - - Hersteller + Produktname + Detailbeschreibung - - - Anzuhängender Text 1 - 5 - - - Einer der fünf Textbausteine wird an die Artikelbeschreibung angehängt, falls Kurz- und Langbeschreibung leer sind. - + + Legt die dem Artikel entsprechende Google-Kategorie fest. Zwingend erforderlich. + Zusätzliche Bilder - Aktivieren Sie diese Option, falls Sie mehr als ein Produktbild exportieren möchten. Bis zu 10 sind möglich. + Legt fest, ob mehr als ein Produktbild exportiert werden soll. Bis zu 10 sind möglich. Zustand @@ -148,9 +70,6 @@ Vorrätig - - Bestellbar - Vergriffen @@ -161,19 +80,7 @@ Aktionspreis exportieren - Aktivieren Sie diese Option, falls Sie den Aktionspreis inkl. Aktionszeitraum exportieren möchten. - - - Hersteller\Marke - - - Dieser Wert wird verwendet, wenn für ein Produkt kein Hersteller bzw. Marke hinterlegt ist. - - - Eigene Artikelnummer - - - Aktivieren Sie diese Option, falls die eigene Artikelnummer als Hersteller-Artikelnummer (MPN) exportiert werden soll, sofern diese fehlt. + Legt fest, ob ein Aktionspreis inkl. Aktionszeitraum exportiert werden soll, sofern ein solcher für das jeweilige Produkt existiert. Geschlecht @@ -226,18 +133,6 @@ Muster oder grafisches Druckdesign eines Produktes. Angabe ist z.B. bei Bekleidung sinnvoll. - - Nur online zu kaufen - - - Aktivieren Sie diese Option, wenn Produkte nur online erworben werden können. Aktivieren Sie diese Option nicht, falls Produkte auch in Ihrem Ladengeschäft erhältlich sind. - - - HTML entfernen - - - Aktivieren Sie diese Option, falls für den Export alle HTML-Auszeichnungen aus der Artikelbeschreibung entfernt werden sollen. - Leer @@ -259,12 +154,6 @@ Noch nicht bearbeitet - - Shop - - - Wählen Sie den Shop, für den der Feed erstellt werden soll. - Verfällt in Tagen @@ -278,24 +167,69 @@ Versanddaten exportieren - Aktivieren Sie diese Option, wenn Sie z.B. das Gewicht des Produkt exportieren möchten. + Legt fest, ob das Gewicht eines Produktes exportiert werden soll. Grundpreis exportieren - Aktivieren Sie diese Option, wenn Sie denGrundpreis des Produkt exportieren möchten. - - - Netto- in Bruttopreise umrechnen - - - Legt fest, dass Netto- in Bruttopreise umgerechnet werden sollen. - - - Sprache - - - Legt die Sprache fest, in der sprachabhängige Werte (z.B. der Produktname) exportiert werden sollen. + Legt fest, ob der Grundpreis eines Produktes exportiert werden soll. + + Multipack + + + Anzahl identischer Produkte in einem händlerdefinierten Multipack. Muss größer 1 sein. + + + Bundle + + + Händlerdefiniertes Produktpaket bestehend aus einem Haupt- und mehreren Zubehörartikel bzw. Add-ons. + + + Nicht jugendfrei + + + Produkt, welches nur für erwachsene Nutzer bestimmt ist. + + + Energieeffizienz + + + Die Energieeffizienzklasse des Produktes gemäß EU-Richtlinie 2010/30/EU. Mögliche Werte sind G, F, E, D, C, B, A, A+, A++, A+++. + + + A+++,A++,A+,A,B,C,D,E,F,G + + + Label 0 + + + Benutzerdefiniertes Label 0. Dient der individuellen Gruppierung von Produkten. + + + Label 1 + + + Benutzerdefiniertes Label 1. Dient der individuellen Gruppierung von Produkten. + + + Label 2 + + + Benutzerdefiniertes Label 2. Dient der individuellen Gruppierung von Produkten. + + + Label 3 + + + Benutzerdefiniertes Label 3. Dient der individuellen Gruppierung von Produkten. + + + Label 4 + + + Benutzerdefiniertes Label 4. Dient der individuellen Gruppierung von Produkten. +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml index fa68704f19..3b75a8c306 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml @@ -1,128 +1,50 @@  - Google Merchant Center (GMC) Feed + Google Merchant Center feed + + Allows you to export product data in the feed format of Google Merchant Center (GMC). + + + Google Merchant Center XML feed + + + Allows to export product data in XML feed format of Google Merchant Center (GMC). + +
  • 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.
  • ]]>
    - - - here.]]> - - Default Google category is not set - - Currency - - - The default currency that will be used to generate the feed. - Default Google category Will be used if there is no category specified on produkt level. - - General - - - Product specific data - - - Generate feed - - - Product thumbnail image size - - - The default size (pixels) for product thumbnail image. - Product Google Category - - Gender - - - Age group - - - Color - - - Size - - - Material - - - Pattern - - - Automatically generate feed - - - Check if you want the feed to be automatically generated. - - - A task period (minutes) - - - Specify a task period in minutes (generation of a new feed file). - - - Settings successfully saved. Please restart the application if "Automatically generate feed" has been changed. - - - Product description - - - Specify what information to use for the description of the product. - - - Automatic - - - Short description - - - Long description - - - Product name + short description - - - Product name + long description - - - Manufacturer + Product name + short description - - - Manufacturer + Product name + long description - - - Text 1 - 5 to be appended - - - One of the five text elements will be appended to the product description if long and short description are empty. - + + Specifies the Google category corresponding to the product. Mandatory. + Additional images - Check if you want to export more than one image. Up to 10 are possible. + Check the box if you want to export more than one image. Up to 10 are possible. Condition @@ -148,9 +70,6 @@ In stock - - Available for order - Out of stock @@ -163,18 +82,6 @@ Activate this option if you want to export special price and special price period. - - Manufacturer\Brand - - - This value is used if there's no manufacturer\brand for a product. - - - Own SKU - - - Check to export SKU as manufacturer part number (MPN) if it is missing. - Gender @@ -226,18 +133,6 @@ The pattern or graphic print featured on a product. Usefull for clothes for instance. - - Only online available - - - Check if products can only be purchased online. - - - Remove HTML - - - Check if you want to remove all HTML from the product description for the export. - Empty @@ -259,12 +154,6 @@ Not edited yet - - Store - - - Select the store that will be used to generate the feed. - Expires in days @@ -286,16 +175,61 @@ Activate this option if you want to export the base price of the product. - - Convert net into gross prices - - - Determines to convert net into gross prices. - - - Language - - - Determines which language to use to export language dependent values (for instance the product name). - + + Multipack + + + Number of identical products in a merchant defined multipack. Must be greater than 1. + + + Bundle + + + Merchant defined product package consisting of one main and several accessories or add-ons. + + + Adult + + + Product, which is intended only for adult users. + + + Energy efficiency + + + The energy efficiency class of the product in accordance with EU directive 2010/30/EU. Possible values are G, F, E, D, C, B, A, A +, A++, A+++. + + + A+++,A++,A+,A,B,C,D,E,F,G + + + Label 0 + + + Custom label 0 serves the individual grouping of products. + + + Label 1 + + + Custom label 1 serves the individual grouping of products. + + + Label 2 + + + Custom label 2 serves the individual grouping of products. + + + Label 3 + + + Custom label 3 serves the individual grouping of products. + + + Label 4 + + + Custom label 4 serves the individual grouping of products. +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedFroogleModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedFroogleModel.cs deleted file mode 100644 index c11a6767e5..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedFroogleModel.cs +++ /dev/null @@ -1,258 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Web.Mvc; -using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; -using Newtonsoft.Json; -using SmartStore.Web.Framework.Plugins; -using SmartStore.Core.Domain.Catalog; -using FluentValidation.Attributes; -using SmartStore.GoogleMerchantCenter.Validators; - -namespace SmartStore.GoogleMerchantCenter.Models -{ - [Validator(typeof(ConfigurationValidator))] - public class FeedFroogleModel : PromotionFeedConfigModel - { - public string GridEditUrl { get; set; } - public int GridPageSize { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.ProductPictureSize")] - public int ProductPictureSize { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Currency")] - public int CurrencyId { get; set; } - public List AvailableCurrencies { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.DefaultGoogleCategory")] - public string DefaultGoogleCategory { get; set; } - - public string[] AvailableGoogleCategories { get; set; } - public string AvailableGoogleCategoriesAsJson - { - get - { - if (AvailableGoogleCategories != null && AvailableGoogleCategories.Length > 0) - return JsonConvert.SerializeObject(AvailableGoogleCategories); - return ""; - } - } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.TaskEnabled")] - public bool TaskEnabled { get; set; } - [SmartResourceDisplayName("Plugins.Feed.Froogle.GenerateStaticFileEachMinutes")] - public int GenerateStaticFileEachMinutes { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.BuildDescription")] - public string BuildDescription { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.AppendDescriptionText")] - public string AppendDescriptionText1 { get; set; } - public string AppendDescriptionText2 { get; set; } - public string AppendDescriptionText3 { get; set; } - public string AppendDescriptionText4 { get; set; } - public string AppendDescriptionText5 { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.AdditionalImages")] - public bool AdditionalImages { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Condition")] - public string Condition { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Availability")] - public string Availability { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.SpecialPrice")] - public bool SpecialPrice { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Brand")] - public string Brand { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.UseOwnProductNo")] - public bool UseOwnProductNo { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Gender")] - public string Gender { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.AgeGroup")] - public string AgeGroup { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Color")] - public string Color { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Size")] - public string Size { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Material")] - public string Material { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Pattern")] - public string Pattern { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.OnlineOnly")] - public bool OnlineOnly { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.DescriptionToPlainText")] - public bool DescriptionToPlainText { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.SearchProductName")] - public string SearchProductName { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.SearchIsTouched")] - public string SearchIsTouched { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Store")] - public int StoreId { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.ExpirationDays")] - public int ExpirationDays { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.ExportShipping")] - public bool ExportShipping { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.ExportBasePrice")] - public bool ExportBasePrice { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.ConvertNetToGrossPrices")] - public bool ConvertNetToGrossPrices { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.LanguageId")] - public int LanguageId { get; set; } - - public void Copy(FroogleSettings settings, bool fromSettings) - { - if (fromSettings) - { - AppendDescriptionText1 = settings.AppendDescriptionText1; - AppendDescriptionText2 = settings.AppendDescriptionText2; - AppendDescriptionText3 = settings.AppendDescriptionText3; - AppendDescriptionText4 = settings.AppendDescriptionText4; - AppendDescriptionText5 = settings.AppendDescriptionText5; - ProductPictureSize = settings.ProductPictureSize; - CurrencyId = settings.CurrencyId; - DefaultGoogleCategory = settings.DefaultGoogleCategory; - BuildDescription = settings.BuildDescription; - AdditionalImages = settings.AdditionalImages; - Condition = settings.Condition; - Availability = settings.Availability; - SpecialPrice = settings.SpecialPrice; - Brand = settings.Brand; - UseOwnProductNo = settings.UseOwnProductNo; - Gender = settings.Gender; - AgeGroup = settings.AgeGroup; - Color = settings.Color; - Size = settings.Size; - Material = settings.Material; - Pattern = settings.Pattern; - OnlineOnly = settings.OnlineOnly; - DescriptionToPlainText = settings.DescriptionToPlainText; - StoreId = settings.StoreId; - ExpirationDays = settings.ExpirationDays; - ExportShipping = settings.ExportShipping; - ExportBasePrice = settings.ExportBasePrice; - ConvertNetToGrossPrices = settings.ConvertNetToGrossPrices; - LanguageId = settings.LanguageId; - } - else - { - settings.AppendDescriptionText1 = AppendDescriptionText1; - settings.AppendDescriptionText2 = AppendDescriptionText2; - settings.AppendDescriptionText3 = AppendDescriptionText3; - settings.AppendDescriptionText4 = AppendDescriptionText4; - settings.AppendDescriptionText5 = AppendDescriptionText5; - settings.ProductPictureSize = ProductPictureSize; - settings.CurrencyId = CurrencyId; - settings.DefaultGoogleCategory = DefaultGoogleCategory; - settings.BuildDescription = BuildDescription; - settings.AdditionalImages = AdditionalImages; - settings.Condition = Condition; - settings.Availability = Availability; - settings.SpecialPrice = SpecialPrice; - settings.Brand = Brand; - settings.UseOwnProductNo = UseOwnProductNo; - settings.Gender = Gender; - settings.AgeGroup = AgeGroup; - settings.Color = Color; - settings.Size = Size; - settings.Material = Material; - settings.Pattern = Pattern; - settings.OnlineOnly = OnlineOnly; - settings.DescriptionToPlainText = DescriptionToPlainText; - settings.StoreId = StoreId; - settings.ExpirationDays = ExpirationDays; - settings.ExportShipping = ExportShipping; - settings.ExportBasePrice = ExportBasePrice; - settings.ConvertNetToGrossPrices = ConvertNetToGrossPrices; - settings.LanguageId = LanguageId; - } - } - } - - - public class GoogleProductModel : ModelBase - { - public int TotalCount { get; set; } - - //this attribute is required to disable editing - [ScaffoldColumn(false)] - public int ProductId - { - get { return Id; } - set { Id = value; } - } - public int Id { get; set; } - - //this attribute is required to disable editing - [ReadOnly(true)] - [ScaffoldColumn(false)] - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.ProductName")] - public string Name { get; set; } - - public string SKU { get; set; } - public int ProductTypeId { get; set; } - public ProductType ProductType { get { return (ProductType)ProductTypeId; } } - public string ProductTypeName { get; set; } - public string ProductTypeLabelHint - { - get - { - switch (ProductType) - { - case ProductType.SimpleProduct: - return "smnet-hide"; - case ProductType.GroupedProduct: - return "success"; - case ProductType.BundledProduct: - return "info"; - default: - return ""; - } - } - } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.GoogleCategory")] - public string Taxonomy { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Gender")] - public string Gender { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.AgeGroup")] - public string AgeGroup { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Color")] - public string Color { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Size")] - public string Size { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Material")] - public string Material { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Pattern")] - public string Pattern { get; set; } - - public string GenderLocalize { get; set; } - public string AgeGroupLocalize { get; set; } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs new file mode 100644 index 0000000000..36c5f02799 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs @@ -0,0 +1,141 @@ +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; + +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[] EnergyEfficiencyClasses { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.SearchProductName")] + public string SearchProductName { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.SearchIsTouched")] + public string SearchIsTouched { get; set; } + } + + public class GoogleProductModel : ModelBase + { + public int TotalCount { get; set; } + + //this attribute is required to disable editing + [ScaffoldColumn(false)] + public int ProductId + { + get { return Id; } + set { Id = value; } + } + public int Id { get; set; } + + //this attribute is required to disable editing + [ReadOnly(true)] + [ScaffoldColumn(false)] + [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.ProductName")] + public string Name { get; set; } + + public string SKU { get; set; } + public int ProductTypeId { get; set; } + public ProductType ProductType { get { return (ProductType)ProductTypeId; } } + public string ProductTypeName { get; set; } + public string ProductTypeLabelHint + { + get + { + switch (ProductType) + { + case ProductType.SimpleProduct: + return "smnet-hide"; + case ProductType.GroupedProduct: + return "success"; + case ProductType.BundledProduct: + return "info"; + default: + return ""; + } + } + } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.GoogleCategory")] + public string Taxonomy { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Gender")] + public string Gender { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.AgeGroup")] + public string AgeGroup { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Color")] + public string Color { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Size")] + public string Size { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Material")] + public string Material { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Pattern")] + public string Pattern { get; set; } + + [SmartResourceDisplayName("Common.Export")] + public int Export { get; set; } + [SmartResourceDisplayName("Common.Export")] + public bool Export2 + { + get { return Export != 0; } + set { Export = (value ? 1 : 0); } + } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Multipack")] + public int Multipack { get; set; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.Multipack")] + public int? Multipack2 + { + get { return Multipack > 0 ? Multipack : (int?)null; } + set { Multipack = (value ?? 0); } + } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.IsBundle")] + public bool? IsBundle { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.IsAdult")] + public bool? IsAdult { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.EnergyEfficiencyClass")] + public string EnergyEfficiencyClass { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.CustomLabel0")] + public string CustomLabel0 { get; set; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.CustomLabel1")] + public string CustomLabel1 { get; set; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.CustomLabel2")] + public string CustomLabel2 { get; set; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.CustomLabel3")] + public string CustomLabel3 { get; set; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.CustomLabel4")] + public string CustomLabel4 { get; set; } + + public string GenderLocalize { get; set; } + public string AgeGroupLocalize { get; set; } + public string Export2Localize { get; set; } + public string IsBundleLocalize { get; set; } + public string IsAdultLocalize { get; set; } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs new file mode 100644 index 0000000000..69d81d50dd --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs @@ -0,0 +1,77 @@ +using System; +using System.Xml.Serialization; +using FluentValidation.Attributes; +using Newtonsoft.Json; +using SmartStore.GoogleMerchantCenter.Validators; +using SmartStore.Web.Framework; + +namespace SmartStore.GoogleMerchantCenter.Models +{ + [Serializable] + [Validator(typeof(ProfileConfigurationValidator))] + public class ProfileConfigurationModel + { + public ProfileConfigurationModel() + { + Condition = "new"; + AdditionalImages = true; + SpecialPrice = true; + } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.DefaultGoogleCategory")] + public string DefaultGoogleCategory { get; set; } + + [XmlIgnore] + public string[] AvailableGoogleCategories { get; set; } + + [XmlIgnore] + public string AvailableGoogleCategoriesAsJson + { + get + { + if (AvailableGoogleCategories != null && AvailableGoogleCategories.Length > 0) + return JsonConvert.SerializeObject(AvailableGoogleCategories); + return ""; + } + } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.AdditionalImages")] + public bool AdditionalImages { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Condition")] + public string Condition { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Availability")] + public string Availability { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.SpecialPrice")] + public bool SpecialPrice { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Gender")] + public string Gender { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.AgeGroup")] + public string AgeGroup { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Color")] + public string Color { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Size")] + public string Size { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Material")] + public string Material { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Pattern")] + public string Pattern { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.ExpirationDays")] + public int ExpirationDays { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.ExportShipping")] + public bool ExportShipping { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.ExportBasePrice")] + public bool ExportBasePrice { get; set; } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs new file mode 100644 index 0000000000..3edd31e361 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -0,0 +1,439 @@ +using System; +using System.Linq; +using System.Xml; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; +using SmartStore.GoogleMerchantCenter.Models; +using SmartStore.GoogleMerchantCenter.Services; +using SmartStore.Services.DataExchange.Export; +using SmartStore.Services.Directory; + +namespace SmartStore.GoogleMerchantCenter.Providers +{ + [SystemName("Feeds.GoogleMerchantCenterProductXml")] + [FriendlyName("Google Merchant Center XML product feed")] + [DisplayOrder(1)] + [ExportFeatures(Features = + ExportFeatures.CreatesInitialPublicDeployment | + ExportFeatures.CanOmitGroupedProducts | + ExportFeatures.CanProjectAttributeCombinations | + ExportFeatures.CanProjectDescription | + ExportFeatures.UsesSkuAsMpnFallback | + ExportFeatures.OffersBrandFallback | + ExportFeatures.CanIncludeMainPicture | + ExportFeatures.UsesSpecialPrice)] + 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 MeasureSettings _measureSettings; + + public GmcXmlExportProvider( + IGoogleFeedService googleFeedService, + IMeasureService measureService, + MeasureSettings measureSettings) + { + _googleFeedService = googleFeedService; + _measureService = measureService; + _measureSettings = measureSettings; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + private string BasePriceUnits(string value) + { + const string defaultValue = "kg"; + + if (value.IsEmpty()) + return defaultValue; + + // TODO: Product.BasePriceMeasureUnit should be localized + switch (value.ToLowerInvariant()) + { + case "mg": + case "milligramm": + case "milligram": + return "mg"; + case "g": + case "gramm": + case "gram": + return "g"; + case "kg": + case "kilogramm": + case "kilogram": + return "kg"; + + case "ml": + case "milliliter": + case "millilitre": + return "ml"; + case "cl": + case "zentiliter": + case "centilitre": + return "cl"; + case "l": + case "liter": + case "litre": + return "l"; + case "cbm": + case "kubikmeter": + case "cubic metre": + return "cbm"; + + case "cm": + case "zentimeter": + case "centimetre": + return "cm"; + case "m": + case "meter": + return "m"; + + case "qm²": + case "quadratmeter": + case "square metre": + return "sqm"; + + default: + return defaultValue; + } + } + + private bool BasePriceSupported(int baseAmount, string unit) + { + if (baseAmount == 1 || baseAmount == 10 || baseAmount == 100) + return true; + + if (baseAmount == 75 && unit == "cl") + return true; + + if ((baseAmount == 50 || baseAmount == 1000) && unit == "kg") + return true; + + return false; + } + + public static string SystemName + { + get { return "Feeds.GoogleMerchantCenterProductXml"; } + } + + public static string Unspecified + { + get { return "__nospec__"; } + } + + public override ExportConfigurationInfo ConfigurationInfo + { + get + { + return new ExportConfigurationInfo + { + PartialViewName = "~/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml", + ModelType = typeof(ProfileConfigurationModel), + Initialize = obj => + { + var model = (obj as ProfileConfigurationModel); + + model.AvailableGoogleCategories = _googleFeedService.GetTaxonomyList(); + } + }; + } + } + + public override string FileExtension + { + get { return "XML"; } + } + + protected override void Export(ExportExecuteContext context) + { + dynamic currency = context.Currency; + string measureWeightSystemKey = ""; + var dateFormat = "yyyy-MM-ddTHH:mmZ"; + + var measureWeight = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); + + if (measureWeight != null) + measureWeightSystemKey = measureWeight.SystemKeyword; + + var config = (context.ConfigurationData as ProfileConfigurationModel) ?? new ProfileConfigurationModel(); + + using (var writer = XmlWriter.Create(context.DataStream, ExportXmlHelper.DefaultSettings)) + { + writer.WriteStartDocument(); + writer.WriteStartElement("rss"); + writer.WriteAttributeString("version", "2.0"); + writer.WriteAttributeString("xmlns", "g", null, _googleNamespace); + writer.WriteStartElement("channel"); + writer.WriteElementString("title", "{0} - Feed for Google Merchant Center".FormatInvariant((string)context.Store.Name)); + writer.WriteElementString("link", "http://base.google.com/base/"); + writer.WriteElementString("description", "Information about products"); + + while (context.Abort == DataExchangeAbortion.None && context.DataSegmenter.ReadNextSegment()) + { + var segment = context.DataSegmenter.CurrentSegment; + + int[] productIds = segment.Select(x => (int)((dynamic)x).Id).ToArray(); + var googleProducts = _googleFeedService.GetGoogleProductRecords(productIds); + + foreach (dynamic product in segment) + { + if (context.Abort != DataExchangeAbortion.None) + break; + + Product entity = product.Entity; + var gmc = googleProducts.FirstOrDefault(x => x.ProductId == entity.Id); + + if (gmc != null && !gmc.Export) + continue; + + writer.WriteStartElement("item"); + + try + { + string category = (gmc == null ? null : gmc.Taxonomy); + string productType = product._CategoryPath; + string mainImageUrl = product._MainPictureUrl; + var price = (decimal)product.Price; + var uniqueId = (string)product._UniqueId; + string brand = product._Brand; + string gtin = product.Gtin; + string mpn = product.ManufacturerPartNumber; + string condition = "new"; + string availability = "in stock"; + + var specialPrice = product._FutureSpecialPrice as decimal?; + if (!specialPrice.HasValue) + specialPrice = product._SpecialPrice; + + if (category.IsEmpty()) + category = config.DefaultGoogleCategory; + + if (category.IsEmpty()) + context.Log.Error(T("Plugins.Feed.Froogle.MissingDefaultCategory")); + + if (config.Condition.IsCaseInsensitiveEqual(Unspecified)) + { + condition = ""; + } + else if (config.Condition.HasValue()) + { + condition = config.Condition; + } + + if (config.Availability.IsCaseInsensitiveEqual(Unspecified)) + { + availability = ""; + } + else if (config.Availability.HasValue()) + { + availability = config.Availability; + } + else + { + if (entity.ManageInventoryMethod == ManageInventoryMethod.ManageStock && entity.StockQuantity <= 0) + { + if (entity.BackorderMode == BackorderMode.NoBackorders) + availability = "out of stock"; + else if (entity.BackorderMode == BackorderMode.AllowQtyBelow0 || entity.BackorderMode == BackorderMode.AllowQtyBelow0AndNotifyCustomer) + availability = (entity.AvailableForPreOrder ? "preorder" : "out of stock"); + } + } + + writer.WriteElementString("g", "id", _googleNamespace, uniqueId); + + writer.WriteStartElement("title"); + writer.WriteCData(((string)product.Name).Truncate(70).RemoveInvalidXmlChars()); + writer.WriteEndElement(); + + writer.WriteStartElement("description"); + writer.WriteCData(((string)product.FullDescription).RemoveInvalidXmlChars()); + writer.WriteEndElement(); + + writer.WriteStartElement("g", "google_product_category", _googleNamespace); + writer.WriteCData(category.RemoveInvalidXmlChars()); + writer.WriteFullEndElement(); + + if (productType.HasValue()) + { + writer.WriteStartElement("g", "product_type", _googleNamespace); + writer.WriteCData(productType.RemoveInvalidXmlChars()); + writer.WriteFullEndElement(); + } + + writer.WriteElementString("link", (string)product._DetailUrl); + + if (mainImageUrl.HasValue()) + { + writer.WriteElementString("g", "image_link", _googleNamespace, mainImageUrl); + } + + if (config.AdditionalImages) + { + var imageCount = 0; + foreach (dynamic productPicture in product.ProductPictures) + { + string pictureUrl = productPicture.Picture._ImageUrl; + if (pictureUrl.HasValue() && (mainImageUrl.IsEmpty() || !mainImageUrl.IsCaseInsensitiveEqual(pictureUrl)) && ++imageCount <= 10) + { + writer.WriteElementString("g", "additional_image_link", _googleNamespace, pictureUrl); + } + } + } + + writer.WriteElementString("g", "condition", _googleNamespace, condition); + writer.WriteElementString("g", "availability", _googleNamespace, 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); + } + + if (config.SpecialPrice && specialPrice.HasValue) + { + writer.WriteElementString("g", "sale_price", _googleNamespace, specialPrice.Value.FormatInvariant() + " " + (string)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_effective_date", _googleNamespace, specialPriceDate); + } + + price = (product._RegularPrice as decimal?) ?? price; + } + + writer.WriteElementString("g", "price", _googleNamespace, price.FormatInvariant() + " " + (string)currency.CurrencyCode); + + writer.WriteCData("gtin", gtin, "g", _googleNamespace); + writer.WriteCData("brand", brand, "g", _googleNamespace); + writer.WriteCData("mpn", mpn, "g", _googleNamespace); + + 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); + + 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); + + 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); + + writer.WriteElementString("g", "identifier_exists", _googleNamespace, gtin.HasValue() || brand.HasValue() || mpn.HasValue() ? "TRUE" : "FALSE"); + + if (config.ExpirationDays > 0) + { + writer.WriteElementString("g", "expiration_date", _googleNamespace, 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); + } + + if (config.ExportBasePrice && entity.BasePriceHasValue) + { + var measureUnit = BasePriceUnits((string)product.BasePriceMeasureUnit); + + if (BasePriceSupported(entity.BasePriceBaseAmount ?? 0, measureUnit)) + { + 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); + } + } + + 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()) + { + writer.WriteElementString("g", "energy_efficiency_class", _googleNamespace, gmc.EnergyEfficiencyClass); + } + + if (gmc != null && gmc.CustomLabel0.HasValue()) + { + writer.WriteElementString("g", "custom_label_0", _googleNamespace, gmc.CustomLabel0); + } + + if (gmc != null && gmc.CustomLabel1.HasValue()) + { + writer.WriteElementString("g", "custom_label_1", _googleNamespace, gmc.CustomLabel1); + } + + if (gmc != null && gmc.CustomLabel2.HasValue()) + { + writer.WriteElementString("g", "custom_label_2", _googleNamespace, gmc.CustomLabel2); + } + + if (gmc != null && gmc.CustomLabel3.HasValue()) + { + writer.WriteElementString("g", "custom_label_3", _googleNamespace, gmc.CustomLabel3); + } + + if (gmc != null && gmc.CustomLabel4.HasValue()) + { + writer.WriteElementString("g", "custom_label_4", _googleNamespace, gmc.CustomLabel4); + } + + ++context.RecordsSucceeded; + } + catch (Exception exception) + { + context.RecordException(exception, entity.Id); + } + + writer.WriteEndElement(); // item + } + } + + writer.WriteEndElement(); // channel + writer.WriteEndElement(); // rss + writer.WriteEndDocument(); + } + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/RouteProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/RouteProvider.cs index 57ac853189..d0e3b85340 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/RouteProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/RouteProvider.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.GoogleMerchantCenter { @@ -10,11 +10,12 @@ public void RegisterRoutes(RouteCollection routes) { routes.MapRoute("SmartStore.GoogleMerchantCenter", "Plugins/SmartStore.GoogleMerchantCenter/{action}", - new { controller = "FeedFroogle", action = "Configure" }, + new { controller = "FeedGoogleMerchantCenter", action = "Configure" }, new[] { "SmartStore.GoogleMerchantCenter.Controllers" } ) - .DataTokens["area"] = "SmartStore.GoogleMerchantCenter"; + .DataTokens["area"] = GoogleMerchantCenterFeedPlugin.SystemName; } + public int Priority { get diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs index b9daa3c73e..b5fab3af04 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs @@ -1,24 +1,16 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; -using System.Web; -using System.Web.Mvc; -using System.Xml; -using Autofac; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Logging; +using SmartStore.Core.Localization; +using SmartStore.Core.Plugins; using SmartStore.GoogleMerchantCenter.Domain; using SmartStore.GoogleMerchantCenter.Models; -using SmartStore.Services.Catalog; -using SmartStore.Services.Directory; -using SmartStore.Services.Localization; -using SmartStore.Services.Tasks; -using SmartStore.Web.Framework.Plugins; +using SmartStore.Services; using Telerik.Web.Mvc; namespace SmartStore.GoogleMerchantCenter.Services @@ -27,43 +19,23 @@ public partial class GoogleFeedService : IGoogleFeedService { private const string _googleNamespace = "http://base.google.com/ns/1.0"; - private readonly FeedPluginHelper _helper; private readonly IRepository _gpRepository; - private readonly IProductService _productService; - private readonly IManufacturerService _manufacturerService; - private readonly IMeasureService _measureService; - private readonly MeasureSettings _measureSettings; - private readonly IDbContext _dbContext; - private readonly AdminAreaSettings _adminAreaSettings; + private readonly ICommonServices _services; + private readonly IPluginFinder _pluginFinder; public GoogleFeedService( IRepository gpRepository, - IProductService productService, - IManufacturerService manufacturerService, - FroogleSettings settings, - IMeasureService measureService, - MeasureSettings measureSettings, - IDbContext dbContext, - AdminAreaSettings adminAreaSettings, - IComponentContext ctx) + ICommonServices services, + IPluginFinder pluginFinder) { _gpRepository = gpRepository; - _productService = productService; - _manufacturerService = manufacturerService; - Settings = settings; - _measureService = measureService; - _measureSettings = measureSettings; - _dbContext = dbContext; - _adminAreaSettings = adminAreaSettings; - - _helper = new FeedPluginHelper(ctx, "SmartStore.GoogleMerchantCenter", "SmartStore.GoogleMerchantCenter", () => - { - return Settings as PromotionFeedSettings; - }); + _services = services; + _pluginFinder = pluginFinder; + + T = NullLocalizer.Instance; } - public FroogleSettings Settings { get; set; } - public FeedPluginHelper Helper { get { return _helper; } } + public Localizer T { get; set; } public GoogleProductRecord GetGoogleProductRecord(int productId) { @@ -79,6 +51,15 @@ from gp in _gpRepository.Table return record; } + public List GetGoogleProductRecords(int[] productIds) + { + if (productIds == null || productIds.Length == 0) + return new List(); + + var lst = _gpRepository.TableUntracked.Where(x => productIds.Contains(x.ProductId)).ToList(); + return lst; + } + public void InsertGoogleProductRecord(GoogleProductRecord record) { if (record == null) @@ -103,377 +84,7 @@ public void DeleteGoogleProductRecord(GoogleProductRecord record) _gpRepository.Delete(record); } - private bool SpecialPrice(Product product, out string specialPriceDate) - { - specialPriceDate = ""; - - try - { - if (Settings.SpecialPrice && product.SpecialPrice.HasValue && product.SpecialPriceStartDateTimeUtc.HasValue && product.SpecialPriceEndDateTimeUtc.HasValue) - { - if (!(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) - { - string dateFormat = "yyyy-MM-ddTHH:mmZ"; - string startDate = product.SpecialPriceStartDateTimeUtc.Value.ToString(dateFormat); - string endDate = product.SpecialPriceEndDateTimeUtc.Value.ToString(dateFormat); - - specialPriceDate = "{0}/{1}".FormatWith(startDate, endDate); - } - } - } - catch (Exception exc) - { - exc.Dump(); - } - return specialPriceDate.HasValue(); - } - private string ProductCategory(GoogleProductRecord googleProduct) - { - string productCategory = ""; - - if (googleProduct != null) - productCategory = googleProduct.Taxonomy; - - if (productCategory.IsEmpty()) - productCategory = Settings.DefaultGoogleCategory; - - return productCategory; - } - private string Condition() - { - if (Settings.Condition.IsCaseInsensitiveEqual(PluginHelper.NotSpecified)) - return ""; - - if (Settings.Condition.IsEmpty()) - return "new"; - - return Settings.Condition; - } - private string Availability(Product product) - { - if (Settings.Availability.IsCaseInsensitiveEqual(PluginHelper.NotSpecified)) - return ""; - - if (Settings.Availability.IsEmpty()) - { - if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock && product.StockQuantity <= 0) - { - switch (product.BackorderMode) - { - case BackorderMode.NoBackorders: - return "out of stock"; - case BackorderMode.AllowQtyBelow0: - case BackorderMode.AllowQtyBelow0AndNotifyCustomer: - return "available for order"; - } - } - return "in stock"; - } - return Settings.Availability; - } - private string Gender(GoogleProductRecord googleProduct) - { - if (Settings.Gender.IsCaseInsensitiveEqual(PluginHelper.NotSpecified)) - return ""; - - if (googleProduct != null && googleProduct.Gender.HasValue()) - return googleProduct.Gender; - - return Settings.Gender; - } - private string AgeGroup(GoogleProductRecord googleProduct) - { - if (Settings.AgeGroup.IsCaseInsensitiveEqual(PluginHelper.NotSpecified)) - return ""; - - if (googleProduct != null && googleProduct.AgeGroup.HasValue()) - return googleProduct.AgeGroup; - - return Settings.AgeGroup; - } - private string Color(GoogleProductRecord googleProduct) - { - if (googleProduct != null && googleProduct.Color.HasValue()) - return googleProduct.Color; - - return Settings.Color; - } - private string Size(GoogleProductRecord googleProduct) - { - if (googleProduct != null && googleProduct.Size.HasValue()) - return googleProduct.Size; - - return Settings.Size; - } - private string Material(GoogleProductRecord googleProduct) - { - if (googleProduct != null && googleProduct.Material.HasValue()) - return googleProduct.Material; - - return Settings.Material; - } - private string Pattern(GoogleProductRecord googleProduct) - { - if (googleProduct != null && googleProduct.Pattern.HasValue()) - return googleProduct.Pattern; - - return Settings.Pattern; - } - - private string ItemGroupId(GoogleProductRecord googleProduct) - { - if (googleProduct != null && googleProduct.ItemGroupId.HasValue()) - return googleProduct.ItemGroupId; - - return ""; - } - - private bool BasePriceSupported(int baseAmount, string unit) - { - if (baseAmount == 1 || baseAmount == 10 || baseAmount == 100) - return true; - - if (baseAmount == 75 && unit == "cl") - return true; - - if ((baseAmount == 50 || baseAmount == 1000) && unit == "kg") - return true; - - return false; - } - - private string BasePriceUnits(string value) - { - const string defaultValue = "kg"; - - if (value.IsEmpty()) - return defaultValue; - - // TODO: Product.BasePriceMeasureUnit should be localized - switch (value.ToLower()) - { - case "mg": - case "milligramm": - case "milligram": - return "mg"; - case "g": - case "gramm": - case "gram": - return "g"; - case "kg": - case "kilogramm": - case "kilogram": - return "kg"; - - case "ml": - case "milliliter": - case "millilitre": - return "ml"; - case "cl": - case "zentiliter": - case "centilitre": - return "cl"; - case "l": - case "liter": - case "litre": - return "l"; - case "cbm": - case "kubikmeter": - case "cubic metre": - return "cbm"; - - case "cm": - case "zentimeter": - case "centimetre": - return "cm"; - case "m": - case "meter": - return "m"; - - case "qm": - case "quadratmeter": - case "square metre": - return "sqm"; - - default: - return defaultValue; - } - } - private void WriteItem(FeedFileCreationContext fileCreation, XmlWriter writer, Product product, Currency currency, string measureWeightSystemKey) - { - writer.WriteStartElement("item"); - - try - { - var manu = _manufacturerService.GetProductManufacturersByProductId(product.Id).FirstOrDefault(); - var mainImageUrl = Helper.GetMainProductImageUrl(fileCreation.Store, product); - var googleProduct = GetGoogleProductRecord(product.Id); - var category = ProductCategory(googleProduct); - - if (category.IsEmpty()) - fileCreation.ErrorMessage = Helper.GetResource("MissingDefaultCategory"); - - string manuName = (manu != null ? manu.Manufacturer.GetLocalized(x => x.Name, Settings.LanguageId, true, false) : null); - string productName = product.GetLocalized(x => x.Name, Settings.LanguageId, true, false); - string shortDescription = product.GetLocalized(x => x.ShortDescription, Settings.LanguageId, true, false); - string fullDescription = product.GetLocalized(x => x.FullDescription, Settings.LanguageId, true, false); - - var brand = (manuName ?? Settings.Brand); - var mpn = Helper.GetManufacturerPartNumber(product); - - bool identifierExists = product.Gtin.HasValue() || brand.HasValue() || mpn.HasValue(); - - writer.WriteElementString("g", "id", _googleNamespace, product.Id.ToString()); - - writer.WriteStartElement("title"); - writer.WriteCData(productName.Truncate(70)); - writer.WriteEndElement(); - - var description = Helper.BuildProductDescription(productName, shortDescription, fullDescription, manuName, d => - { - if (fullDescription.IsEmpty() && shortDescription.IsEmpty()) - { - var rnd = new Random(); - - switch (rnd.Next(1, 5)) - { - case 1: return d.Grow(Settings.AppendDescriptionText1, " "); - case 2: return d.Grow(Settings.AppendDescriptionText2, " "); - case 3: return d.Grow(Settings.AppendDescriptionText3, " "); - case 4: return d.Grow(Settings.AppendDescriptionText4, " "); - case 5: return d.Grow(Settings.AppendDescriptionText5, " "); - } - } - return d; - }); - - writer.WriteStartElement("description"); - writer.WriteCData(description.RemoveInvalidXmlChars()); - writer.WriteEndElement(); - - writer.WriteStartElement("g", "google_product_category", _googleNamespace); - writer.WriteCData(category); - writer.WriteFullEndElement(); - - string productType = Helper.GetCategoryPath(product); - if (productType.HasValue()) - { - writer.WriteStartElement("g", "product_type", _googleNamespace); - writer.WriteCData(productType); - writer.WriteFullEndElement(); - } - - writer.WriteElementString("link", Helper.GetProductDetailUrl(fileCreation.Store, product)); - writer.WriteElementString("g", "image_link", _googleNamespace, mainImageUrl); - - foreach (string additionalImageUrl in Helper.GetAdditionalProductImages(fileCreation.Store, product, mainImageUrl)) - { - writer.WriteElementString("g", "additional_image_link", _googleNamespace, additionalImageUrl); - } - - writer.WriteElementString("g", "condition", _googleNamespace, Condition()); - writer.WriteElementString("g", "availability", _googleNamespace, Availability(product)); - - decimal price = Helper.GetProductPrice(product, currency); - string specialPriceDate; - - if (SpecialPrice(product, out specialPriceDate)) - { - writer.WriteElementString("g", "sale_price", _googleNamespace, price.FormatInvariant() + " " + currency.CurrencyCode); - writer.WriteElementString("g", "sale_price_effective_date", _googleNamespace, specialPriceDate); - - // get regular price ignoring any special price - decimal specialPrice = product.SpecialPrice.Value; - product.SpecialPrice = null; - price = Helper.GetProductPrice(product, currency); - product.SpecialPrice = specialPrice; - - _dbContext.SetToUnchanged(product); - } - - writer.WriteElementString("g", "price", _googleNamespace, price.FormatInvariant() + " " + currency.CurrencyCode); - - writer.WriteCData("gtin", product.Gtin, "g", _googleNamespace); - writer.WriteCData("brand", brand, "g", _googleNamespace); - writer.WriteCData("mpn", mpn, "g", _googleNamespace); - - writer.WriteCData("gender", Gender(googleProduct), "g", _googleNamespace); - writer.WriteCData("age_group", AgeGroup(googleProduct), "g", _googleNamespace); - writer.WriteCData("color", Color(googleProduct), "g", _googleNamespace); - writer.WriteCData("size", Size(googleProduct), "g", _googleNamespace); - writer.WriteCData("material", Material(googleProduct), "g", _googleNamespace); - writer.WriteCData("pattern", Pattern(googleProduct), "g", _googleNamespace); - writer.WriteCData("item_group_id", ItemGroupId(googleProduct), "g", _googleNamespace); - - writer.WriteElementString("g", "online_only", _googleNamespace, Settings.OnlineOnly ? "y" : "n"); - writer.WriteElementString("g", "identifier_exists", _googleNamespace, identifierExists ? "TRUE" : "FALSE"); - - if (Settings.ExpirationDays > 0) - { - writer.WriteElementString("g", "expiration_date", _googleNamespace, DateTime.UtcNow.AddDays(Settings.ExpirationDays).ToString("yyyy-MM-dd")); - } - - if (Settings.ExportShipping) - { - string weightInfo, weight = 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); - } - - if (Settings.ExportBasePrice && product.BasePriceHasValue) - { - string measureUnit = BasePriceUnits(product.BasePriceMeasureUnit); - - if (BasePriceSupported(product.BasePriceBaseAmount ?? 0, measureUnit)) - { - string basePriceMeasure = "{0} {1}".FormatWith((product.BasePriceAmount ?? decimal.Zero).FormatInvariant(), measureUnit); - string basePriceBaseMeasure = "{0} {1}".FormatWith(product.BasePriceBaseAmount, measureUnit); - - writer.WriteElementString("g", "unit_pricing_measure", _googleNamespace, basePriceMeasure); - writer.WriteElementString("g", "unit_pricing_base_measure", _googleNamespace, basePriceBaseMeasure); - } - } - } - catch (Exception exc) - { - fileCreation.Logger.Error(exc.Message, exc); - } - - writer.WriteEndElement(); // item - } - - public string[] GetTaxonomyList() - { - try - { - string fileDir = Path.Combine(Helper.Plugin.OriginalAssemblyFile.Directory.FullName, "Files"); - string fileName = "taxonomy.{0}.txt".FormatWith(Helper.Language.LanguageCulture ?? "de-DE"); - string path = Path.Combine(fileDir, fileName); - - if (!File.Exists(path)) - path = Path.Combine(fileDir, "taxonomy.en-US.txt"); - - string[] lines = File.ReadAllLines(path, Encoding.UTF8); - - return lines; - } - catch (Exception exc) - { - exc.Dump(); - } - return new string[] { }; - } - - public void UpdateInsert(int pk, string name, string value) + public void Upsert(int pk, string name, string value) { if (pk == 0 || name.IsEmpty()) return; @@ -484,7 +95,7 @@ public void UpdateInsert(int pk, string name, string value) if (product == null) { - product = new GoogleProductRecord() + product = new GoogleProductRecord { ProductId = pk, CreatedOnUtc = utcNow @@ -514,6 +125,36 @@ public void UpdateInsert(int pk, string name, string value) case "Pattern": product.Pattern = value; break; + case "Export2": + product.Export = value.ToBool(true); + break; + case "Multipack2": + product.Multipack = value.ToInt(); + break; + case "IsBundle": + product.IsBundle = (value.IsEmpty() ? (bool?)null : value.ToBool()); + break; + case "IsAdult": + product.IsAdult = (value.IsEmpty() ? (bool?)null : value.ToBool()); + break; + case "EnergyEfficiencyClass": + product.EnergyEfficiencyClass = value; + break; + case "CustomLabel0": + product.CustomLabel0 = value; + break; + case "CustomLabel1": + product.CustomLabel1 = value; + break; + case "CustomLabel2": + product.CustomLabel2 = value; + break; + case "CustomLabel3": + product.CustomLabel3 = value; + break; + case "CustomLabel4": + product.CustomLabel4 = value; + break; } product.UpdatedOnUtc = utcNow; @@ -534,13 +175,16 @@ public void UpdateInsert(int pk, string name, string value) _gpRepository.Update(product); } } + public GridModel GetGridModel(GridCommand command, string searchProductName = null, string touched = null) { var model = new GridModel(); var textInfo = CultureInfo.InvariantCulture.TextInfo; + string yes = T("Admin.Common.Yes"); + string no = T("Admin.Common.No"); - // there's no way to share a context instance across repositories which makes GoogleProductObjectContext pretty useless here. - // so let's fallback to good ole sql... by the way, fastest possible paged data query ever. + // there's no way to share a context instance across repositories in EF. + // so we have to fallback to pure SQL here to get the data paged and filtered. var whereClause = new StringBuilder("(NOT ([t2].[Deleted] = 1)) AND ([t2].[VisibleIndividually] = 1)"); @@ -557,19 +201,50 @@ public GridModel GetGridModel(GridCommand command, string se whereClause.Append(" AND ([t2].[IsTouched] = 0 OR [t2].[IsTouched] IS NULL)"); } - string sql = -"SELECT [TotalCount], [t3].[Id], [t3].[Name], [t3].[SKU], [t3].[ProductTypeId], [t3].[value] AS [Taxonomy], [t3].[value2] AS [Gender], [t3].[value3] AS [AgeGroup], [t3].[value4] AS [Color], [t3].[value5] AS [Size], [t3].[value6] AS [Material], [t3].[value7] AS [Pattern]" + -" FROM (" + -" SELECT COUNT(id) OVER() [TotalCount], ROW_NUMBER() OVER (ORDER BY [t2].[Name]) AS [ROW_NUMBER], [t2].[Id], [t2].[Name], [t2].[SKU], [t2].[ProductTypeId], [t2].[value], [t2].[value2], [t2].[value3], [t2].[value4], [t2].[value5], [t2].[value6], [t2].[value7]" + -" FROM (" + -" SELECT [t0].[Id], [t0].[Name], [t0].[SKU], [t0].[ProductTypeId], [t1].[Taxonomy] AS [value], [t1].[Gender] AS [value2], [t1].[AgeGroup] AS [value3], [t1].[Color] AS [value4], [t1].[Size] AS [value5], [t1].[Material] AS [value6], [t1].[Pattern] AS [value7], [t0].[Deleted], [t0].[VisibleIndividually], [t1].[IsTouched]" + -" FROM [Product] AS [t0]" + -" LEFT OUTER JOIN [GoogleProduct] AS [t1] ON [t0].[Id] = [t1].[ProductId]" + -" ) AS [t2]" + -" WHERE " + whereClause.ToString() + -" ) AS [t3]" + -" WHERE [t3].[ROW_NUMBER] BETWEEN {0} + 1 AND {0} + {1}" + -" ORDER BY [t3].[ROW_NUMBER]"; + string sql = null; + string sqlCount = null; + var isSqlServer = DataSettings.Current.IsSqlServer; + + if (isSqlServer) + { + // fastest possible paged data query + sql = + "SELECT [TotalCount], [t3].[Id], [t3].[Name], [t3].[SKU], [t3].[ProductTypeId], [t3].[value] AS [Taxonomy], [t3].[value2] AS [Gender], [t3].[value3] AS [AgeGroup], [t3].[value4] AS [Color], [t3].[value5] AS [Size], [t3].[value6] AS [Material], [t3].[value7] AS [Pattern], [t3].[value8] AS [Export], [t3].[value9] AS [Multipack], [t3].[value10] AS [IsBundle], [t3].[value11] AS [IsAdult], [t3].[value12] AS [EnergyEfficiencyClass], [t3].[value13] AS [CustomLabel0], [t3].[value14] AS [CustomLabel1], [t3].[value15] AS [CustomLabel2], [t3].[value16] AS [CustomLabel3], [t3].[value17] AS [CustomLabel4]" + + " FROM (" + + " SELECT COUNT(id) OVER() [TotalCount], ROW_NUMBER() OVER (ORDER BY [t2].[Name]) AS [ROW_NUMBER], [t2].[Id], [t2].[Name], [t2].[SKU], [t2].[ProductTypeId], [t2].[value], [t2].[value2], [t2].[value3], [t2].[value4], [t2].[value5], [t2].[value6], [t2].[value7], [t2].[value8], [t2].[value9], [t2].[value10], [t2].[value11], [t2].[value12], [t2].[value13], [t2].[value14], [t2].[value15], [t2].[value16], [t2].[value17]" + + " FROM (" + + " SELECT [t0].[Id], [t0].[Name], [t0].[SKU], [t0].[ProductTypeId], [t1].[Taxonomy] AS [value], [t1].[Gender] AS [value2], [t1].[AgeGroup] AS [value3], [t1].[Color] AS [value4], [t1].[Size] AS [value5], [t1].[Material] AS [value6], [t1].[Pattern] AS [value7], COALESCE([t1].[Export],1) AS [value8], COALESCE([t1].[Multipack],0) AS [value9], [t1].[IsBundle] AS [value10], [t1].[IsAdult] AS [value11], [t1].[EnergyEfficiencyClass] AS [value12], [t1].[CustomLabel0] AS [value13], [t1].[CustomLabel1] AS [value14], [t1].[CustomLabel2] AS [value15], [t1].[CustomLabel3] AS [value16], [t1].[CustomLabel4] AS [value17], [t0].[Deleted], [t0].[VisibleIndividually], [t1].[IsTouched]" + + " FROM [Product] AS [t0]" + + " LEFT OUTER JOIN [GoogleProduct] AS [t1] ON [t0].[Id] = [t1].[ProductId]" + + " ) AS [t2]" + + " WHERE " + whereClause.ToString() + + " ) AS [t3]" + + " WHERE [t3].[ROW_NUMBER] BETWEEN {0} + 1 AND {0} + {1}" + + " ORDER BY [t3].[ROW_NUMBER]"; + } + else + { + // OFFSET... FETCH NEXT requires SQL Server 2012 or SQL CE 4 + sql = + "SELECT [t2].[Id], [t2].[Name], [t2].[SKU], [t2].[ProductTypeId], [t2].[value] AS [Taxonomy], [t2].[value2] AS [Gender], [t2].[value3] AS [AgeGroup], [t2].[value4] AS [Color], [t2].[value5] AS [Size], [t2].[value6] AS [Material], [t2].[value7] AS [Pattern], [t2].[value8] AS [Export], [t2].[value9] AS [Multipack], [t2].[value10] AS [IsBundle], [t2].[value11] AS [IsAdult], [t2].[value12] AS [EnergyEfficiencyClass], [t2].[value13] AS [CustomLabel0], [t2].[value14] AS [CustomLabel1], [t2].[value15] AS [CustomLabel2], [t2].[value16] AS [CustomLabel3], [t2].[value17] AS [CustomLabel4]" + + " FROM (" + + " SELECT [t0].[Id], [t0].[Name], [t0].[SKU], [t0].[ProductTypeId], [t1].[Taxonomy] AS [value], [t1].[Gender] AS [value2], [t1].[AgeGroup] AS [value3], [t1].[Color] AS [value4], [t1].[Size] AS [value5], [t1].[Material] AS [value6], [t1].[Pattern] AS [value7], COALESCE([t1].[Export],1) AS [value8], COALESCE([t1].[Multipack],0) AS [value9], [t1].[IsBundle] AS [value10], [t1].[IsAdult] AS [value11], [t1].[EnergyEfficiencyClass] AS [value12], [t1].[CustomLabel0] AS [value13], [t1].[CustomLabel1] AS [value14], [t1].[CustomLabel2] AS [value15], [t1].[CustomLabel3] AS [value16], [t1].[CustomLabel4] AS [value17], [t0].[Deleted], [t0].[VisibleIndividually], [t1].[IsTouched] AS [IsTouched]" + + " FROM [Product] AS [t0]" + + " LEFT OUTER JOIN [GoogleProduct] AS [t1] ON [t0].[Id] = [t1].[ProductId]" + + " ) AS [t2]" + + " WHERE " + whereClause.ToString() + + " ORDER BY [t2].[Name]" + + " OFFSET {0} ROWS FETCH NEXT {1} ROWS ONLY"; + + sqlCount = + "SELECT COUNT(*)" + + " FROM (" + + " SELECT [t0].[Id], [t0].[Name], [t0].[Deleted], [t0].[VisibleIndividually], [t1].[IsTouched] AS [IsTouched]" + + " FROM [Product] AS [t0]" + + " LEFT OUTER JOIN [GoogleProduct] AS [t1] ON [t0].[Id] = [t1].[ProductId]" + + " ) AS [t2]" + + " WHERE " + whereClause.ToString(); + } var data = _gpRepository.Context.SqlQuery(sql, (command.Page - 1) * command.PageSize, command.PageSize).ToList(); @@ -577,193 +252,68 @@ public GridModel GetGridModel(GridCommand command, string se data.ForEach(x => { if (x.ProductType != ProductType.SimpleProduct) - { - string key = "Admin.Catalog.Products.ProductType.{0}.Label".FormatWith(x.ProductType.ToString()); - x.ProductTypeName = Helper.GetResource(key); - } + x.ProductTypeName = T("Admin.Catalog.Products.ProductType.{0}.Label".FormatInvariant(x.ProductType.ToString())); - var googleProduct = GetGoogleProductRecord(x.Id); if (x.Gender.HasValue()) - x.GenderLocalize = Helper.GetResource("Gender" + textInfo.ToTitleCase(x.Gender)); + x.GenderLocalize = T("Plugins.Feed.Froogle.Gender" + textInfo.ToTitleCase(x.Gender)); if (x.AgeGroup.HasValue()) - x.AgeGroupLocalize = Helper.GetResource("AgeGroup" + textInfo.ToTitleCase(x.AgeGroup)); + x.AgeGroupLocalize = T("Plugins.Feed.Froogle.AgeGroup" + textInfo.ToTitleCase(x.AgeGroup)); + + x.Export2Localize = (x.Export == 0 ? no : yes); + + if (x.IsBundle.HasValue) + x.IsBundleLocalize = (x.IsBundle.Value ? yes : no); + else + x.IsBundleLocalize = null; + + if (x.IsAdult.HasValue) + x.IsAdultLocalize = (x.IsAdult.Value ? yes : no); + else + x.IsAdultLocalize = null; }); model.Data = data; model.Total = (data.Count > 0 ? data.First().TotalCount : 0); - return model; - - #region old code - - //var searchContext = new ProductSearchContext() - //{ - // Keywords = searchProductName, - // PageIndex = command.Page - 1, - // PageSize = command.PageSize, - // VisibleIndividuallyOnly = true, - // ShowHidden = true - //}; - - //var products = _productService.SearchProducts(searchContext); - - //var data = products.Select(x => - //{ - // var gModel = new GoogleProductModel() - // { - // ProductId = x.Id, - // Name = x.Name - // }; - - // var googleProduct = GetByProductId(x.Id); - - // if (googleProduct != null) - // { - // gModel.Taxonomy = googleProduct.Taxonomy; - // gModel.Gender = googleProduct.Gender; - // gModel.AgeGroup = googleProduct.AgeGroup; - // gModel.Color = googleProduct.Color; - // gModel.Size = googleProduct.Size; - // gModel.Material = googleProduct.Material; - // gModel.Pattern = googleProduct.Pattern; - - // if (gModel.Gender.HasValue()) - // gModel.GenderLocalize = Helper.GetResource("Gender" + CultureInfo.InvariantCulture.TextInfo.ToTitleCase(gModel.Gender)); - - // if (gModel.AgeGroup.HasValue()) - // gModel.AgeGroupLocalize = Helper.GetResource("AgeGroup" + CultureInfo.InvariantCulture.TextInfo.ToTitleCase(gModel.AgeGroup)); - // } - - // return gModel; - //}) - //.ToList(); - - //var model = new GridModel() - //{ - // Data = data, - // Total = products.TotalCount - //}; - - //return model; - - #endregion old code - } - private void CreateFeed(FeedFileCreationContext fileCreation, TaskExecutionContext taskContext) - { - var xmlSettings = new XmlWriterSettings + if (data.Count > 0) { - Encoding = Encoding.UTF8, - CheckCharacters = false - }; - - using (var writer = XmlWriter.Create(fileCreation.Stream, xmlSettings)) + if (isSqlServer) + model.Total = data.First().TotalCount; + else + model.Total = _gpRepository.Context.SqlQuery(sqlCount).FirstOrDefault(); + } + else { - try - { - fileCreation.Logger.Information("Log file - Google Merchant Center feed."); - - var searchContext = new ProductSearchContext - { - OrderBy = ProductSortingEnum.CreatedOn, - PageSize = Settings.PageSize, - StoreId = fileCreation.Store.Id, - VisibleIndividuallyOnly = true - }; - - var currency = Helper.GetUsedCurrency(Settings.CurrencyId); - var measureWeightSystemKey = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).SystemKeyword; - - writer.WriteStartDocument(); - writer.WriteStartElement("rss"); - writer.WriteAttributeString("version", "2.0"); - writer.WriteAttributeString("xmlns", "g", null, _googleNamespace); - writer.WriteStartElement("channel"); - writer.WriteElementString("title", "{0} - Feed for Google Merchant Center".FormatWith(fileCreation.Store.Name)); - writer.WriteElementString("link", "http://base.google.com/base/"); - writer.WriteElementString("description", "Information about products"); - - for (int i = 0; i < 9999999; ++i) - { - searchContext.PageIndex = i; - - var products = _productService.SearchProducts(searchContext); - - if (fileCreation.TotalRecords == 0) - fileCreation.TotalRecords = products.TotalCount * fileCreation.StoreCount; // approx - - foreach (var product in products) - { - fileCreation.Report(); - - if (product.ProductType == ProductType.SimpleProduct || product.ProductType == ProductType.BundledProduct) - { - WriteItem(fileCreation, writer, product, currency, measureWeightSystemKey); - } - else if (product.ProductType == ProductType.GroupedProduct) - { - var associatedSearchContext = new ProductSearchContext - { - OrderBy = ProductSortingEnum.CreatedOn, - PageSize = int.MaxValue, - StoreId = fileCreation.Store.Id, - VisibleIndividuallyOnly = false, - ParentGroupedProductId = product.Id - }; - - foreach (var associatedProduct in _productService.SearchProducts(associatedSearchContext)) - { - WriteItem(fileCreation, writer, associatedProduct, currency, measureWeightSystemKey); - } - } - - if (taskContext.CancellationToken.IsCancellationRequested) - { - fileCreation.Logger.Warning("A cancellation has been requested"); - break; - } - } - - if (!products.HasNextPage) - break; - } - - writer.WriteEndElement(); // channel - writer.WriteEndElement(); // rss - writer.WriteEndDocument(); - - if (fileCreation.ErrorMessage.HasValue()) - fileCreation.Logger.Error(fileCreation.ErrorMessage); - } - catch (Exception exc) - { - fileCreation.Logger.Error(exc.Message, exc); - } + model.Total = 0; } + + return model; } - public void CreateFeed(TaskExecutionContext context) + + public string[] GetTaxonomyList() { - Helper.StartCreatingFeeds(fileCreation => + try { - CreateFeed(fileCreation, context); - return true; - }); - } - public void SetupModel(FeedFroogleModel model) - { - Helper.SetupConfigModel(model, "FeedFroogle"); + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(GoogleMerchantCenterFeedPlugin.SystemName); - model.GenerateStaticFileEachMinutes = Helper.ScheduleTask.Seconds / 60; - model.TaskEnabled = Helper.ScheduleTask.Enabled; + var fileDir = Path.Combine(descriptor.OriginalAssemblyFile.Directory.FullName, "Files"); + var fileName = "taxonomy.{0}.txt".FormatWith(_services.WorkContext.WorkingLanguage.LanguageCulture ?? "de-DE"); + var path = Path.Combine(fileDir, fileName); - model.AvailableCurrencies = Helper.AvailableCurrencies(); - model.AvailableGoogleCategories = GetTaxonomyList(); + if (!File.Exists(path)) + path = Path.Combine(fileDir, "taxonomy.en-US.txt"); - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - model.GridEditUrl = urlHelper.Action("GoogleProductEdit", "FeedFroogle", - new { Namespaces = "SmartStore.GoogleMerchantCenter.Controllers", area = "SmartStore.GoogleMerchantCenter" }); + string[] lines = File.ReadAllLines(path, Encoding.UTF8); - model.GridPageSize = _adminAreaSettings.GridPageSize; + return lines; + } + catch (Exception exc) + { + exc.Dump(); + } + + return new string[] { }; } } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs index e5a6e1d87e..6b89e81213 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs @@ -1,29 +1,26 @@ +using System.Collections.Generic; using SmartStore.GoogleMerchantCenter.Domain; using SmartStore.GoogleMerchantCenter.Models; -using SmartStore.Services.Tasks; -using SmartStore.Web.Framework.Plugins; using Telerik.Web.Mvc; namespace SmartStore.GoogleMerchantCenter.Services { public partial interface IGoogleFeedService { - FroogleSettings Settings { get; set; } - FeedPluginHelper Helper { get; } - GoogleProductRecord GetGoogleProductRecord(int productId); + + List GetGoogleProductRecords(int[] productIds); + void InsertGoogleProductRecord(GoogleProductRecord record); + void UpdateGoogleProductRecord(GoogleProductRecord record); + void DeleteGoogleProductRecord(GoogleProductRecord record); - string[] GetTaxonomyList(); - - void UpdateInsert(int pk, string name, string value); + void Upsert(int pk, string name, string value); GridModel GetGridModel(GridCommand command, string searchProductName = null, string touched = null); - void CreateFeed(TaskExecutionContext context); - - void SetupModel(FeedFroogleModel model); + string[] GetTaxonomyList(); } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj index 4d2df2f67a..f743627997 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj @@ -1,5 +1,5 @@  - + @@ -20,7 +20,7 @@ Properties SmartStore.GoogleMerchantCenter SmartStore.GoogleMerchantCenter - v4.5 + v4.5.1 512 @@ -42,6 +42,7 @@ + true @@ -81,62 +82,66 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - - ..\..\packages\Autofac.Mvc5.3.3.1\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - - ..\..\packages\FluentValidation.5.0.0.1\lib\Net40\FluentValidation.dll + + ..\..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True - + + + + + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - + False ..\..\..\lib\Telerik\Telerik.Web.Mvc.dll @@ -150,7 +155,7 @@ Properties\AssemblyVersionInfo.cs - + @@ -161,20 +166,28 @@ 201408020941244_IsTouched.cs + + + 201504211854125_IsActive.cs + + + + 201601061649324_IsBundle.cs + - - - - + + + + - + @@ -198,28 +211,34 @@ 201403112356126_Initial.cs - + + PreserveNewest + + PreserveNewest 201408020941244_IsTouched.cs + + 201504211854125_IsActive.cs + + + 201601061649324_IsBundle.cs + - - Always - - - Always + + PreserveNewest PreserveNewest - Always + PreserveNewest - Always + PreserveNewest PreserveNewest @@ -247,7 +266,7 @@ - + PreserveNewest @@ -256,6 +275,11 @@ PreserveNewest + + + PreserveNewest + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/StaticFileGenerationTask.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/StaticFileGenerationTask.cs deleted file mode 100644 index 2cc1cfca3b..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/StaticFileGenerationTask.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Autofac; -using SmartStore.GoogleMerchantCenter.Services; -using SmartStore.Services.Tasks; - -namespace SmartStore.GoogleMerchantCenter -{ - public class StaticFileGenerationTask : ITask - { - public void Execute(TaskExecutionContext context) - { - var scope = context.LifetimeScope as ILifetimeScope; - var googleService = scope.Resolve(); - - googleService.CreateFeed(context); - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ConfigurationValidator.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs similarity index 68% rename from src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ConfigurationValidator.cs rename to src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs index 169214a4cc..9799ada57f 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ConfigurationValidator.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs @@ -4,9 +4,9 @@ namespace SmartStore.GoogleMerchantCenter.Validators { - public class ConfigurationValidator : AbstractValidator + public class ProfileConfigurationValidator : AbstractValidator { - public ConfigurationValidator(ILocalizationService localize) + 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/FeedFroogle/Configure.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/Configure.cshtml deleted file mode 100644 index 90fd2a1a3a..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/Configure.cshtml +++ /dev/null @@ -1,605 +0,0 @@ -@model FeedFroogleModel -@using SmartStore.GoogleMerchantCenter; -@using SmartStore.GoogleMerchantCenter.Models; -@using SmartStore.Web.Framework; -@using SmartStore.Web.Framework.Plugins; -@using Telerik.Web.Mvc.UI; -@using SmartStore.Web.Framework.UI; - -@{ - Layout = ""; - - Html.AddCssFileParts(true, - "~/Content/x-editable/bootstrap-editable.css", - "~/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.feed.froogle.css"); - - Html.AppendScriptParts(true, "~/Content/x-editable/bootstrap-editable.js"); -} - - - - - -@(Html.SmartStore().TabStrip().Name("googlebase-configure").Items(x => { - x.Add().Text(T("Plugins.Feed.Froogle.General").Text).Content(@TabGeneral()).Selected(true); - x.Add().Text(T("Plugins.Feed.Froogle.ProductData").Text).Content(@TabProductData()); -})) - -@helper TabGeneral() -{ - - -
    - - - - - @if (Model.GeneratedFiles.Count > 0 && !Model.IsRunning) - { - - - - - } - else if (Model.IsRunning) - { - - - - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - @Html.Raw(@T("Plugins.Feed.Froogle.AdminInstruction")) -
    -
    - @Html.SmartLabelFor(m => m.GeneratedFiles) - - @foreach (var group in Model.GeneratedFiles.GroupBy(x => x.StoreId)) - { - var firstFile = group.First(); -

    - @(firstFile.StoreName): - @foreach (var file in group) - { -
    @file.FileUrl - } -
    @T("Admin.Configuration.ActivityLog"), - @T("Common.UpdatedOn"): @(firstFile.LastWriteTime) -

    - } -
      - - @Model.ProcessInfo -
      - @if (Model.IsRunning) - { - -  @T("Admin.Common.Update") - - } - else - { - -  @T("Plugins.Feed.Froogle.Generate") - - - if (Model.GeneratedFiles.Count > 0) - { - -  @T("Admin.Common.Delete") - - } - } -
    -
    -
    - @Html.SmartLabelFor(m => m.DefaultGoogleCategory) - - -
    - @Html.SmartLabelFor(m => m.ProductPictureSize) - - @Html.EditorFor(m => m.ProductPictureSize) - @Html.ValidationMessageFor(m => m.ProductPictureSize) -
    - @Html.SmartLabelFor(m => m.AdditionalImages) - - @Html.EditorFor(m => m.AdditionalImages) - @Html.ValidationMessageFor(m => m.AdditionalImages) -
    -
    -
    - @Html.SmartLabelFor(model => model.StoreId) - - @Html.DropDownListFor(model => model.StoreId, Model.AvailableStores) - @Html.ValidationMessageFor(model => model.StoreId) -
    - @Html.SmartLabelFor(model => model.LanguageId) - - @Html.DropDownListFor(model => model.LanguageId, Model.AvailableLanguages) - @Html.ValidationMessageFor(model => model.LanguageId) -
    - @Html.SmartLabelFor(m => m.BuildDescription) - - @Html.DropDownList("BuildDescription", new List { - new SelectListItem { Text = Model.Helper.GetResource("Automatic"), Value = "" }, - new SelectListItem { Text = Model.Helper.GetResource("Common.Unspecified"), Value = PluginHelper.NotSpecified }, - new SelectListItem { Text = Model.Helper.GetResource("DescShort"), Value = "short" }, - new SelectListItem { Text = Model.Helper.GetResource("DescLong"), Value = "long" }, - new SelectListItem { Text = Model.Helper.GetResource("DescTitleAndShort"), Value = "titleAndShort" }, - new SelectListItem { Text = Model.Helper.GetResource("DescTitleAndLong"), Value = "titleAndLong" }, - new SelectListItem { Text = Model.Helper.GetResource("DescManuAndTitleAndShort"), Value = "manuAndTitleAndShort" }, - new SelectListItem { Text = Model.Helper.GetResource("DescManuAndTitleAndLong"), Value = "manuAndTitleAndLong" } - }) - @Html.ValidationMessageFor(m => m.BuildDescription) -
    - @Html.SmartLabelFor(m => m.DescriptionToPlainText) - - @Html.EditorFor(m => m.DescriptionToPlainText) - @Html.ValidationMessageFor(m => m.DescriptionToPlainText) -
    - @Html.SmartLabelFor(m => m.AppendDescriptionText1) - - @Html.EditorFor(m => m.AppendDescriptionText1) - @Html.ValidationMessageFor(m => m.AppendDescriptionText1) -
    -   - - @Html.EditorFor(m => m.AppendDescriptionText2) - @Html.ValidationMessageFor(m => m.AppendDescriptionText2) -
    -   - - @Html.EditorFor(m => m.AppendDescriptionText3) - @Html.ValidationMessageFor(m => m.AppendDescriptionText3) -
    -   - - @Html.EditorFor(m => m.AppendDescriptionText4) - @Html.ValidationMessageFor(m => m.AppendDescriptionText4) -
    -   - - @Html.EditorFor(m => m.AppendDescriptionText5) - @Html.ValidationMessageFor(m => m.AppendDescriptionText5) -
    -
    -
    - @Html.SmartLabelFor(m => m.Condition) - - @Html.DropDownList("Condition", new List { - new SelectListItem { Text = Model.Helper.GetResource("Automatic"), Value = "" }, - new SelectListItem { Text = Model.Helper.GetResource("Common.Unspecified"), Value = PluginHelper.NotSpecified }, - new SelectListItem { Text = Model.Helper.GetResource("ConditionNew"), Value = "new" }, - new SelectListItem { Text = Model.Helper.GetResource("ConditionUsed"), Value = "used" }, - new SelectListItem { Text = Model.Helper.GetResource("ConditionRefurbished"), Value = "refurbished" } - }) - @Html.ValidationMessageFor(m => m.Condition) -
    - @Html.SmartLabelFor(m => m.Availability) - - @Html.DropDownList("Availability", new List { - new SelectListItem { Text = Model.Helper.GetResource("Automatic"), Value = "" }, - new SelectListItem { Text = Model.Helper.GetResource("Common.Unspecified"), Value = PluginHelper.NotSpecified }, - new SelectListItem { Text = Model.Helper.GetResource("AvailabilityInStock"), Value = "in stock" }, - new SelectListItem { Text = Model.Helper.GetResource("AvailabilityAvailableForOrder"), Value = "available for order" }, - new SelectListItem { Text = Model.Helper.GetResource("AvailabilityOutOfStock"), Value = "out of stock" }, - new SelectListItem { Text = Model.Helper.GetResource("AvailabilityPreorder"), Value = "preorder" } - }) - @Html.ValidationMessageFor(m => m.Availability) -
    - @Html.SmartLabelFor(m => m.Gender) - - @Html.DropDownList("Gender", new List { - new SelectListItem { Text = Model.Helper.GetResource("Automatic"), Value = "" }, - new SelectListItem { Text = Model.Helper.GetResource("Common.Unspecified"), Value = PluginHelper.NotSpecified }, - new SelectListItem { Text = Model.Helper.GetResource("GenderMale"), Value = "male" }, - new SelectListItem { Text = Model.Helper.GetResource("GenderFemale"), Value = "female" }, - new SelectListItem { Text = Model.Helper.GetResource("GenderUnisex"), Value = "unisex" } - }) - @Html.ValidationMessageFor(m => m.Gender) -
    - @Html.SmartLabelFor(m => m.AgeGroup) - - @Html.DropDownList("AgeGroup", new List { - new SelectListItem { Text = Model.Helper.GetResource("Automatic"), Value = "" }, - new SelectListItem { Text = Model.Helper.GetResource("Common.Unspecified"), Value = PluginHelper.NotSpecified }, - new SelectListItem { Text = Model.Helper.GetResource("AgeGroupAdult"), Value = "adult" }, - new SelectListItem { Text = Model.Helper.GetResource("AgeGroupKids"), Value = "kids" } - }) - @Html.ValidationMessageFor(m => m.AgeGroup) -
    - @Html.SmartLabelFor(m => m.Brand) - - @Html.EditorFor(m => m.Brand) - @Html.ValidationMessageFor(m => m.Brand) -
    - @Html.SmartLabelFor(m => m.Color) - - @Html.EditorFor(m => m.Color) - @Html.ValidationMessageFor(m => m.Color) -
    - @Html.SmartLabelFor(m => m.Size) - - @Html.EditorFor(m => m.Size) - @Html.ValidationMessageFor(m => m.Size) -
    - @Html.SmartLabelFor(m => m.Material) - - @Html.EditorFor(m => m.Material) - @Html.ValidationMessageFor(m => m.Material) -
    - @Html.SmartLabelFor(m => m.Pattern) - - @Html.EditorFor(m => m.Pattern) - @Html.ValidationMessageFor(m => m.Pattern) -
    -
    -
    - @Html.SmartLabelFor(m => m.CurrencyId) - - @Html.DropDownListFor(m => m.CurrencyId, Model.AvailableCurrencies) - @Html.ValidationMessageFor(m => m.CurrencyId) -
    - @Html.SmartLabelFor(m => m.ExpirationDays) - - @Html.EditorFor(m => m.ExpirationDays) - @Html.ValidationMessageFor(m => m.ExpirationDays) -
    - @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) -
    - @Html.SmartLabelFor(m => m.UseOwnProductNo) - - @Html.EditorFor(m => m.UseOwnProductNo) - @Html.ValidationMessageFor(m => m.UseOwnProductNo) -
    - @Html.SmartLabelFor(m => m.OnlineOnly) - - @Html.EditorFor(m => m.OnlineOnly) - @Html.ValidationMessageFor(m => m.OnlineOnly) -
    - @Html.SmartLabelFor(m => m.ConvertNetToGrossPrices) - - @Html.EditorFor(m => m.ConvertNetToGrossPrices) - @Html.ValidationMessageFor(m => m.ConvertNetToGrossPrices) -
    - @Html.SmartLabelFor(m => m.TaskEnabled) - - @Html.EditorFor(m => m.TaskEnabled) - @Html.ValidationMessageFor(m => m.TaskEnabled) -
    - @Html.SmartLabelFor(m => m.GenerateStaticFileEachMinutes) - - @Html.EditorFor(m => m.GenerateStaticFileEachMinutes) - @Html.ValidationMessageFor(m => m.GenerateStaticFileEachMinutes) -
      - -
    -
    -} - -@helper TabProductData() -{ -
    - - @Html.Raw(@T("Plugins.Feed.Froogle.GridEditNote")) -
    - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - @(Html.Telerik().Grid() - .Name("froogleproducts-grid") - .DataKeys(keys => { - keys.Add(x => x.ProductId).RouteKey("ProductId"); - }) - .Columns(c => { - c.Bound(x => x.ProductId).ReadOnly().Visible(false); - c.Bound(x => x.Name) - .ReadOnly().Visible(true).Width(420) - .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.Taxonomy).ClientTemplate(Html.XEditableLink("Taxonomy", "typeahead")); - c.Bound(x => x.Gender).ClientTemplate(Html.XEditableLink("Gender", "select2")).Width(100); - c.Bound(x => x.AgeGroup).ClientTemplate(Html.XEditableLink("AgeGroup", "select2")).Width(100); - 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")); - }) - .ClientEvents(e => { - e.OnDataBound("OnGridDataBound"); - e.OnDataBinding("OnGridDataBinding"); - }) - .DataBinding(dataBinding => { - dataBinding.Ajax() - .Select("GoogleProductList", "FeedFroogle"); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .EnableCustomBinding(true) - ) -
    - - -} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/ProductEditTab.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/ProductEditTab.cshtml deleted file mode 100644 index 5cff66c516..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/ProductEditTab.cshtml +++ /dev/null @@ -1,102 +0,0 @@ -@model SmartStore.GoogleMerchantCenter.Models.GoogleProductModel - -@{ - Layout = ""; -} - - - -@* VERY IMPORTANT for proper model binding *@ -@Html.Hidden("__Type__", Model.GetType().AssemblyQualifiedName) - -@Html.HiddenFor(m => m.ProductId) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(m => m.Taxonomy) - - @Html.TextBoxFor(m => m.Taxonomy, new { data_provide = "typeahead", placeholder = ViewBag.DefaultCategory, style = "width: 97%", autocomplete = "off" }) - @Html.ValidationMessageFor(m => m.Taxonomy) -
    - @Html.SmartLabelFor(m => m.Gender) - - @Html.DropDownListFor(m => m.Gender, (IEnumerable)ViewBag.AvailableGenders, (string)ViewBag.DefaultGender) - @Html.ValidationMessageFor(m => m.Gender) -
    - @Html.SmartLabelFor(m => m.AgeGroup) - - @Html.DropDownListFor(m => m.AgeGroup, (IEnumerable)ViewBag.AvailableAgeGroups, (string)ViewBag.DefaultAgeGroup) - @Html.ValidationMessageFor(m => m.AgeGroup) -
    - @Html.SmartLabelFor(m => m.Color) - - @Html.TextBoxFor(m => m.Color, new { placeholder = ViewBag.DefaultColor }) - @Html.ValidationMessageFor(m => m.Color) -
    - @Html.SmartLabelFor(m => m.Size) - - @Html.TextBoxFor(m => m.Size, new { placeholder = ViewBag.DefaultSize }) - @Html.ValidationMessageFor(m => m.Size) -
    - @Html.SmartLabelFor(m => m.Material) - - @Html.TextBoxFor(m => m.Material, new { placeholder = ViewBag.DefaultMaterial }) - @Html.ValidationMessageFor(m => m.Material) -
    - @Html.SmartLabelFor(m => m.Pattern) - - @Html.TextBoxFor(m => m.Pattern, new { placeholder = ViewBag.DefaultPattern }) - @Html.ValidationMessageFor(m => m.Pattern) -
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml new file mode 100644 index 0000000000..d91ec16571 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml @@ -0,0 +1,272 @@ +@model FeedGoogleMerchantCenterModel +@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; + +@{ + 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.Raw(@T("Plugins.Feed.Froogle.AdminInstruction")) +
    +
    + @Html.Action("InfoProfile", "Export", new { systemName = GmcXmlExportProvider.SystemName, returnUrl = Request.RawUrl, area = "admin" }) +
    +
    +
    + + Google Merchant Center + +
    +
    + +

     

    + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + +
    + @(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 new file mode 100644 index 0000000000..c20ef9acd7 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProductEditTab.cshtml @@ -0,0 +1,192 @@ +@model SmartStore.GoogleMerchantCenter.Models.GoogleProductModel + +@{ + Layout = ""; +} + + + +@* VERY IMPORTANT for proper model binding *@ +@Html.Hidden("__Type__", Model.GetType().AssemblyQualifiedName) + +@Html.HiddenFor(m => m.ProductId) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(m => m.Export2) + + @Html.EditorFor(m => m.Export2) + @Html.ValidationMessageFor(m => m.Export2) +
    + @Html.SmartLabelFor(m => m.Taxonomy) + + @Html.TextBoxFor(m => m.Taxonomy, new { data_provide = "typeahead", placeholder = ViewBag.DefaultCategory, style = "width: 97%", autocomplete = "off" }) + @Html.ValidationMessageFor(m => m.Taxonomy) +
    + @Html.SmartLabelFor(m => m.Gender) + + @Html.DropDownListFor(m => m.Gender, (IEnumerable)ViewBag.AvailableGenders, (string)ViewBag.DefaultGender) + @Html.ValidationMessageFor(m => m.Gender) +
    + @Html.SmartLabelFor(m => m.AgeGroup) + + @Html.DropDownListFor(m => m.AgeGroup, (IEnumerable)ViewBag.AvailableAgeGroups, (string)ViewBag.DefaultAgeGroup) + @Html.ValidationMessageFor(m => m.AgeGroup) +
    + @Html.SmartLabelFor(m => m.IsAdult) + + @Html.EditorFor(m => m.IsAdult, new { placeholder = ViewBag.DefaultIsAdult }) + @Html.ValidationMessageFor(m => m.IsAdult) +
    + @Html.SmartLabelFor(m => m.Color) + + @Html.TextBoxFor(m => m.Color, new { placeholder = ViewBag.DefaultColor }) + @Html.ValidationMessageFor(m => m.Color) +
    + @Html.SmartLabelFor(m => m.Size) + + @Html.TextBoxFor(m => m.Size, new { placeholder = ViewBag.DefaultSize }) + @Html.ValidationMessageFor(m => m.Size) +
    + @Html.SmartLabelFor(m => m.Material) + + @Html.TextBoxFor(m => m.Material, new { placeholder = ViewBag.DefaultMaterial }) + @Html.ValidationMessageFor(m => m.Material) +
    + @Html.SmartLabelFor(m => m.Pattern) + + @Html.TextBoxFor(m => m.Pattern, new { placeholder = ViewBag.DefaultPattern }) + @Html.ValidationMessageFor(m => m.Pattern) +
    + @Html.SmartLabelFor(m => m.Multipack2) + + @Html.EditorFor(m => m.Multipack2, new { placeholder = ViewBag.DefaultMultipack2 }) + @Html.ValidationMessageFor(m => m.Multipack2) +
    + @Html.SmartLabelFor(m => m.IsBundle) + + @Html.EditorFor(m => m.IsBundle, new { placeholder = ViewBag.DefaultIsBundle }) + @Html.ValidationMessageFor(m => m.IsBundle) +
    + @Html.SmartLabelFor(m => m.EnergyEfficiencyClass) + + @Html.DropDownListFor(m => m.EnergyEfficiencyClass, (IEnumerable)ViewBag.AvailableEnergyEfficiencyClasses, (string)ViewBag.DefaultEnergyEfficiencyClass) + @Html.ValidationMessageFor(m => m.EnergyEfficiencyClass) +
    + @Html.SmartLabelFor(m => m.CustomLabel0) + + @Html.EditorFor(m => m.CustomLabel0, new { placeholder = ViewBag.DefaultCustomLabel }) + @Html.ValidationMessageFor(m => m.CustomLabel0) +
    + @Html.SmartLabelFor(m => m.CustomLabel1) + + @Html.EditorFor(m => m.CustomLabel1, new { placeholder = ViewBag.DefaultCustomLabel }) + @Html.ValidationMessageFor(m => m.CustomLabel1) +
    + @Html.SmartLabelFor(m => m.CustomLabel2) + + @Html.EditorFor(m => m.CustomLabel2, new { placeholder = ViewBag.DefaultCustomLabel }) + @Html.ValidationMessageFor(m => m.CustomLabel2) +
    + @Html.SmartLabelFor(m => m.CustomLabel3) + + @Html.EditorFor(m => m.CustomLabel3, new { placeholder = ViewBag.DefaultCustomLabel }) + @Html.ValidationMessageFor(m => m.CustomLabel3) +
    + @Html.SmartLabelFor(m => m.CustomLabel4) + + @Html.EditorFor(m => m.CustomLabel4, new { placeholder = ViewBag.DefaultCustomLabel }) + @Html.ValidationMessageFor(m => m.CustomLabel4) +
    \ 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 new file mode 100644 index 0000000000..5f41005fb9 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml @@ -0,0 +1,163 @@ +@using SmartStore.GoogleMerchantCenter.Providers +@using SmartStore.GoogleMerchantCenter.Models; +@model ProfileConfigurationModel +@{ + Layout = null; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(m => m.DefaultGoogleCategory) + + @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.ValidationMessageFor(m => m.DefaultGoogleCategory) +
    + @Html.SmartLabelFor(m => m.ExpirationDays) + + @Html.EditorFor(m => m.ExpirationDays) + @Html.ValidationMessageFor(m => m.ExpirationDays) +
    + @Html.SmartLabelFor(m => m.Condition) + + @Html.DropDownList("Condition", new List + { + new SelectListItem { Text = T("Common.Auto"), Value = "" }, + new SelectListItem { Text = T("Common.Unspecified"), Value = GmcXmlExportProvider.Unspecified }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.ConditionNew"), Value = "new" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.ConditionUsed"), Value = "used" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.ConditionRefurbished"), Value = "refurbished" } + }) + @Html.ValidationMessageFor(m => m.Condition) +
    + @Html.SmartLabelFor(m => m.Availability) + + @Html.DropDownList("Availability", new List + { + new SelectListItem { Text = T("Common.Auto"), Value = "" }, + new SelectListItem { Text = T("Common.Unspecified"), Value = GmcXmlExportProvider.Unspecified }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.AvailabilityInStock"), Value = "in stock" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.AvailabilityOutOfStock"), Value = "out of stock" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.AvailabilityPreorder"), Value = "preorder" } + }) + @Html.ValidationMessageFor(m => m.Availability) +
    + @Html.SmartLabelFor(m => m.Gender) + + @Html.DropDownList("Gender", new List + { + new SelectListItem { Text = T("Common.Auto"), Value = "" }, + new SelectListItem { Text = T("Common.Unspecified"), Value = GmcXmlExportProvider.Unspecified }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.GenderMale"), Value = "male" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.GenderFemale"), Value = "female" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.GenderUnisex"), Value = "unisex" } + }) + @Html.ValidationMessageFor(m => m.Gender) +
    + @Html.SmartLabelFor(m => m.AgeGroup) + + @Html.DropDownList("AgeGroup", new List + { + new SelectListItem { Text = T("Common.Auto"), Value = "" }, + new SelectListItem { Text = T("Common.Unspecified"), Value = GmcXmlExportProvider.Unspecified }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.AgeGroupAdult"), Value = "adult" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.AgeGroupKids"), Value = "kids" } + }) + @Html.ValidationMessageFor(m => m.AgeGroup) +
    + @Html.SmartLabelFor(m => m.Color) + + @Html.EditorFor(m => m.Color) + @Html.ValidationMessageFor(m => m.Color) +
    + @Html.SmartLabelFor(m => m.Size) + + @Html.EditorFor(m => m.Size) + @Html.ValidationMessageFor(m => m.Size) +
    + @Html.SmartLabelFor(m => m.Material) + + @Html.EditorFor(m => m.Material) + @Html.ValidationMessageFor(m => m.Material) +
    + @Html.SmartLabelFor(m => m.Pattern) + + @Html.EditorFor(m => m.Pattern) + @Html.ValidationMessageFor(m => m.Pattern) +
    + @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 31dc8df754..e9d36a3c51 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/Web.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md index 866e2c3ebf..2c1f7000cf 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md @@ -1,5 +1,39 @@ -#Release Notes# +#Release Notes -##Google Merchant Center (GMC) 2.31## -###Improvements### +##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 + +##Google Merchant Center (GMC) 2.2.0.5 +###New Features +* Supports GMC fields: multipack, bundle, adult, energy efficiency class and custom label (0 to 4) +* Export of availability date +###Improvements +* Removed "online_only" because it's not part of the GMC feed specification anymore + +##Google Merchant Center (GMC) 2.2.0.4 +###Improvements +* Supports new export framework +###Bugfixes +* Availability value "available for order" deprecated + +##Google Merchant Center (GMC) 2.2.0.3 +###Bugfixes +* Include\exclude option in product tab should initially be activated + +##Google Merchant Center (GMC) 2.2.0.2 +###Improvements +* Supporting of paged google-product data query for SQL-Server Compact Edition + +##Google Merchant Center (GMC) 2.2.0.1 +###New Features +* #582 Option to include\exclude a product + +##Google Merchant Center (GMC) 2.2.0 +###Improvements * Paged product query to reduce memory payload \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config b/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config index d2d548e91e..3527aa9a5c 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 b03b5655c0..ba87d0f098 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config @@ -1,117 +1,117 @@ - + - - + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs b/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs index 468940bc74..14ef7c989e 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs @@ -14,6 +14,7 @@ using SmartStore.Services.Stores; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; namespace SmartStore.OfflinePayment.Controllers @@ -33,14 +34,22 @@ public OfflinePaymentController( this._services = services; this._storeService = storeService; this._ctx = ctx; - - T = NullLocalizer.Instance; } - public Localizer T { get; set; } - #region Global + private List GetTransactModes() + { + var list = new List + { + new SelectListItem { Text = T("Enums.SmartStore.Core.Domain.Payments.PaymentStatus.Pending"), Value = ((int)TransactMode.Pending).ToString() }, + new SelectListItem { Text = T("Enums.SmartStore.Core.Domain.Payments.PaymentStatus.Authorized"), Value = ((int)TransactMode.Authorize).ToString() }, + new SelectListItem { Text = T("Enums.SmartStore.Core.Domain.Payments.PaymentStatus.Paid"), Value = ((int)TransactMode.Paid).ToString() } + }; + + return list; + } + [NonAction] private TModel ConfigureGet(Action fn = null) where TModel : ConfigurationModelBase, new() @@ -195,6 +204,10 @@ public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) paymentInfo.DirectDebitCountry = form["DirectDebitCountry"]; paymentInfo.DirectDebitIban = form["DirectDebitIban"]; } + else if (type == "PurchaseOrderNumber") + { + paymentInfo.PurchaseOrderNumber = form["PurchaseOrderNumber"]; + } } return paymentInfo; @@ -233,6 +246,10 @@ public override string GetPaymentSummary(FormCollection form) return number.Mask(8); } } + else if (type == "PurchaseOrderNumber") + { + return form["PurchaseOrderNumber"]; + } } return null; @@ -409,7 +426,17 @@ public ActionResult ManualConfigure() var model = ConfigureGet((m, s) => { m.TransactMode = s.TransactMode; - m.TransactModeValues = s.TransactMode.ToSelectList(); + m.TransactModeValues = GetTransactModes(); + m.ExcludedCreditCards = s.ExcludedCreditCards.SplitSafe(","); + + m.AvailableCreditCards = ManualProvider.CreditCardTypes + .Select(x => new SelectListItem + { + Text = x.Text, + Value = x.Value, + Selected = m.ExcludedCreditCards.Contains(x.Value) + }) + .ToList(); }); return View(model); @@ -424,8 +451,7 @@ public ActionResult ManualConfigure(ManualConfigurationModel model, FormCollecti ConfigurePost(model, form, s => { s.TransactMode = model.TransactMode; - - model.TransactModeValues = s.TransactMode.ToSelectList(); + s.ExcludedCreditCards = string.Join(",", model.ExcludedCreditCards ?? new string[0]); }); return ManualConfigure(); @@ -433,50 +459,35 @@ public ActionResult ManualConfigure(ManualConfigurationModel model, FormCollecti public ActionResult ManualPaymentInfo() { - var model = PaymentInfoGet(); - - // CC types - model.CreditCardTypes.Add(new SelectListItem() - { - Text = "Visa", - Value = "Visa", - }); - model.CreditCardTypes.Add(new SelectListItem() - { - Text = "Master card", - Value = "MasterCard", - }); - model.CreditCardTypes.Add(new SelectListItem() + var model = PaymentInfoGet((m, s) => { - Text = "Discover", - Value = "Discover", - }); - model.CreditCardTypes.Add(new SelectListItem() - { - Text = "Amex", - Value = "Amex", + var excludedCreditCards = s.ExcludedCreditCards.SplitSafe(","); + + foreach (var creditCard in ManualProvider.CreditCardTypes) + { + if (!excludedCreditCards.Any(x => x.IsCaseInsensitiveEqual(creditCard.Value))) + { + m.CreditCardTypes.Add(new SelectListItem + { + Text = creditCard.Text, + Value = creditCard.Value + }); + } + } }); // years for (int i = 0; i < 15; i++) { string year = Convert.ToString(DateTime.Now.Year + i); - model.ExpireYears.Add(new SelectListItem() - { - Text = year, - Value = year, - }); + model.ExpireYears.Add(new SelectListItem { Text = year, Value = year }); } // months for (int i = 1; i <= 12; i++) { string text = (i < 10) ? "0" + i.ToString() : i.ToString(); - model.ExpireMonths.Add(new SelectListItem() - { - Text = text, - Value = i.ToString(), - }); + model.ExpireMonths.Add(new SelectListItem { Text = text, Value = i.ToString() }); } // set postback values @@ -484,6 +495,7 @@ public ActionResult ManualPaymentInfo() model.CardholderName = form["CardholderName"]; model.CardNumber = form["CardNumber"]; model.CardCode = form["CardCode"]; + var selectedCcType = model.CreditCardTypes.Where(x => x.Value.Equals(form["CreditCardType"], StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); if (selectedCcType != null) selectedCcType.Selected = true; @@ -499,5 +511,38 @@ public ActionResult ManualPaymentInfo() #endregion - } + #region PurchaseOrderNumber + + [AdminAuthorize] + [ChildActionOnly] + public ActionResult PurchaseOrderNumberConfigure() + { + var model = ConfigureGet(); + + return View("GenericConfigure", model); + } + + [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + public ActionResult PurchaseOrderNumberConfigure(PurchaseOrderNumberConfigurationModel model, FormCollection form) + { + if (!ModelState.IsValid) + return InvoiceConfigure(); + + ConfigurePost(model, form); + + return PurchaseOrderNumberConfigure(); + } + + public ActionResult PurchaseOrderNumberPaymentInfo() + { + var model = PaymentInfoGet(); + + var form = this.GetPaymentData(); + model.PurchaseOrderNumber = form["PurchaseOrderNumber"]; + + return PartialView("PurchaseOrderNumberPaymentInfo", model); + } + + #endregion + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Description.txt b/src/Plugins/SmartStore.OfflinePayment/Description.txt index 66c5f6a545..db6f0241f9 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: 1.1 -MinAppVersion: 2.1.0 +Version: 2.6.0 +MinAppVersion: 2.5.0 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 2effbe1ecb..a9f2235ba1 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.OfflinePayment/Localization/resources.de-de.xml @@ -25,7 +25,7 @@ Zusätzliche prozentuale Gebühr zum Gesamtbetrag. Ein fester Wert wird verwendet, falls diese Option nicht aktiviert ist. - + Nachnahme @@ -122,10 +122,16 @@ - Markiere die Zahlung nach dem Abschluß der Bestellung als + Zahlungsstatus nach Bestellabschluss - Bestimmen Sie den Transaktionsmodus. + Legt den Zahlungsstatus nach Bestellabschluss fest. + + + Auszuschließende Kreditkarten + + + Kreditkarten mit denen Kunden nicht zahlen dürfen. @@ -211,5 +217,21 @@ + + + Bestellnummer + + + + + + + + + + Bestellnummer + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml b/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml index 7f21d92f8a..af3d0cc477 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml @@ -33,7 +33,7 @@ @@ -54,7 +54,7 @@ @@ -96,7 +96,7 @@ @@ -115,10 +115,16 @@ - After checkout mark payment as + Payment status after order completion - Specify transaction mode. + Specifies the payment status after order completion. + + + Excluded credit cards + + + Credit cards that customers may not pay with. @@ -137,7 +143,7 @@ @@ -207,5 +213,21 @@ + + + Purchase order + + + + + + + + + + PO Number + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs b/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs index 5a2b5690b5..ba59935fdb 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs @@ -2,7 +2,7 @@ using System.Web.Mvc; using SmartStore.OfflinePayment.Settings; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.OfflinePayment.Models { @@ -35,7 +35,11 @@ public class ManualConfigurationModel : ConfigurationModelBase { [SmartResourceDisplayName("Plugins.Payments.Manual.Fields.TransactMode")] public TransactMode TransactMode { get; set; } - public SelectList TransactModeValues { get; set; } + public List TransactModeValues { get; set; } + + [SmartResourceDisplayName("Plugins.Payments.Manual.ExcludedCreditCards")] + public string[] ExcludedCreditCards { get; set; } + public List AvailableCreditCards { get; set; } } public class PayInStoreConfigurationModel : ConfigurationModelBase @@ -45,4 +49,8 @@ public class PayInStoreConfigurationModel : ConfigurationModelBase public class PrepaymentConfigurationModel : ConfigurationModelBase { } + + public class PurchaseOrderNumberConfigurationModel : ConfigurationModelBase + { + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs b/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs index a48704604d..b13dd98de0 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.OfflinePayment.Models { @@ -97,4 +97,10 @@ public class PrepaymentPaymentInfoModel : PaymentInfoModelBase { } + public class PurchaseOrderNumberPaymentInfoModel : PaymentInfoModelBase + { + [SmartResourceDisplayName("Plugins.Payment.PurchaseOrder.PurchaseOrderNumber")] + [AllowHtml] + public string PurchaseOrderNumber { get; set; } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Plugin.cs b/src/Plugins/SmartStore.OfflinePayment/Plugin.cs index 1fd3c082ee..f496f22698 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Plugin.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Plugin.cs @@ -41,12 +41,18 @@ public override void Install() }); settings.SaveSetting(new ManualPaymentSettings { + DescriptionText = "@Plugins.Payments.Manual.PaymentInfoDescription", TransactMode = TransactMode.Pending }); settings.SaveSetting(new DirectDebitPaymentSettings { DescriptionText = "@Plugins.Payments.DirectDebit.PaymentInfoDescription" }); + settings.SaveSetting(new PurchaseOrderNumberPaymentSettings + { + DescriptionText = "@Plugins.Payments.PurchaseOrderNumber.PaymentInfoDescription", + TransactMode = TransactMode.Pending + }); // add resources loc.ImportPluginResourcesFromXml(this.PluginDescriptor); diff --git a/src/Plugins/SmartStore.OfflinePayment/Providers/ManualProvider.cs b/src/Plugins/SmartStore.OfflinePayment/Providers/ManualProvider.cs index edbfbb52d1..4f4331c2eb 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Providers/ManualProvider.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Providers/ManualProvider.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Web.Mvc; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Plugins; using SmartStore.OfflinePayment.Settings; @@ -10,12 +12,28 @@ namespace SmartStore.OfflinePayment [DisplayOrder(1)] public class ManualProvider : OfflinePaymentProviderBase, IConfigurable { + public static List CreditCardTypes + { + get + { + var creditCardTypes = new List + { + new SelectListItem { Text = "Visa", Value = "Visa" }, + new SelectListItem { Text = "Master Card", Value = "MasterCard" }, + new SelectListItem { Text = "Discover", Value = "Discover" }, + new SelectListItem { Text = "Amex", Value = "Amex" } + }; + return creditCardTypes; + } + } + public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) { var result = new ProcessPaymentResult(); var settings = CommonServices.Settings.LoadSetting(processPaymentRequest.StoreId); result.AllowStoringCreditCardNumber = true; + switch (settings.TransactMode) { case TransactMode.Pending: @@ -24,14 +42,12 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces case TransactMode.Authorize: result.NewPaymentStatus = PaymentStatus.Authorized; break; - case TransactMode.AuthorizeAndCapture: + case TransactMode.Paid: result.NewPaymentStatus = PaymentStatus.Paid; break; default: - { - result.AddError(T("Common.Payment.TranactionTypeNotSupported")); - return result; - } + result.AddError(T("Common.Payment.TranactionTypeNotSupported")); + return result; } return result; @@ -51,7 +67,7 @@ public override ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReque case TransactMode.Authorize: result.NewPaymentStatus = PaymentStatus.Authorized; break; - case TransactMode.AuthorizeAndCapture: + case TransactMode.Paid: result.NewPaymentStatus = PaymentStatus.Paid; break; default: diff --git a/src/Plugins/SmartStore.OfflinePayment/Providers/PurchaseOrderNumberProvider.cs b/src/Plugins/SmartStore.OfflinePayment/Providers/PurchaseOrderNumberProvider.cs new file mode 100644 index 0000000000..530dfd1d3a --- /dev/null +++ b/src/Plugins/SmartStore.OfflinePayment/Providers/PurchaseOrderNumberProvider.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Web.Routing; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Plugins; +using SmartStore.OfflinePayment.Settings; +using SmartStore.Services.Configuration; +using SmartStore.Services.Localization; +using SmartStore.Services.Payments; + +namespace SmartStore.OfflinePayment +{ + [SystemName("SmartStore.PurchaseOrderNumber")] + [FriendlyName("Purchase Order Number")] + [DisplayOrder(10)] + public class PurchaseOrderNumberProvider : OfflinePaymentProviderBase, IConfigurable + { + private readonly ISettingService _settingService; + private readonly ILocalizationService _localizationService; + + public PurchaseOrderNumberProvider(ISettingService settingService, ILocalizationService localizationService) + { + _settingService = settingService; + _localizationService = localizationService; + } + + public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) + { + var result = new ProcessPaymentResult(); + var settings = CommonServices.Settings.LoadSetting(processPaymentRequest.StoreId); + + result.AllowStoringCreditCardNumber = true; + switch (settings.TransactMode) + { + case TransactMode.Pending: + result.NewPaymentStatus = PaymentStatus.Pending; + break; + case TransactMode.Authorize: + result.NewPaymentStatus = PaymentStatus.Authorized; + break; + case TransactMode.Paid: + result.NewPaymentStatus = PaymentStatus.Paid; + break; + default: + { + result.AddError(T("Common.Payment.TranactionTypeNotSupported")); + return result; + } + } + + return result; + } + + public override bool RequiresInteraction + { + get + { + return true; + } + } + + protected override string GetActionPrefix() + { + return "PurchaseOrderNumber"; + } + + } +} diff --git a/src/Plugins/SmartStore.OfflinePayment/RouteProvider.cs b/src/Plugins/SmartStore.OfflinePayment/RouteProvider.cs index c0cf43ddd5..70a8e63318 100644 --- a/src/Plugins/SmartStore.OfflinePayment/RouteProvider.cs +++ b/src/Plugins/SmartStore.OfflinePayment/RouteProvider.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.OfflinePayment { diff --git a/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs b/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs index 7344b3ca56..52fbe9bc18 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using SmartStore.Core.Configuration; namespace SmartStore.OfflinePayment.Settings @@ -27,8 +25,14 @@ public class InvoicePaymentSettings : PaymentSettingsBase, ISettings public class ManualPaymentSettings : PaymentSettingsBase, ISettings { public TransactMode TransactMode { get; set; } + public string ExcludedCreditCards { get; set; } } + public class PurchaseOrderNumberPaymentSettings : PaymentSettingsBase, ISettings + { + public TransactMode TransactMode { get; set; } + } + public class PayInStorePaymentSettings : PaymentSettingsBase, ISettings { } @@ -40,19 +44,21 @@ public class PrepaymentPaymentSettings : PaymentSettingsBase, ISettings /// /// Represents manual payment processor transaction mode /// - public enum TransactMode : int + public enum TransactMode { /// /// Pending /// Pending = 0, + /// /// Authorize /// Authorize = 1, + /// - /// Authorize and capture + /// Paid /// - AuthorizeAndCapture = 2 + Paid = 2 } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj b/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj index 524ef57347..9083d62e99 100644 --- a/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj +++ b/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj @@ -1,5 +1,5 @@  - + @@ -20,7 +20,7 @@ Properties SmartStore.OfflinePayment SmartStore.OfflinePayment - v4.5 + v4.5.1 512 @@ -42,6 +42,7 @@ +
    true @@ -81,52 +82,54 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll - False + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - - ..\..\packages\FluentValidation.5.0.0.1\lib\Net40\FluentValidation.dll - False + + ..\..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True - + + + + + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - + @@ -142,6 +145,7 @@ + @@ -211,6 +215,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualConfigure.cshtml b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualConfigure.cshtml index a8bbca8460..89604eb07f 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualConfigure.cshtml +++ b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualConfigure.cshtml @@ -1,6 +1,5 @@ @model SmartStore.OfflinePayment.Models.ManualConfigurationModel @using SmartStore.Web.Framework; - @{ Layout = ""; } @@ -16,9 +15,19 @@
    @Html.SettingOverrideCheckbox(model => model.TransactMode) - @Html.DropDownList("TransactMode", Model.TransactModeValues) + @Html.DropDownListFor(model => model.TransactMode, Model.TransactModeValues)
    + @Html.SmartLabelFor(model => model.ExcludedCreditCards) + + @Html.SettingOverrideCheckbox(model => model.ExcludedCreditCards) + @Html.ListBoxFor(x => x.ExcludedCreditCards, new MultiSelectList(Model.AvailableCreditCards, "Value", "Text"), new { multiple = "multiple" }) + @Html.ValidationMessageFor(model => model.ExcludedCreditCards) +
    @Html.SmartLabelFor(model => model.AdditionalFee) diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualPaymentInfo.Mobile.cshtml b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualPaymentInfo.Mobile.cshtml index 49fa227e1e..dc1a947aab 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualPaymentInfo.Mobile.cshtml +++ b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualPaymentInfo.Mobile.cshtml @@ -7,7 +7,7 @@ @Html.Hidden("OfflinePaymentMethodType", "Manual") @Html.SmartLabelFor(model => model.CreditCardTypes, false) -@Html.DropDownListFor(model => model.CreditCardType, Model.CreditCardTypes) +@Html.DropDownListFor(model => model.CreditCardType, Model.CreditCardTypes, new { data_native_menu = "false" }) @Html.SmartLabelFor(model => model.CardholderName, false) @Html.TextBoxFor(model => model.CardholderName, new { autocomplete = "off" }) @Html.ValidationMessageFor(model => model.CardholderName) @@ -17,9 +17,9 @@ @Html.SmartLabelFor(model => model.ExpireMonth, false)
    - @Html.DropDownListFor(model => model.ExpireMonth, Model.ExpireMonths) + @Html.DropDownListFor(model => model.ExpireMonth, Model.ExpireMonths, new { data_native_menu = "false" }) / - @Html.DropDownListFor(model => model.ExpireYear, Model.ExpireYears) + @Html.DropDownListFor(model => model.ExpireYear, Model.ExpireYears, new { data_native_menu = "false" })
    @Html.SmartLabelFor(model => model.CardCode, false) diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/PurchaseOrderNumberPaymentInfo.Mobile.cshtml b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/PurchaseOrderNumberPaymentInfo.Mobile.cshtml new file mode 100644 index 0000000000..87191ec5e0 --- /dev/null +++ b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/PurchaseOrderNumberPaymentInfo.Mobile.cshtml @@ -0,0 +1,10 @@ +@{ + Layout = ""; +} +@using SmartStore.Web.Framework; +@using SmartStore.OfflinePayment.Models; +@model PurchaseOrderNumberPaymentInfoModel + +@Html.SmartLabelFor(model => model.PurchaseOrderNumber, false) +@Html.TextBoxFor(model => model.PurchaseOrderNumber, new { autocomplete = "off" }) +@Html.ValidationMessageFor(model => model.PurchaseOrderNumber) diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/PurchaseOrderNumberPaymentInfo.cshtml b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/PurchaseOrderNumberPaymentInfo.cshtml new file mode 100644 index 0000000000..0d21481f2d --- /dev/null +++ b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/PurchaseOrderNumberPaymentInfo.cshtml @@ -0,0 +1,20 @@ +@using SmartStore.Web.Framework; +@using SmartStore.OfflinePayment.Models; + +@model PurchaseOrderNumberPaymentInfoModel + +@{ + Layout = ""; +} + +@Html.Hidden("OfflinePaymentMethodType", "PurchaseOrderNumber") + +
    +
    + @Html.LabelFor(model => model.PurchaseOrderNumber, new { @class="control-label required" }) +
    + @Html.TextBoxFor(model => model.PurchaseOrderNumber, new { style = "width: 165px;", autocomplete = "off" }) + @Html.ValidationMessageFor(model => model.PurchaseOrderNumber) +
    +
    +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/Web.config b/src/Plugins/SmartStore.OfflinePayment/Views/Web.config index 31dc8df754..e9d36a3c51 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Views/Web.config +++ b/src/Plugins/SmartStore.OfflinePayment/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.OfflinePayment/changelog.md b/src/Plugins/SmartStore.OfflinePayment/changelog.md index b577e2d27c..a7d6c374d2 100644 --- a/src/Plugins/SmartStore.OfflinePayment/changelog.md +++ b/src/Plugins/SmartStore.OfflinePayment/changelog.md @@ -1,5 +1,9 @@ -#Release Notes# +#Release Notes -##Offline Payment Methods 1.1## -###Improvements### +##Offline Payment Methods 2.5.0 +###New Features +* Option to exclude credit card types + +##Offline Payment Methods 1.1 +###Improvements * Multistore configuration \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/packages.config b/src/Plugins/SmartStore.OfflinePayment/packages.config index 5d16a91643..5669aa5ceb 100644 --- a/src/Plugins/SmartStore.OfflinePayment/packages.config +++ b/src/Plugins/SmartStore.OfflinePayment/packages.config @@ -1,10 +1,10 @@  - - - - - + + + + + - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/web.config b/src/Plugins/SmartStore.OfflinePayment/web.config index b03b5655c0..ba87d0f098 100644 --- a/src/Plugins/SmartStore.OfflinePayment/web.config +++ b/src/Plugins/SmartStore.OfflinePayment/web.config @@ -1,117 +1,117 @@ - + - - + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.PayPal/Content/favicon.png b/src/Plugins/SmartStore.PayPal/Content/favicon.png new file mode 100644 index 0000000000..aa29944a07 Binary files /dev/null and b/src/Plugins/SmartStore.PayPal/Content/favicon.png differ diff --git a/src/Plugins/SmartStore.PayPal/Content/images/logo200x53.png b/src/Plugins/SmartStore.PayPal/Content/images/logo200x53.png deleted file mode 100644 index a23fcb7a0b..0000000000 Binary files a/src/Plugins/SmartStore.PayPal/Content/images/logo200x53.png and /dev/null differ diff --git a/src/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css b/src/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css index eb06a6cd69..06e549454c 100644 --- a/src/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css +++ b/src/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css @@ -1,7 +1,7 @@ #paypal-checkout .selection-text { text-align: right; font-weight: bold; - padding: 12px 15px 5px 0; + padding: 12px 15px 0 0; } #paypal-checkout .form-horizontal { @@ -11,19 +11,9 @@ #paypal-checkout #paypal-express-button { cursor: pointer; } -.configure-paypal-direct .logo, -.configure-paypal-express .logo, -.configure-paypal-standard .logo -.paypal-standard-public .logo{ - width: 200px; - height: 53px; -} .paypal-standard-public { margin-bottom: 10px; } -.button-save { - padding-top: 10px; -} .form-horizontal.paypal-direct #s2id_CreditCardType{ min-width: 180px; } diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalControllerBase.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalControllerBase.cs new file mode 100644 index 0000000000..06d2c94600 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalControllerBase.cs @@ -0,0 +1,325 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Web; +using System.Web.Mvc; +using SmartStore.Core.Configuration; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Logging; +using SmartStore.PayPal.Settings; +using SmartStore.Services.Orders; +using SmartStore.Services.Payments; +using SmartStore.Web.Framework.Controllers; + +namespace SmartStore.PayPal.Controllers +{ + public abstract class PayPalControllerBase : PaymentControllerBase where TSetting : PayPalSettingsBase, ISettings, new() + { + public PayPalControllerBase( + string systemName, + IPaymentService paymentService, + IOrderService orderService, + IOrderProcessingService orderProcessingService) + { + SystemName = systemName; + PaymentService = paymentService; + OrderService = orderService; + OrderProcessingService = orderProcessingService; + } + + protected string SystemName { get; private set; } + protected IPaymentService PaymentService { get; private set; } + protected IOrderService OrderService { get; private set; } + protected IOrderProcessingService OrderProcessingService { get; private set; } + + protected PaymentStatus GetPaymentStatus(string paymentStatus, string pendingReason, decimal payPalTotal, decimal orderTotal) + { + var result = PaymentStatus.Pending; + + if (paymentStatus == null) + paymentStatus = string.Empty; + + if (pendingReason == null) + pendingReason = string.Empty; + + switch (paymentStatus.ToLowerInvariant()) + { + case "pending": + switch (pendingReason.ToLowerInvariant()) + { + case "authorization": + result = PaymentStatus.Authorized; + break; + default: + result = PaymentStatus.Pending; + break; + } + break; + case "processed": + case "completed": + case "canceled_reversal": + result = PaymentStatus.Paid; + break; + case "denied": + case "expired": + case "failed": + case "voided": + result = PaymentStatus.Voided; + break; + case "reversed": + result = PaymentStatus.Refunded; + break; + case "refunded": + if ((Math.Abs(orderTotal) - Math.Abs(payPalTotal)) > decimal.Zero) + result = PaymentStatus.PartiallyRefunded; + else + result = PaymentStatus.Refunded; + break; + default: + break; + } + return result; + } + + protected bool VerifyIPN(PayPalSettingsBase settings, string formString, out Dictionary values) + { + // settings: multistore context not possible here. we need the custom value to determine what store it is. + + var request = settings.GetPayPalWebRequest(); + request.Method = "POST"; + request.ContentType = "application/x-www-form-urlencoded"; + request.UserAgent = Request.UserAgent; + + var formContent = string.Format("{0}&cmd=_notify-validate", formString); + request.ContentLength = formContent.Length; + + using (var sw = new StreamWriter(request.GetRequestStream(), Encoding.ASCII)) + { + sw.Write(formContent); + } + + string response = null; + using (var sr = new StreamReader(request.GetResponse().GetResponseStream())) + { + response = HttpUtility.UrlDecode(sr.ReadToEnd()); + } + + var success = response.Trim().Equals("VERIFIED", StringComparison.OrdinalIgnoreCase); + + values = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var item in formString.SplitSafe("&")) + { + var line = HttpUtility.UrlDecode(item).TrimSafe(); + var equalIndex = line.IndexOf('='); + + if (equalIndex >= 0) + values.Add(line.Substring(0, equalIndex), line.Substring(equalIndex + 1)); + } + + return success; + } + + [ValidateInput(false)] + public ActionResult IPNHandler() + { + if (!PaymentService.IsPaymentMethodActive(SystemName, Services.StoreContext.CurrentStore.Id)) + throw new SmartException(T("Plugins.Payments.PayPal.NoModuleLoading")); + + var settings = Services.Settings.LoadSetting(); + byte[] param = Request.BinaryRead(Request.ContentLength); + //var strRequest = Encoding.ASCII.GetString(param); + var strRequest = Encoding.UTF8.GetString(param); + Dictionary values; + var sb = new StringBuilder(); + + if (VerifyIPN(settings, strRequest, out values)) + { + #region values + + decimal total = decimal.Zero; + try + { + total = decimal.Parse(values["mc_gross"], new CultureInfo("en-US")); + } + catch { } + + string payer_status = string.Empty; + values.TryGetValue("payer_status", out payer_status); + string payment_status = string.Empty; + values.TryGetValue("payment_status", out payment_status); + string pending_reason = string.Empty; + values.TryGetValue("pending_reason", out pending_reason); + string mc_currency = string.Empty; + values.TryGetValue("mc_currency", out mc_currency); + string txn_id = string.Empty; + values.TryGetValue("txn_id", out txn_id); + string txn_type = string.Empty; + values.TryGetValue("txn_type", out txn_type); + string rp_invoice_id = string.Empty; + values.TryGetValue("rp_invoice_id", out rp_invoice_id); + string payment_type = string.Empty; + values.TryGetValue("payment_type", out payment_type); + string payer_id = string.Empty; + values.TryGetValue("payer_id", out payer_id); + string receiver_id = string.Empty; + values.TryGetValue("receiver_id", out receiver_id); + string invoice = string.Empty; + values.TryGetValue("invoice", out invoice); + string payment_fee = string.Empty; + values.TryGetValue("payment_fee", out payment_fee); + + #endregion + + sb.AppendLine("PayPal IPN:"); + foreach (KeyValuePair kvp in values.Where(x => x.Value.HasValue())) + { + sb.AppendLine(kvp.Key + ": " + kvp.Value); + } + + switch (txn_type) + { + case "recurring_payment_profile_created": + //do nothing here + break; + case "recurring_payment": + #region Recurring payment + { + Guid orderNumberGuid = Guid.Empty; + try + { + orderNumberGuid = new Guid(rp_invoice_id); + } + catch { } + + var initialOrder = OrderService.GetOrderByGuid(orderNumberGuid); + if (initialOrder != null) + { + var newPaymentStatus = GetPaymentStatus(payment_status, pending_reason, total, initialOrder.OrderTotal); + var recurringPayments = OrderService.SearchRecurringPayments(0, 0, initialOrder.Id, null); + + foreach (var rp in recurringPayments) + { + switch (newPaymentStatus) + { + case PaymentStatus.Authorized: + case PaymentStatus.Paid: + { + var recurringPaymentHistory = rp.RecurringPaymentHistory; + if (recurringPaymentHistory.Count == 0) + { + //first payment + var rph = new RecurringPaymentHistory + { + RecurringPaymentId = rp.Id, + OrderId = initialOrder.Id, + CreatedOnUtc = DateTime.UtcNow + }; + rp.RecurringPaymentHistory.Add(rph); + OrderService.UpdateRecurringPayment(rp); + } + else + { + //next payments + OrderProcessingService.ProcessNextRecurringPayment(rp); + } + } + break; + } + } + + Logger.Information(T("Plugins.Payments.PayPal.IpnRecurringPaymentInfo"), new SmartException(sb.ToString())); + } + else + { + Logger.Error(T("Plugins.Payments.PayPal.IpnOrderNotFound"), new SmartException(sb.ToString())); + } + } + #endregion + break; + default: + #region Standard payment + { + var orderNumber = ""; + var orderNumberGuid = Guid.Empty; + values.TryGetValue("custom", out orderNumber); + + try + { + orderNumberGuid = new Guid(orderNumber); + } + catch { } + + var order = OrderService.GetOrderByGuid(orderNumberGuid); + if (order != null) + { + order.HasNewPaymentNotification = true; + + OrderService.AddOrderNote(order, sb.ToString()); + + if (settings.IpnChangesPaymentStatus) + { + var newPaymentStatus = GetPaymentStatus(payment_status, pending_reason, total, order.OrderTotal); + + switch (newPaymentStatus) + { + case PaymentStatus.Pending: + break; + case PaymentStatus.Authorized: + if (OrderProcessingService.CanMarkOrderAsAuthorized(order)) + { + OrderProcessingService.MarkAsAuthorized(order); + } + break; + case PaymentStatus.Paid: + if (OrderProcessingService.CanMarkOrderAsPaid(order)) + { + OrderProcessingService.MarkOrderAsPaid(order); + } + break; + case PaymentStatus.Refunded: + if (OrderProcessingService.CanRefundOffline(order)) + { + OrderProcessingService.RefundOffline(order); + } + break; + case PaymentStatus.PartiallyRefunded: + if (OrderProcessingService.CanPartiallyRefundOffline(order, Math.Abs(total))) + { + OrderProcessingService.PartiallyRefundOffline(order, Math.Abs(total)); + } + break; + case PaymentStatus.Voided: + if (OrderProcessingService.CanVoidOffline(order)) + { + OrderProcessingService.VoidOffline(order); + } + break; + default: + break; + } + } + } + else + { + Logger.Error(T("Plugins.Payments.PayPal.IpnOrderNotFound"), new SmartException(sb.ToString())); + } + } + #endregion + break; + } + } + else + { + Logger.Error(T("Plugins.Payments.PayPal.IpnFailed"), new SmartException(strRequest)); + } + + //nothing should be rendered to visitor + return Content(""); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs index 1103b9365f..abdf2df189 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs @@ -1,14 +1,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; using System.Linq; -using System.Text; using System.Web.Mvc; -using Autofac; -using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; -using SmartStore.Core.Logging; using SmartStore.PayPal.Models; using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; @@ -16,59 +10,57 @@ using SmartStore.Services; using SmartStore.Services.Orders; using SmartStore.Services.Payments; -using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; namespace SmartStore.PayPal.Controllers { - public class PayPalDirectController : PaymentControllerBase + public class PayPalDirectController : PayPalControllerBase { - private readonly PluginHelper _helper; - private readonly IPaymentService _paymentService; - private readonly IOrderService _orderService; - private readonly IOrderProcessingService _orderProcessingService; private readonly ICommonServices _services; - private readonly IStoreService _storeService; public PayPalDirectController( - IPaymentService paymentService, IOrderService orderService, + IPaymentService paymentService, + IOrderService orderService, IOrderProcessingService orderProcessingService, PaymentSettings paymentSettings, - IComponentContext ctx, ICommonServices services, - IStoreService storeService) + ICommonServices services) : base( + PayPalDirectProvider.SystemName, + paymentService, + orderService, + orderProcessingService) { - _paymentService = paymentService; - _orderService = orderService; - _orderProcessingService = orderProcessingService; _services = services; - _storeService = storeService; - _helper = new PluginHelper(ctx, "SmartStore.PayPal", "Plugins.Payments.PayPalDirect"); } private SelectList TransactModeValues(TransactMode selected) { - return new SelectList(new List() + return new SelectList(new List { - new { ID = (int)TransactMode.Authorize, Name = _helper.GetResource("ModeAuth") }, - new { ID = (int)TransactMode.AuthorizeAndCapture, Name = _helper.GetResource("ModeAuthAndCapture") } - }, "ID", "Name", (int)selected); + new { ID = (int)TransactMode.Authorize, Name = T("Plugins.Payments.PayPalDirect.ModeAuth") }, + new { ID = (int)TransactMode.AuthorizeAndCapture, Name = T("Plugins.Payments.PayPalDirect.ModeAuthAndCapture") } + }, + "ID", "Name", (int)selected); } [AdminAuthorize, ChildActionOnly] public ActionResult Configure() { var model = new PayPalDirectConfigurationModel(); - 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); model.Copy(settings, true); - model.TransactModeValues = TransactModeValues(settings.TransactMode); + model.TransactModeValues = TransactModeValues(settings.TransactMode); - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, _services.Settings); + model.AvailableSecurityProtocols = PayPalService.GetSecurityProtocols() + .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) + .ToList(); + + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, Services.Settings); return View(model); } @@ -82,18 +74,18 @@ public ActionResult Configure(PayPalDirectConfigurationModel model, FormCollecti 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); model.Copy(settings, false); - storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, _services.Settings); + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); // multistore context not possible, see IPN handling - _services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); - _services.Settings.ClearCache(); - NotifySuccess(_services.Localization.GetResource("Plugins.Payments.PayPal.ConfigSaveNote")); + Services.Settings.ClearCache(); + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); return Configure(); } @@ -103,22 +95,22 @@ public ActionResult PaymentInfo() var model = new PayPalDirectPaymentInfoModel(); //CC types - model.CreditCardTypes.Add(new SelectListItem() + model.CreditCardTypes.Add(new SelectListItem { Text = "Visa", Value = "Visa", }); - model.CreditCardTypes.Add(new SelectListItem() + model.CreditCardTypes.Add(new SelectListItem { Text = "Master card", Value = "MasterCard", }); - model.CreditCardTypes.Add(new SelectListItem() + model.CreditCardTypes.Add(new SelectListItem { Text = "Discover", Value = "Discover", }); - model.CreditCardTypes.Add(new SelectListItem() + model.CreditCardTypes.Add(new SelectListItem { Text = "Amex", Value = "Amex", @@ -128,7 +120,7 @@ public ActionResult PaymentInfo() for (int i = 0; i < 15; i++) { string year = Convert.ToString(DateTime.Now.Year + i); - model.ExpireYears.Add(new SelectListItem() + model.ExpireYears.Add(new SelectListItem { Text = year, Value = year, @@ -139,7 +131,7 @@ public ActionResult PaymentInfo() for (int i = 1; i <= 12; i++) { string text = (i < 10) ? "0" + i.ToString() : i.ToString(); - model.ExpireMonths.Add(new SelectListItem() + model.ExpireMonths.Add(new SelectListItem { Text = text, Value = i.ToString(), @@ -151,6 +143,7 @@ public ActionResult PaymentInfo() model.CardholderName = form["CardholderName"]; model.CardNumber = form["CardNumber"]; model.CardCode = form["CardCode"]; + var selectedCcType = model.CreditCardTypes.Where(x => x.Value.Equals(form["CreditCardType"], StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); if (selectedCcType != null) selectedCcType.Selected = true; @@ -168,10 +161,9 @@ public ActionResult PaymentInfo() public override IList ValidatePaymentForm(FormCollection form) { var warnings = new List(); + var validator = new PaymentInfoValidator(Services.Localization); - //validate - var validator = new PaymentInfoValidator(_services.Localization); - var model = new PayPalDirectPaymentInfoModel() + var model = new PayPalDirectPaymentInfoModel { CardholderName = form["CardholderName"], CardNumber = form["CardNumber"], @@ -181,9 +173,10 @@ public override IList ValidatePaymentForm(FormCollection form) }; var validationResult = validator.Validate(model); - if (!validationResult.IsValid) - foreach (var error in validationResult.Errors) - warnings.Add(error.ErrorMessage); + if (!validationResult.IsValid) + { + validationResult.Errors.Each(x => warnings.Add(x.ErrorMessage)); + } return warnings; } @@ -191,12 +184,14 @@ public override IList ValidatePaymentForm(FormCollection form) public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) { var paymentInfo = new ProcessPaymentRequest(); + 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.CreditCardCvv2 = form["CardCode"]; + return paymentInfo; } @@ -204,220 +199,12 @@ public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) public override string GetPaymentSummary(FormCollection form) { var number = form["CardNumber"]; - return "{0}, {1}, {2}".FormatCurrent( + + return "{0}, {1}, {2}".FormatInvariant( form["CreditCardType"], form["CardholderName"], number.Mask(4) ); } - - [ValidateInput(false)] - public ActionResult IPNHandler() - { - Debug.WriteLine("PayPal Direct IPN: {0}".FormatWith(Request.ContentLength)); - - byte[] param = Request.BinaryRead(Request.ContentLength); - string strRequest = Encoding.ASCII.GetString(param); - Dictionary values; - - var provider = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalDirect", true); - var processor = provider != null ? provider.Value as PayPalDirectProvider : null; - if (processor == null) - throw new SmartException(_helper.GetResource("NoModuleLoading")); - - if (processor.VerifyIPN(strRequest, out values)) - { - #region values - decimal total = decimal.Zero; - try - { - total = decimal.Parse(values["mc_gross"], new CultureInfo("en-US")); - } - catch { } - - string payer_status = string.Empty; - values.TryGetValue("payer_status", out payer_status); - string payment_status = string.Empty; - values.TryGetValue("payment_status", out payment_status); - string pending_reason = string.Empty; - values.TryGetValue("pending_reason", out pending_reason); - string mc_currency = string.Empty; - values.TryGetValue("mc_currency", out mc_currency); - string txn_id = string.Empty; - values.TryGetValue("txn_id", out txn_id); - string txn_type = string.Empty; - values.TryGetValue("txn_type", out txn_type); - string rp_invoice_id = string.Empty; - values.TryGetValue("rp_invoice_id", out rp_invoice_id); - string payment_type = string.Empty; - values.TryGetValue("payment_type", out payment_type); - string payer_id = string.Empty; - values.TryGetValue("payer_id", out payer_id); - string receiver_id = string.Empty; - values.TryGetValue("receiver_id", out receiver_id); - string invoice = string.Empty; - values.TryGetValue("invoice", out invoice); - string payment_fee = string.Empty; - values.TryGetValue("payment_fee", out payment_fee); - - #endregion - - var sb = new StringBuilder(); - sb.AppendLine("PayPal IPN:"); - foreach (KeyValuePair kvp in values) - { - sb.AppendLine(kvp.Key + ": " + kvp.Value); - } - - var newPaymentStatus = PayPalHelper.GetPaymentStatus(payment_status, pending_reason); - sb.AppendLine("{0}: {1}".FormatWith(_helper.GetResource("NewPaymentStatus"), newPaymentStatus)); - - switch (txn_type) - { - case "recurring_payment_profile_created": - //do nothing here - break; - case "recurring_payment": - #region Recurring payment - { - Guid orderNumberGuid = Guid.Empty; - try - { - orderNumberGuid = new Guid(rp_invoice_id); - } - catch - { - } - - var initialOrder = _orderService.GetOrderByGuid(orderNumberGuid); - if (initialOrder != null) - { - var recurringPayments = _orderService.SearchRecurringPayments(0, 0, initialOrder.Id, null); - foreach (var rp in recurringPayments) - { - switch (newPaymentStatus) - { - case PaymentStatus.Authorized: - case PaymentStatus.Paid: - { - var recurringPaymentHistory = rp.RecurringPaymentHistory; - if (recurringPaymentHistory.Count == 0) - { - //first payment - var rph = new RecurringPaymentHistory() - { - RecurringPaymentId = rp.Id, - OrderId = initialOrder.Id, - CreatedOnUtc = DateTime.UtcNow - }; - rp.RecurringPaymentHistory.Add(rph); - _orderService.UpdateRecurringPayment(rp); - } - else - { - //next payments - _orderProcessingService.ProcessNextRecurringPayment(rp); - } - } - break; - } - } - - //this.OrderService.InsertOrderNote(newOrder.OrderId, sb.ToString(), DateTime.UtcNow); - Logger.Information(_helper.GetResource("IpnLogInfo"), new SmartException(sb.ToString())); - } - else - { - Logger.Error(_helper.GetResource("IpnOrderNotFound"), new SmartException(sb.ToString())); - } - } - #endregion - break; - default: - #region Standard payment - { - string orderNumber = string.Empty; - values.TryGetValue("custom", out orderNumber); - Guid orderNumberGuid = Guid.Empty; - try - { - orderNumberGuid = new Guid(orderNumber); - } - catch - { - } - - var order = _orderService.GetOrderByGuid(orderNumberGuid); - if (order != null) - { - - //order note - order.OrderNotes.Add(new OrderNote() - { - Note = sb.ToString(), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - switch (newPaymentStatus) - { - case PaymentStatus.Pending: - { - } - break; - case PaymentStatus.Authorized: - { - if (_orderProcessingService.CanMarkOrderAsAuthorized(order)) - { - _orderProcessingService.MarkAsAuthorized(order); - } - } - break; - case PaymentStatus.Paid: - { - if (_orderProcessingService.CanMarkOrderAsPaid(order)) - { - _orderProcessingService.MarkOrderAsPaid(order); - } - } - break; - case PaymentStatus.Refunded: - { - if (_orderProcessingService.CanRefundOffline(order)) - { - _orderProcessingService.RefundOffline(order); - } - } - break; - case PaymentStatus.Voided: - { - if (_orderProcessingService.CanVoidOffline(order)) - { - _orderProcessingService.VoidOffline(order); - } - } - break; - default: - break; - } - } - else - { - Logger.Error(_helper.GetResource("IpnOrderNotFound"), new SmartException(sb.ToString())); - } - } - #endregion - break; - } - } - else - { - Logger.Error(_helper.GetResource("IpnFailed"), new SmartException(strRequest)); - } - - //nothing should be rendered to visitor - return Content(""); - } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs index 7ac577fcc6..776d1a210a 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs @@ -1,114 +1,121 @@ using System; using System.Collections.Generic; -using System.Globalization; +using System.Linq; +using System.Net; using System.Text; using System.Web.Mvc; -using Autofac; using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Localization; -using SmartStore.Core.Logging; using SmartStore.PayPal.Models; using SmartStore.PayPal.PayPalSvc; using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.PayPal.Validators; -using SmartStore.Services; using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Directory; -using SmartStore.Services.Localization; using SmartStore.Services.Orders; using SmartStore.Services.Payments; -using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; namespace SmartStore.PayPal.Controllers { - public class PayPalExpressController : PaymentControllerBase + public class PayPalExpressController : PayPalControllerBase { - private readonly PluginHelper _helper; - private readonly IPaymentService _paymentService; - private readonly IOrderService _orderService; - private readonly IOrderProcessingService _orderProcessingService; - private readonly ILogger _logger; - private readonly PaymentSettings _paymentSettings; - private readonly ILocalizationService _localizationService; private readonly OrderSettings _orderSettings; private readonly ICurrencyService _currencyService; - private readonly CurrencySettings _currencySettings; private readonly IOrderTotalCalculationService _orderTotalCalculationService; private readonly ICustomerService _customerService; private readonly IGenericAttributeService _genericAttributeService; - private readonly ICommonServices _services; - private readonly IStoreService _storeService; public PayPalExpressController( - IPaymentService paymentService, IOrderService orderService, + IPaymentService paymentService, + IOrderService orderService, IOrderProcessingService orderProcessingService, - ILogger logger, - PaymentSettings paymentSettings, ILocalizationService localizationService, OrderSettings orderSettings, - ICurrencyService currencyService, CurrencySettings currencySettings, - IOrderTotalCalculationService orderTotalCalculationService, ICustomerService customerService, - IGenericAttributeService genericAttributeService, - IComponentContext ctx, ICommonServices services, - IStoreService storeService) + ICurrencyService currencyService, + IOrderTotalCalculationService orderTotalCalculationService, + ICustomerService customerService, + IGenericAttributeService genericAttributeService) : base( + PayPalExpressProvider.SystemName, + paymentService, + orderService, + orderProcessingService) { - _paymentService = paymentService; - _orderService = orderService; - _orderProcessingService = orderProcessingService; - _logger = logger; - _paymentSettings = paymentSettings; - _localizationService = localizationService; _orderSettings = orderSettings; _currencyService = currencyService; - _currencySettings = currencySettings; _orderTotalCalculationService = orderTotalCalculationService; _customerService = customerService; _genericAttributeService = genericAttributeService; - _services = services; - _storeService = storeService; - - _helper = new PluginHelper(ctx, "SmartStore.PayPal", "Plugins.Payments.PayPalExpress"); - - T = NullLocalizer.Instance; } - public Localizer T + private SelectList TransactModeValues(TransactMode selected) { - get; - set; + return new SelectList(new List + { + new { ID = (int)TransactMode.Authorize, Name = T("Plugins.Payments.PayPalExpress.ModeAuth") }, + new { ID = (int)TransactMode.AuthorizeAndCapture, Name = T("Plugins.Payments.PayPalExpress.ModeAuthAndCapture") } + }, + "ID", "Name", (int)selected); } - public SelectList TransactModeValues(TransactMode selected) + private string GetCheckoutButtonUrl(PayPalExpressPaymentSettings settings) { - return new SelectList(new List() { - new { ID = (int)TransactMode.Authorize, Name = _helper.GetResource("ModeAuth") }, - new { ID = (int)TransactMode.AuthorizeAndCapture, Name = _helper.GetResource("ModeAuthAndCapture") } - }, "ID", "Name", (int)selected); + const string expressCheckoutButton = "https://www.paypalobjects.com/{0}/i/btn/btn_xpressCheckout.gif"; + + HttpWebResponse response = null; + var culture = Services.WorkContext.WorkingLanguage.LanguageCulture; + + if (settings.SecurityProtocol.HasValue) + { + ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; + } + + var buttonUrl = expressCheckoutButton.FormatInvariant(culture.Replace("-", "_")); + var request = (HttpWebRequest)WebRequest.Create(buttonUrl); + request.Method = "HEAD"; + + try + { + response = (HttpWebResponse)request.GetResponse(); + return buttonUrl; + } + catch (WebException) + { + /* A WebException will be thrown if the status of the response is not `200 OK` */ + return expressCheckoutButton.FormatInvariant("en_US"); + } + finally + { + if (response != null) + { + response.Close(); + } + } } [AdminAuthorize, ChildActionOnly] public ActionResult Configure() { var model = new PayPalExpressConfigurationModel(); - 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); model.Copy(settings, true); model.TransactModeValues = TransactModeValues(settings.TransactMode); - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, _services.Settings); + model.AvailableSecurityProtocols = PayPalService.GetSecurityProtocols() + .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) + .ToList(); + + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, Services.Settings); return View(model); } @@ -122,263 +129,68 @@ public ActionResult Configure(PayPalExpressConfigurationModel model, FormCollect 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); model.Copy(settings, false); - storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, _services.Settings); + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); // multistore context not possible, see IPN handling - _services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); - _services.Settings.ClearCache(); - NotifySuccess(_services.Localization.GetResource("Plugins.Payments.PayPal.ConfigSaveNote")); + Services.Settings.ClearCache(); + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); return Configure(); } public ActionResult PaymentInfo() { - var model = new PayPalExpressPaymentInfoModel(); - model.CurrentPageIsBasket = PayPalHelper.CurrentPageIsBasket(this.ControllerContext.ParentActionViewContext.RequestContext.RouteData); + model.CurrentPageIsBasket = ControllerContext.ParentActionViewContext.RequestContext.RouteData.IsRouteEqual("ShoppingCart", "Cart"); if (model.CurrentPageIsBasket) { - var culture = _services.WorkContext.WorkingLanguage.LanguageCulture; - var buttonUrl = "https://www.paypalobjects.com/{0}/i/btn/btn_xpressCheckout.gif".FormatWith(culture.Replace("-", "_")); - model.SubmitButtonImageUrl = PayPalHelper.CheckIfButtonExists(buttonUrl); + var settings = Services.Settings.LoadSetting(Services.StoreContext.CurrentStore.Id); + + model.SubmitButtonImageUrl = GetCheckoutButtonUrl(settings); } return PartialView(model); } - [ValidateInput(false)] - public ActionResult IPNHandler() + public ActionResult MiniShoppingCart() { - byte[] param = Request.BinaryRead(Request.ContentLength); - string strRequest = Encoding.ASCII.GetString(param); - Dictionary values; + var settings = Services.Settings.LoadSetting(Services.StoreContext.CurrentStore.Id); - var provider = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalExpress", true); - var processor = provider != null ? provider.Value as PayPalExpress : null; - if (processor == null) - throw new SmartException(T("PayPal Express module cannot be loaded")); - - if (processor.VerifyIPN(strRequest, out values)) + if (settings.ShowButtonInMiniShoppingCart) { - #region values - decimal total = decimal.Zero; - try - { - total = decimal.Parse(values["mc_gross"], new CultureInfo("en-US")); - } - catch { } - - string payer_status = string.Empty; - values.TryGetValue("payer_status", out payer_status); - string payment_status = string.Empty; - values.TryGetValue("payment_status", out payment_status); - string pending_reason = string.Empty; - values.TryGetValue("pending_reason", out pending_reason); - string mc_currency = string.Empty; - values.TryGetValue("mc_currency", out mc_currency); - string txn_id = string.Empty; - values.TryGetValue("txn_id", out txn_id); - string txn_type = string.Empty; - values.TryGetValue("txn_type", out txn_type); - string rp_invoice_id = string.Empty; - values.TryGetValue("rp_invoice_id", out rp_invoice_id); - string payment_type = string.Empty; - values.TryGetValue("payment_type", out payment_type); - string payer_id = string.Empty; - values.TryGetValue("payer_id", out payer_id); - string receiver_id = string.Empty; - values.TryGetValue("receiver_id", out receiver_id); - string invoice = string.Empty; - values.TryGetValue("invoice", out invoice); - string payment_fee = string.Empty; - values.TryGetValue("payment_fee", out payment_fee); - - #endregion - - var sb = new StringBuilder(); - sb.AppendLine("Paypal IPN:"); - foreach (KeyValuePair kvp in values) - { - sb.AppendLine(kvp.Key + ": " + kvp.Value); - } - - var newPaymentStatus = PayPalHelper.GetPaymentStatus(payment_status, pending_reason); - sb.AppendLine("New payment status: " + newPaymentStatus); + var model = new PayPalExpressPaymentInfoModel(); + model.SubmitButtonImageUrl = GetCheckoutButtonUrl(settings); - switch (txn_type) - { - case "recurring_payment_profile_created": - //do nothing here - break; - case "recurring_payment": - #region Recurring payment - { - Guid orderNumberGuid = Guid.Empty; - try - { - orderNumberGuid = new Guid(rp_invoice_id); - } - catch - { - } - - var initialOrder = _orderService.GetOrderByGuid(orderNumberGuid); - if (initialOrder != null) - { - var recurringPayments = _orderService.SearchRecurringPayments(0, 0, initialOrder.Id, null); - foreach (var rp in recurringPayments) - { - switch (newPaymentStatus) - { - case PaymentStatus.Authorized: - case PaymentStatus.Paid: - { - var recurringPaymentHistory = rp.RecurringPaymentHistory; - if (recurringPaymentHistory.Count == 0) - { - //first payment - var rph = new RecurringPaymentHistory() - { - RecurringPaymentId = rp.Id, - OrderId = initialOrder.Id, - CreatedOnUtc = DateTime.UtcNow - }; - rp.RecurringPaymentHistory.Add(rph); - _orderService.UpdateRecurringPayment(rp); - } - else - { - //next payments - _orderProcessingService.ProcessNextRecurringPayment(rp); - //UNDONE change new order status according to newPaymentStatus - //UNDONE refund/void is not supported - } - } - break; - } - } - - _logger.Information("PayPal IPN. Recurring info", new SmartException(sb.ToString())); - } - else - { - _logger.Error("PayPal IPN. Order is not found", new SmartException(sb.ToString())); - } - } - #endregion - break; - default: - #region Standard payment - { - string orderNumber = string.Empty; - values.TryGetValue("custom", out orderNumber); - Guid orderNumberGuid = Guid.Empty; - try - { - orderNumberGuid = new Guid(orderNumber); - } - catch - { - } - - var order = _orderService.GetOrderByGuid(orderNumberGuid); - if (order != null) - { - - //order note - order.OrderNotes.Add(new OrderNote() - { - Note = sb.ToString(), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - switch (newPaymentStatus) - { - case PaymentStatus.Pending: - { - } - break; - case PaymentStatus.Authorized: - { - if (_orderProcessingService.CanMarkOrderAsAuthorized(order)) - { - _orderProcessingService.MarkAsAuthorized(order); - } - } - break; - case PaymentStatus.Paid: - { - if (_orderProcessingService.CanMarkOrderAsPaid(order)) - { - _orderProcessingService.MarkOrderAsPaid(order); - } - } - break; - case PaymentStatus.Refunded: - { - if (_orderProcessingService.CanRefundOffline(order)) - { - _orderProcessingService.RefundOffline(order); - } - } - break; - case PaymentStatus.Voided: - { - if (_orderProcessingService.CanVoidOffline(order)) - { - _orderProcessingService.VoidOffline(order); - } - } - break; - default: - break; - } - } - else - { - _logger.Error("PayPal IPN. Order is not found", new SmartException(sb.ToString())); - } - } - #endregion - break; - } - } - else - { - _logger.Error("PayPal IPN failed.", new SmartException(strRequest)); + return PartialView(model); } - //nothing should be rendered to visitor - return Content(""); + return new EmptyResult(); } - public ActionResult SubmitButton() { try { //user validation - if ((_services.WorkContext.CurrentCustomer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed)) + if ((Services.WorkContext.CurrentCustomer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed)) return RedirectToRoute("Login"); - var settings = _services.Settings.LoadSetting(_services.StoreContext.CurrentStore.Id); - var cart = _services.WorkContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, _services.StoreContext.CurrentStore.Id); + var store = Services.StoreContext.CurrentStore; + var customer = Services.WorkContext.CurrentCustomer; + var settings = Services.Settings.LoadSetting(store.Id); + var cart = Services.WorkContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); if (cart.Count == 0) return RedirectToRoute("ShoppingCart"); - var currency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; - if (String.IsNullOrEmpty(settings.ApiAccountName)) throw new ApplicationException("PayPal API Account Name is not set"); if (String.IsNullOrEmpty(settings.ApiAccountPassword)) @@ -386,32 +198,28 @@ public ActionResult SubmitButton() if (String.IsNullOrEmpty(settings.Signature)) throw new ApplicationException("PayPal API Signature is not set"); - var provider = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalExpress", true); - var processor = provider != null ? provider.Value as PayPalExpress : null; + var provider = PaymentService.LoadPaymentMethodBySystemName(PayPalExpressProvider.SystemName, true); + var processor = provider != null ? provider.Value as PayPalExpressProvider : null; if (processor == null) throw new SmartException("PayPal Express Checkout module cannot be loaded"); var processPaymentRequest = new PayPalProcessPaymentRequest(); - processPaymentRequest.StoreId = _services.StoreContext.CurrentStore.Id; + processPaymentRequest.StoreId = store.Id; //Get sub-total and discounts that apply to sub-total 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); + out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscount, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); //order total decimal resultTemp = decimal.Zero; resultTemp += subTotalWithDiscountBase; - // get customer - int customerId = Convert.ToInt32(_services.WorkContext.CurrentCustomer.Id.ToString()); - var customer = _customerService.GetCustomerById(customerId); - //Get discounts that apply to Total Discount appliedDiscount = null; var discountAmount = _orderTotalCalculationService.GetOrderTotalDiscount(customer, resultTemp, out appliedDiscount); @@ -428,20 +236,20 @@ public ActionResult SubmitButton() decimal tempDiscount = discountAmount + orderSubTotalDiscountAmountBase; - resultTemp = _currencyService.ConvertFromPrimaryStoreCurrency(resultTemp, _services.WorkContext.WorkingCurrency); + resultTemp = _currencyService.ConvertFromPrimaryStoreCurrency(resultTemp, Services.WorkContext.WorkingCurrency); if (tempDiscount > decimal.Zero) { - tempDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(tempDiscount, _services.WorkContext.WorkingCurrency); + tempDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(tempDiscount, Services.WorkContext.WorkingCurrency); } - processPaymentRequest.PaymentMethodSystemName = "Payments.PayPalExpress"; + processPaymentRequest.PaymentMethodSystemName = PayPalExpressProvider.SystemName; processPaymentRequest.OrderTotal = resultTemp; processPaymentRequest.Discount = tempDiscount; processPaymentRequest.IsRecurringPayment = false; //var selectedPaymentMethodSystemName = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.SelectedPaymentMethod, _storeContext.CurrentStore.Id); - processPaymentRequest.CustomerId = _services.WorkContext.CurrentCustomer.Id; + processPaymentRequest.CustomerId = Services.WorkContext.CurrentCustomer.Id; this.Session["OrderPaymentInfo"] = processPaymentRequest; var resp = processor.SetExpressCheckout(processPaymentRequest, cart); @@ -450,15 +258,12 @@ public ActionResult SubmitButton() { processPaymentRequest.PaypalToken = resp.Token; processPaymentRequest.OrderGuid = new Guid(); - processPaymentRequest.IsShippingMethodSet = PayPalHelper.CurrentPageIsBasket(this.RouteData); + processPaymentRequest.IsShippingMethodSet = ControllerContext.RouteData.IsRouteEqual("ShoppingCart", "Cart"); this.Session["OrderPaymentInfo"] = processPaymentRequest; - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, "Payments.PayPalExpress", - _services.StoreContext.CurrentStore.Id); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, PayPalExpressProvider.SystemName, store.Id); - var result = new RedirectResult(String.Format( - PayPalHelper.GetPaypalUrl(settings) + - "?cmd=_express-checkout&useraction=commit&token={0}", resp.Token)); + var result = new RedirectResult(String.Format(settings.GetPayPalUrl() + "?cmd=_express-checkout&useraction=commit&token={0}", resp.Token)); return result; } @@ -470,7 +275,7 @@ public ActionResult SubmitButton() error.AppendLine(String.Format("{0} | {1} | {2}", errormsg.ErrorCode, errormsg.ShortMessage, errormsg.LongMessage)); } - _logger.InsertLog(LogLevel.Error, resp.Errors[0].ShortMessage, resp.Errors[0].LongMessage, _services.WorkContext.CurrentCustomer); + Logger.InsertLog(LogLevel.Error, resp.Errors[0].ShortMessage, resp.Errors[0].LongMessage, customer); NotifyError(error.ToString(), false); @@ -479,7 +284,7 @@ public ActionResult SubmitButton() } catch (Exception ex) { - _logger.InsertLog(LogLevel.Error, ex.Message, ex.StackTrace, _services.WorkContext.CurrentCustomer); + Logger.InsertLog(LogLevel.Error, ex.Message, ex.StackTrace, Services.WorkContext.CurrentCustomer); NotifyError(ex.Message, false); @@ -490,8 +295,8 @@ public ActionResult SubmitButton() public ActionResult GetDetails(string token) { - var provider = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalExpress", true); - var processor = provider != null ? provider.Value as PayPalExpress : null; + var provider = PaymentService.LoadPaymentMethodBySystemName("Payments.PayPalExpress", true); + var processor = provider != null ? provider.Value as PayPalExpressProvider : null; if (processor == null) throw new SmartException("PayPal Express module cannot be loaded"); @@ -502,13 +307,14 @@ public ActionResult GetDetails(string token) var paymentInfo = this.Session["OrderPaymentInfo"] as ProcessPaymentRequest; paymentInfo = processor.SetCheckoutDetails(paymentInfo, resp.GetExpressCheckoutDetailsResponseDetails); this.Session["OrderPaymentInfo"] = paymentInfo; + + var store = Services.StoreContext.CurrentStore; var customer = _customerService.GetCustomerById(paymentInfo.CustomerId); - _services.WorkContext.CurrentCustomer = customer; - _customerService.UpdateCustomer(_services.WorkContext.CurrentCustomer); + Services.WorkContext.CurrentCustomer = customer; + _customerService.UpdateCustomer(Services.WorkContext.CurrentCustomer); - var selectedShippingOption = _services.WorkContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, - _services.StoreContext.CurrentStore.Id); + var selectedShippingOption = Services.WorkContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, store.Id); if (selectedShippingOption != null) { return RedirectToAction("Confirm", "Checkout", new { area = "" }); @@ -516,8 +322,7 @@ public ActionResult GetDetails(string token) else { //paymentInfo.RequiresPaymentWorkflow = false; - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, paymentInfo.PaymentMethodSystemName, - _services.StoreContext.CurrentStore.Id); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, paymentInfo.PaymentMethodSystemName, store.Id); _customerService.UpdateCustomer(customer); return RedirectToAction("BillingAddress", "Checkout", new { area = "" }); @@ -531,7 +336,7 @@ public ActionResult GetDetails(string token) error.AppendLine(String.Format("{0} | {1} | {2}", errormsg.ErrorCode, errormsg.ShortMessage, errormsg.LongMessage)); } - _logger.InsertLog(LogLevel.Error, resp.Errors[0].ShortMessage, resp.Errors[0].LongMessage, _services.WorkContext.CurrentCustomer); + Logger.InsertLog(LogLevel.Error, resp.Errors[0].ShortMessage, resp.Errors[0].LongMessage, Services.WorkContext.CurrentCustomer); NotifyError(error.ToString(), false); @@ -551,16 +356,18 @@ public override IList ValidatePaymentForm(FormCollection form) { var warnings = new List(); - //validate - var validator = new PayPalExpressPaymentInfoValidator(_localizationService); - var model = new PayPalExpressPaymentInfoModel() - { + var validator = new PayPalExpressPaymentInfoValidator(Services.Localization); + var model = new PayPalExpressPaymentInfoModel(); - }; var validationResult = validator.Validate(model); + if (!validationResult.IsValid) + { foreach (var error in validationResult.Errors) + { warnings.Add(error.ErrorMessage); + } + } return warnings; } diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs new file mode 100644 index 0000000000..7a835f3d19 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -0,0 +1,408 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using System.Web.Mvc; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Html; +using SmartStore.Core.Plugins; +using SmartStore.PayPal.Models; +using SmartStore.PayPal.Services; +using SmartStore.PayPal.Settings; +using SmartStore.PayPal.Validators; +using SmartStore.Services.Catalog; +using SmartStore.Services.Common; +using SmartStore.Services.Customers; +using SmartStore.Services.Directory; +using SmartStore.Services.Localization; +using SmartStore.Services.Payments; +using SmartStore.Services.Tax; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; +using SmartStore.Web.Framework.Settings; + +namespace SmartStore.PayPal.Controllers +{ + public class PayPalPlusController : PayPalRestApiControllerBase + { + private readonly HttpContextBase _httpContext; + private readonly PluginMediator _pluginMediator; + private readonly IGenericAttributeService _genericAttributeService; + private readonly IPaymentService _paymentService; + private readonly ITaxService _taxService; + private readonly ICurrencyService _currencyService; + private readonly IPriceFormatter _priceFormatter; + + public PayPalPlusController( + HttpContextBase httpContext, + PluginMediator pluginMediator, + IPayPalService payPalService, + IGenericAttributeService genericAttributeService, + IPaymentService paymentService, + ITaxService taxService, + ICurrencyService currencyService, + IPriceFormatter priceFormatter) : base( + PayPalPlusProvider.SystemName, + payPalService) + { + _httpContext = httpContext; + _pluginMediator = pluginMediator; + _genericAttributeService = genericAttributeService; + _paymentService = paymentService; + _taxService = taxService; + _currencyService = currencyService; + _priceFormatter = priceFormatter; + } + + private string GetPaymentMethodName(Provider provider) + { + if (provider != null) + { + var name = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); + + if (name.IsEmpty()) + name = provider.Metadata.FriendlyName; + + if (name.IsEmpty()) + name = provider.Metadata.SystemName; + + return name; + } + return ""; + } + + private string GetPaymentFee(Provider provider, List cart) + { + var paymentMethodAdditionalFee = provider.Value.GetAdditionalHandlingFee(cart); + var rateBase = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, Services.WorkContext.CurrentCustomer); + var rate = _currencyService.ConvertFromPrimaryStoreCurrency(rateBase, Services.WorkContext.WorkingCurrency); + + if (rate != decimal.Zero) + { + return _priceFormatter.FormatPaymentMethodAdditionalFee(rate, true); + } + return ""; + } + + private PayPalPlusCheckoutModel.ThirdPartyPaymentMethod GetThirdPartyPaymentMethodModel( + Provider provider, + PayPalPlusPaymentSettings settings, + Store store) + { + var model = new PayPalPlusCheckoutModel.ThirdPartyPaymentMethod(); + model.MethodName = GetPaymentMethodName(provider); + model.RedirectUrl = Url.Action("CheckoutReturn", "PayPalPlus", new { area = Plugin.SystemName, systemName = provider.Metadata.SystemName }, store.SslEnabled ? "https" : "http"); + + try + { + if (settings.DisplayPaymentMethodDescription) + { + // not the short description, the full description is intended for frontend + var paymentMethod = _paymentService.GetPaymentMethodBySystemName(provider.Metadata.SystemName); + if (paymentMethod != null) + { + var description = paymentMethod.GetLocalized(x => x.FullDescription); + if (description.HasValue()) + { + description = HtmlUtils.ConvertHtmlToPlainText(description); + description = HtmlUtils.StripTags(HttpUtility.HtmlDecode(description)); + + if (description.HasValue()) + model.Description = description.EncodeJsString(); + } + } + } + } + catch { } + + try + { + if (settings.DisplayPaymentMethodLogo && provider.Metadata.PluginDescriptor != null && store.SslEnabled) + { + var brandImageUrl = _pluginMediator.GetBrandImageUrl(provider.Metadata); + if (brandImageUrl.HasValue()) + { + if (brandImageUrl.StartsWith("~")) + brandImageUrl = brandImageUrl.Substring(1); + + var uri = new UriBuilder(Uri.UriSchemeHttps, Request.Url.Host, -1, brandImageUrl); + model.ImageUrl = uri.ToString(); + } + } + } + catch { } + + return model; + } + + [NonAction] + public override IList ValidatePaymentForm(FormCollection form) + { + var warnings = new List(); + return warnings; + } + + [NonAction] + public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) + { + var paymentInfo = new ProcessPaymentRequest(); + return paymentInfo; + } + + [AdminAuthorize, ChildActionOnly] + public ActionResult Configure() + { + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + + var model = new PayPalPlusConfigurationModel + { + ConfigGroups = T("Plugins.SmartStore.PayPal.ConfigGroups").Text.SplitSafe(";") + }; + + model.AvailableSecurityProtocols = PayPal.Services.PayPalService.GetSecurityProtocols() + .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) + .ToList(); + + // it's better to also offer inactive methods here but filter them out in frontend + var methods = _paymentService.LoadAllPaymentMethods(storeScope); + + model.AvailableThirdPartyPaymentMethods = methods + .Where(x => + x.Metadata.PluginDescriptor.SystemName != Plugin.SystemName && + !x.Value.RequiresInteraction && + (x.Metadata.PluginDescriptor.SystemName == "SmartStore.OfflinePayment" || x.Value.PaymentMethodType == PaymentMethodType.Redirection)) + .Select(x => new SelectListItem { Value = x.Metadata.SystemName, Text = GetPaymentMethodName(x) }) + .ToList(); + + + model.Copy(settings, true); + + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, Services.Settings); + + return View(model); + } + + [HttpPost, AdminAuthorize, ChildActionOnly] + public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection form) + { + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + var oldClientId = settings.ClientId; + var oldSecret = settings.Secret; + var oldProfileId = settings.ExperienceProfileId; + + var validator = new PayPalPlusConfigValidator(Services.Localization, x => + { + return storeScope == 0 || storeDependingSettingHelper.IsOverrideChecked(settings, x, form); + }); + + validator.Validate(model, ModelState); + + if (!ModelState.IsValid) + return Configure(); + + ModelState.Clear(); + + model.Copy(settings, false); + + // credentials changed: reset profile and webhook id to avoid errors + if (!oldClientId.IsCaseInsensitiveEqual(settings.ClientId) || !oldSecret.IsCaseInsensitiveEqual(settings.Secret)) + { + if (oldProfileId.IsCaseInsensitiveEqual(settings.ExperienceProfileId)) + settings.ExperienceProfileId = null; + + settings.WebhookId = null; + } + + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + + Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + + Services.Settings.ClearCache(); + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + + return Configure(); + } + + public ActionResult PaymentInfo() + { + return new EmptyResult(); + } + + public ActionResult PaymentWall() + { + var sb = new StringBuilder(); + var store = Services.StoreContext.CurrentStore; + var customer = Services.WorkContext.CurrentCustomer; + var language = Services.WorkContext.WorkingLanguage; + var settings = Services.Settings.LoadSetting(store.Id); + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); + + var pppMethod = _paymentService.GetPaymentMethodBySystemName(PayPalPlusProvider.SystemName); + var pppProvider = _paymentService.LoadPaymentMethodBySystemName(PayPalPlusProvider.SystemName, false, store.Id); + + var methods = _paymentService.LoadActivePaymentMethods(customer, cart, store.Id, null, false); + var session = _httpContext.GetPayPalSessionData(); + + var model = new PayPalPlusCheckoutModel(); + model.ThirdPartyPaymentMethods = new List(); + model.UseSandbox = settings.UseSandbox; + model.LanguageCulture = (language.LanguageCulture ?? "de_DE").Replace("-", "_"); + model.PayPalPlusPseudoMessageFlag = TempData["PayPalPlusPseudoMessageFlag"] as string; + model.PayPalFee = GetPaymentFee(pppProvider, cart); + model.HasAnyFees = model.PayPalFee.HasValue(); + + if (pppMethod != null) + { + model.FullDescription = pppMethod.GetLocalized(x => x.FullDescription, language.Id); + } + + if (customer.BillingAddress != null && customer.BillingAddress.Country != null) + { + model.BillingAddressCountryCode = customer.BillingAddress.Country.TwoLetterIsoCode; + } + + foreach (var systemName in settings.ThirdPartyPaymentMethods) + { + var provider = methods.FirstOrDefault(x => x.Metadata.SystemName == systemName); + if (provider != null) + { + var methodModel = GetThirdPartyPaymentMethodModel(provider, settings, store); + model.ThirdPartyPaymentMethods.Add(methodModel); + + var fee = GetPaymentFee(provider, cart); + if (fee.HasValue()) + model.HasAnyFees = true; + if (sb.Length > 0) + sb.Append(", "); + sb.AppendFormat("['{0}','{1}']", methodModel.MethodName.Replace("'", ""), fee); + } + } + + model.ThirdPartyFees = sb.ToString(); + + // we must create a new paypal payment each time the payment wall is rendered because otherwise patch payment can fail + // with "Item amount must add up to specified amount subtotal (or total if amount details not specified)". + session.PaymentId = null; + session.ApprovalUrl = null; + + var result = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + var protocol = (store.SslEnabled ? "https" : "http"); + var returnUrl = Url.Action("CheckoutReturn", "PayPalPlus", new { area = Plugin.SystemName }, protocol); + var cancelUrl = Url.Action("CheckoutCancel", "PayPalPlus", new { area = Plugin.SystemName }, protocol); + + result = PayPalService.CreatePayment(settings, session, cart, PayPalPlusProvider.SystemName, returnUrl, cancelUrl); + if (result.Success && result.Json != null) + { + foreach (var link in result.Json.links) + { + if (((string)link.rel).IsCaseInsensitiveEqual("approval_url")) + { + session.PaymentId = result.Id; + session.ApprovalUrl = link.href; + break; + } + } + } + else + { + model.ErrorMessage = result.ErrorMessage; + } + } + else + { + model.ErrorMessage = result.ErrorMessage; + } + + model.ApprovalUrl = session.ApprovalUrl; + + return View(model); + } + + [HttpPost] + public ActionResult PatchShipping() + { + var store = Services.StoreContext.CurrentStore; + var customer = Services.WorkContext.CurrentCustomer; + var settings = Services.Settings.LoadSetting(store.Id); + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); + var session = HttpContext.GetPayPalSessionData(); + + var apiResult = PayPalService.PatchShipping(settings, session, cart, PayPalPlusProvider.SystemName); + + return new JsonResult { Data = new { success = apiResult.Success, error = apiResult.ErrorMessage } }; + } + + public ActionResult CheckoutCompleted() + { + var instruct = _httpContext.Session[PayPalPlusProvider.CheckoutCompletedKey] as string; + + if (instruct.HasValue()) + { + return Content(instruct); + } + + return new EmptyResult(); + } + + [ValidateInput(false)] + public ActionResult CheckoutReturn(string systemName, string paymentId, string PayerID) + { + // Request.QueryString: + // paymentId: PAY-0TC88803RP094490KK4KM6AI, token: EC-5P379249AL999154U, PayerID: 5L9K773HHJLPN + + var customer = Services.WorkContext.CurrentCustomer; + var store = Services.StoreContext.CurrentStore; + var settings = Services.Settings.LoadSetting(store.Id); + var session = _httpContext.GetPayPalSessionData(); + + if (systemName.IsEmpty()) + systemName = PayPalPlusProvider.SystemName; + + if (paymentId.HasValue() && session.PaymentId.IsEmpty()) + session.PaymentId = paymentId; + + session.PayerId = PayerID; + + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, systemName, store.Id); + + var paymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; + if (paymentRequest == null) + { + _httpContext.Session["OrderPaymentInfo"] = new ProcessPaymentRequest + { + PaymentMethodSystemName = systemName + }; + } + + return RedirectToAction("Confirm", "Checkout", new { area = "" }); + } + + [ValidateInput(false)] + public ActionResult CheckoutCancel() + { + // Request.QueryString: + // token: EC-6JM38216F6718012L, ppp_msg: 1 + + // undocumented + var pseudoMessageFlag = Request.QueryString["ppp_msg"] as string; + + if (pseudoMessageFlag.HasValue()) + { + TempData["PayPalPlusPseudoMessageFlag"] = pseudoMessageFlag; + } + + // back to where he came from + return RedirectToAction("PaymentMethod", "Checkout", new { area = "" }); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs new file mode 100644 index 0000000000..8d5818e5d5 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs @@ -0,0 +1,177 @@ +using System; +using System.IO; +using System.Net; +using System.Web.Mvc; +using SmartStore.Core.Configuration; +using SmartStore.PayPal.Services; +using SmartStore.PayPal.Settings; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; + +namespace SmartStore.PayPal.Controllers +{ + public abstract class PayPalRestApiControllerBase : PaymentControllerBase where TSetting : PayPalApiSettingsBase, ISettings, new() + { + public PayPalRestApiControllerBase( + string systemName, + IPayPalService payPalService) + { + SystemName = systemName; + PayPalService = payPalService; + } + + private string GetControllerName() + { + return GetType().Name.Replace("Controller", ""); + } + + protected string SystemName { get; private set; } + protected IPayPalService PayPalService { get; private set; } + + [AdminAuthorize] + public ActionResult UpsertExperienceProfile() + { + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + + var store = Services.StoreService.GetStoreById(storeScope == 0 ? Services.StoreContext.CurrentStore.Id : storeScope); + var session = new PayPalSessionData(); + + var result = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + result = PayPalService.UpsertCheckoutExperience(settings, session, store); + if (result.Success && result.Id.HasValue()) + { + settings.ExperienceProfileId = result.Id; + Services.Settings.SaveSetting(settings, x => x.ExperienceProfileId, storeScope, false); + Services.Settings.ClearCache(); + } + } + + if (result.Success) + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + else + NotifyError(result.ErrorMessage); + + return RedirectToAction("ConfigureProvider", "Plugin", new { area = "admin", systemName = SystemName }); + } + + [AdminAuthorize] + public ActionResult DeleteExperienceProfile() + { + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + var session = new PayPalSessionData(); + + var result = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + result = PayPalService.DeleteCheckoutExperience(settings, session); + if (result.Success) + { + settings.ExperienceProfileId = null; + Services.Settings.SaveSetting(settings, x => x.ExperienceProfileId, storeScope, false); + Services.Settings.ClearCache(); + } + } + + if (result.Success) + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + else + NotifyError(result.ErrorMessage); + + return RedirectToAction("ConfigureProvider", "Plugin", new { area = "admin", systemName = SystemName }); + } + + [AdminAuthorize] + public ActionResult CreateWebhook() + { + var settings = Services.Settings.LoadSetting(); + var session = new PayPalSessionData(); + + if (settings.WebhookId.HasValue()) + { + var unused = PayPalService.DeleteWebhook(settings, session); + + Services.Settings.SaveSetting(settings, x => x.WebhookId, 0, false); + } + + var url = Url.Action("Webhook", GetControllerName(), new { area = Plugin.SystemName }, "https"); + + var result = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + result = PayPalService.CreateWebhook(settings, session, url); + if (result.Success) + { + settings.WebhookId = result.Id; + Services.Settings.SaveSetting(settings, x => x.WebhookId, 0, false); + } + } + + Services.Settings.ClearCache(); + + if (result.Success) + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + else + NotifyError(result.ErrorMessage); + + return RedirectToAction("ConfigureProvider", "Plugin", new { area = "admin", systemName = SystemName }); + } + + [AdminAuthorize] + public ActionResult DeleteWebhook() + { + var settings = Services.Settings.LoadSetting(); + var session = new PayPalSessionData(); + + if (settings.WebhookId.HasValue()) + { + var result = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + result = PayPalService.DeleteWebhook(settings, session); + if (result.Success) + { + settings.WebhookId = null; + Services.Settings.SaveSetting(settings, x => x.WebhookId, 0, false); + Services.Settings.ClearCache(); + } + } + + if (result.Success) + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + else + NotifyError(result.ErrorMessage); + } + + return RedirectToAction("ConfigureProvider", "Plugin", new { area = "admin", systemName = SystemName }); + } + + [ValidateInput(false)] + public ActionResult Webhook() + { + HttpStatusCode result = HttpStatusCode.OK; + + try + { + string json = null; + using (var reader = new StreamReader(Request.InputStream)) + { + json = reader.ReadToEnd(); + } + + var settings = Services.Settings.LoadSetting(); + + result = PayPalService.ProcessWebhook(settings, Request.Headers, json, PayPalPlusProvider.SystemName); + } + catch (Exception exception) + { + PayPalService.LogError(exception, isWarning: true); + } + + return new HttpStatusCodeResult(result); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs index bc28786e9d..e007d86dfc 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs @@ -1,73 +1,50 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.Linq; -using System.Text; using System.Web.Mvc; -using SmartStore.Core; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Logging; using SmartStore.PayPal.Models; +using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; -using SmartStore.Services; -using SmartStore.Services.Localization; using SmartStore.Services.Orders; using SmartStore.Services.Payments; -using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; namespace SmartStore.PayPal.Controllers { - public class PayPalStandardController : PaymentControllerBase + public class PayPalStandardController : PayPalControllerBase { - private readonly IPaymentService _paymentService; - private readonly IOrderService _orderService; - private readonly IOrderProcessingService _orderProcessingService; - private readonly IStoreContext _storeContext; - private readonly IWorkContext _workContext; - private readonly IWebHelper _webHelper; - private readonly PaymentSettings _paymentSettings; - private readonly ILocalizationService _localizationService; - private readonly ICommonServices _services; - private readonly IStoreService _storeService; - public PayPalStandardController( - IPaymentService paymentService, IOrderService orderService, - IOrderProcessingService orderProcessingService, - IStoreContext storeContext, - IWorkContext workContext, - IWebHelper webHelper, - PaymentSettings paymentSettings, - ILocalizationService localizationService, - ICommonServices services, - IStoreService storeService) + IPaymentService paymentService, + IOrderService orderService, + IOrderProcessingService orderProcessingService) : base( + PayPalStandardProvider.SystemName, + paymentService, + orderService, + orderProcessingService) { - _paymentService = paymentService; - _orderService = orderService; - _orderProcessingService = orderProcessingService; - _storeContext = storeContext; - _workContext = workContext; - _webHelper = webHelper; - _paymentSettings = paymentSettings; - _localizationService = localizationService; - _services = services; - _storeService = storeService; } [AdminAuthorize, ChildActionOnly] public ActionResult Configure() { var model = new PayPalStandardConfigurationModel(); - 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); model.Copy(settings, true); - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, _services.Settings); + model.AvailableSecurityProtocols = PayPalService.GetSecurityProtocols() + .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) + .ToList(); + + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, Services.Settings); return View(model); } @@ -81,18 +58,18 @@ public ActionResult Configure(PayPalStandardConfigurationModel model, FormCollec 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); model.Copy(settings, false); - storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, _services.Settings); + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); // multistore context not possible, see IPN handling - _services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); - _services.Settings.ClearCache(); - NotifySuccess(_services.Localization.GetResource("Plugins.Payments.PayPal.ConfigSaveNote")); + Services.Settings.ClearCache(); + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); return Configure(); } @@ -119,38 +96,42 @@ public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) [ValidateInput(false)] public ActionResult PDTHandler(FormCollection form) { - string tx = _webHelper.QueryString("tx"); Dictionary values; + var tx = Services.WebHelper.QueryString("tx"); + var utcNow = DateTime.UtcNow; + var orderNumberGuid = Guid.Empty; + var orderNumber = string.Empty; + var total = decimal.Zero; string response; - var provider = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalStandard", true); - var processor = provider != null ? provider.Value as PayPalStandardProvider : null; + var provider = PaymentService.LoadPaymentMethodBySystemName(SystemName, true); + var processor = (provider != null ? provider.Value as PayPalStandardProvider : null); if (processor == null) - throw new SmartException(_localizationService.GetResource("Plugins.Payments.PayPalStandard.NoModuleLoading")); + throw new SmartException(T("Plugins.Payments.PayPal.NoModuleLoading")); - var settings = _services.Settings.LoadSetting(); + var settings = Services.Settings.LoadSetting(); if (processor.GetPDTDetails(tx, settings, out values, out response)) { - string orderNumber = string.Empty; values.TryGetValue("custom", out orderNumber); - Guid orderNumberGuid = Guid.Empty; + try { orderNumberGuid = new Guid(orderNumber); } catch { } - Order order = _orderService.GetOrderByGuid(orderNumberGuid); + + var order = OrderService.GetOrderByGuid(orderNumberGuid); + if (order != null) { - decimal total = decimal.Zero; try { total = decimal.Parse(values["mc_gross"], new CultureInfo("en-US")); } catch (Exception exc) { - Logger.Error(_localizationService.GetResource("Plugins.Payments.PayPalStandard.FailedGetGross"), exc); + Logger.Error(T("Plugins.Payments.PayPalStandard.FailedGetGross"), exc); } string payer_status = string.Empty; @@ -174,33 +155,54 @@ public ActionResult PDTHandler(FormCollection form) string payment_fee = string.Empty; values.TryGetValue("payment_fee", out payment_fee); - string paymentNote = _localizationService.GetResource("Plugins.Payments.PayPalStandard.PaymentNote").FormatWith( + var paymentNote = T("Plugins.Payments.PayPalStandard.PaymentNote", total, mc_currency, payer_status, payment_status, pending_reason, txn_id, payment_type, payer_id, receiver_id, invoice, payment_fee); - //order note - order.OrderNotes.Add(new OrderNote() - { - Note = paymentNote, - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //validate order total - if (settings.PdtValidateOrderTotal && !Math.Round(total, 2).Equals(Math.Round(order.OrderTotal, 2))) + OrderService.AddOrderNote(order, paymentNote); + + // validate order total... you may get differences if settings.PassProductNamesAndTotals is true + if (settings.PdtValidateOrderTotal) { - Logger.Error(_localizationService.GetResource("Plugins.Payments.PayPalStandard.UnequalTotalOrder").FormatWith(total, order.OrderTotal)); + var roundedTotal = Math.Round(total, 2); + var roundedOrderTotal = Math.Round(order.OrderTotal, 2); + var roundedDifference = Math.Abs(roundedTotal - roundedOrderTotal); + + if (!roundedTotal.Equals(roundedOrderTotal)) + { + var message = T("Plugins.Payments.PayPalStandard.UnequalTotalOrder", + total, roundedOrderTotal.FormatInvariant(), order.OrderTotal, roundedDifference.FormatInvariant()); - return RedirectToAction("Index", "Home", new { area = "" }); + if (settings.PdtValidateOnlyWarn) + { + OrderService.AddOrderNote(order, message); + } + else + { + Logger.Error(message); + + return RedirectToAction("Index", "Home", new { area = "" }); + } + } } - //mark order as paid - if (_orderProcessingService.CanMarkOrderAsPaid(order)) + // mark order as paid + var newPaymentStatus = GetPaymentStatus(payment_status, pending_reason, total, order.OrderTotal); + + if (newPaymentStatus == PaymentStatus.Paid) { - order.AuthorizationTransactionId = txn_id; - _orderService.UpdateOrder(order); + // note, order can be marked as paid through IPN + if (order.AuthorizationTransactionId.IsEmpty()) + { + order.AuthorizationTransactionId = order.CaptureTransactionId = txn_id; + order.AuthorizationTransactionResult = order.CaptureTransactionResult = "Success"; - _orderProcessingService.MarkOrderAsPaid(order); + OrderService.UpdateOrder(order); + } + + if (OrderProcessingService.CanMarkOrderAsPaid(order)) + { + OrderProcessingService.MarkOrderAsPaid(order); + } } } @@ -208,287 +210,23 @@ public ActionResult PDTHandler(FormCollection form) } else { - string orderNumber = string.Empty; - values.TryGetValue("custom", out orderNumber); - Guid orderNumberGuid = Guid.Empty; try { + values.TryGetValue("custom", out orderNumber); orderNumberGuid = new Guid(orderNumber); - } - catch { } - Order order = _orderService.GetOrderByGuid(orderNumberGuid); - if (order != null) - { - //order note - order.OrderNotes.Add(new OrderNote() - { - Note = "{0} {1}".FormatWith(_localizationService.GetResource("Plugins.Payments.PayPalStandard.PdtFailed"), response), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - return RedirectToAction("Index", "Home", new { area = "" }); - } - } - - [ValidateInput(false)] - public ActionResult IPNHandler() - { - Debug.WriteLine("PayPal Standard IPN: {0}".FormatWith(Request.ContentLength)); - - byte[] param = Request.BinaryRead(Request.ContentLength); - string strRequest = Encoding.ASCII.GetString(param); - Dictionary values; - var provider = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalStandard", true); - var processor = provider != null ? provider.Value as PayPalStandardProvider : null; - if (processor == null) - throw new SmartException(_localizationService.GetResource("Plugins.Payments.PayPalStandard.NoModuleLoading")); - - if (processor.VerifyIPN(strRequest, out values)) - { - #region values - decimal total = decimal.Zero; - try - { - total = decimal.Parse(values["mc_gross"], new CultureInfo("en-US")); + var order = OrderService.GetOrderByGuid(orderNumberGuid); + OrderService.AddOrderNote(order, "{0} {1}".FormatInvariant(T("Plugins.Payments.PayPalStandard.PdtFailed"), response)); } catch { } - string payer_status = string.Empty; - values.TryGetValue("payer_status", out payer_status); - string payment_status = string.Empty; - values.TryGetValue("payment_status", out payment_status); - string pending_reason = string.Empty; - values.TryGetValue("pending_reason", out pending_reason); - string mc_currency = string.Empty; - values.TryGetValue("mc_currency", out mc_currency); - string txn_id = string.Empty; - values.TryGetValue("txn_id", out txn_id); - string txn_type = string.Empty; - values.TryGetValue("txn_type", out txn_type); - string rp_invoice_id = string.Empty; - values.TryGetValue("rp_invoice_id", out rp_invoice_id); - string payment_type = string.Empty; - values.TryGetValue("payment_type", out payment_type); - string payer_id = string.Empty; - values.TryGetValue("payer_id", out payer_id); - string receiver_id = string.Empty; - values.TryGetValue("receiver_id", out receiver_id); - string invoice = string.Empty; - values.TryGetValue("invoice", out invoice); - string payment_fee = string.Empty; - values.TryGetValue("payment_fee", out payment_fee); - - #endregion - - var sb = new StringBuilder(); - sb.AppendLine("PayPal IPN:"); - foreach (KeyValuePair kvp in values) - { - sb.AppendLine(kvp.Key + ": " + kvp.Value); - } - - var newPaymentStatus = GetPaymentStatus(payment_status, pending_reason); - sb.AppendLine("{0}: {1}".FormatWith(_localizationService.GetResource("Plugins.Payments.PayPalStandard.NewPaymentStatus"), newPaymentStatus)); - - switch (txn_type) - { - case "recurring_payment_profile_created": - //do nothing here - break; - case "recurring_payment": - #region Recurring payment - { - Guid orderNumberGuid = Guid.Empty; - try - { - orderNumberGuid = new Guid(rp_invoice_id); - } - catch { } - - var initialOrder = _orderService.GetOrderByGuid(orderNumberGuid); - if (initialOrder != null) - { - var recurringPayments = _orderService.SearchRecurringPayments(0, 0, initialOrder.Id, null); - foreach (var rp in recurringPayments) - { - switch (newPaymentStatus) - { - case PaymentStatus.Authorized: - case PaymentStatus.Paid: { - var recurringPaymentHistory = rp.RecurringPaymentHistory; - if (recurringPaymentHistory.Count == 0) - { - //first payment - var rph = new RecurringPaymentHistory() - { - RecurringPaymentId = rp.Id, - OrderId = initialOrder.Id, - CreatedOnUtc = DateTime.UtcNow - }; - rp.RecurringPaymentHistory.Add(rph); - _orderService.UpdateRecurringPayment(rp); - } - else - { - //next payments - _orderProcessingService.ProcessNextRecurringPayment(rp); - } - } - break; - } - } - - //this.OrderService.InsertOrderNote(newOrder.OrderId, sb.ToString(), DateTime.UtcNow); - Logger.Information(_localizationService.GetResource("Plugins.Payments.PayPalStandard.IpnLogInfo"), new SmartException(sb.ToString())); - } - else - { - Logger.Error(_localizationService.GetResource("Plugins.Payments.PayPalStandard.IpnOrderNotFound"), new SmartException(sb.ToString())); - } - } - #endregion - break; - default: - #region Standard payment - { - string orderNumber = string.Empty; - values.TryGetValue("custom", out orderNumber); - Guid orderNumberGuid = Guid.Empty; - try - { - orderNumberGuid = new Guid(orderNumber); - } - catch { } - - var order = _orderService.GetOrderByGuid(orderNumberGuid); - if (order != null) - { - //order note - order.OrderNotes.Add(new OrderNote() - { - Note = sb.ToString(), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - switch (newPaymentStatus) - { - case PaymentStatus.Pending: - { - } - break; - case PaymentStatus.Authorized: { - if (_orderProcessingService.CanMarkOrderAsAuthorized(order)) - { - _orderProcessingService.MarkAsAuthorized(order); - } - } - break; - case PaymentStatus.Paid: - { - if (_orderProcessingService.CanMarkOrderAsPaid(order)) - { - - order.AuthorizationTransactionId = txn_id; - _orderService.UpdateOrder(order); - - _orderProcessingService.MarkOrderAsPaid(order); - } - } - break; - case PaymentStatus.Refunded: { - if (_orderProcessingService.CanRefundOffline(order)) - { - _orderProcessingService.RefundOffline(order); - } - } - break; - case PaymentStatus.Voided: { - if (_orderProcessingService.CanVoidOffline(order)) - { - _orderProcessingService.VoidOffline(order); - } - } - break; - default: - break; - } - } - else - { - Logger.Error(_localizationService.GetResource("Plugins.Payments.PayPalStandard.IpnOrderNotFound"), new SmartException(sb.ToString())); - } - } - #endregion - break; - } - } - else - { - Logger.Error(_localizationService.GetResource("Plugins.Payments.PayPalStandard.IpnFailed"), new SmartException(strRequest)); + return RedirectToAction("Index", "Home", new { area = "" }); } - - //nothing should be rendered to visitor - return Content(""); } - /// - /// Gets a payment status - /// - /// PayPal payment status - /// PayPal pending reason - /// Payment status - public PaymentStatus GetPaymentStatus(string paymentStatus, string pendingReason) - { - var result = PaymentStatus.Pending; - - if (paymentStatus == null) - paymentStatus = string.Empty; - - if (pendingReason == null) - pendingReason = string.Empty; - - switch (paymentStatus.ToLowerInvariant()) - { - case "pending": - switch (pendingReason.ToLowerInvariant()) - { - case "authorization": - result = PaymentStatus.Authorized; - break; - default: - result = PaymentStatus.Pending; - break; - } - break; - case "processed": - case "completed": - case "canceled_reversal": - result = PaymentStatus.Paid; - break; - case "denied": - case "expired": - case "failed": - case "voided": - result = PaymentStatus.Voided; - break; - case "refunded": - case "reversed": - result = PaymentStatus.Refunded; - break; - default: - break; - } - return result; - } - public ActionResult CancelOrder(FormCollection form) { - var order = _orderService.SearchOrders(_storeContext.CurrentStore.Id, _workContext.CurrentCustomer.Id, null, null, null, null, null, null, null, null, 0, 1) + var order = OrderService.SearchOrders(Services.StoreContext.CurrentStore.Id, Services.WorkContext.CurrentCustomer.Id, null, null, null, null, null, null, null, null, 0, 1) .FirstOrDefault(); if (order != null) diff --git a/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs b/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs index 0285470f18..95e7e8f1c3 100644 --- a/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs +++ b/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs @@ -2,9 +2,9 @@ using Autofac.Integration.Mvc; using SmartStore.Core.Infrastructure; using SmartStore.Core.Infrastructure.DependencyManagement; -using SmartStore.Core.Plugins; -using SmartStore.Web.Controllers; using SmartStore.PayPal.Filters; +using SmartStore.PayPal.Services; +using SmartStore.Web.Controllers; namespace SmartStore.PayPal { @@ -12,9 +12,21 @@ public class DependencyRegistrar : IDependencyRegistrar { public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, bool isActiveModule) { + builder.RegisterType().As().InstancePerRequest(); + if (isActiveModule) { builder.RegisterType().AsActionFilterFor(x => x.PaymentMethod()).InstancePerRequest(); + + builder.RegisterType().AsActionFilterFor(x => x.FlyoutShoppingCart()).InstancePerRequest(); + + builder.RegisterType() + .AsActionFilterFor(x => x.PaymentMethod()) + .InstancePerRequest(); + + builder.RegisterType() + .AsActionFilterFor(x => x.Completed()) + .InstancePerRequest(); } } diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index 317a0b09cc..b96ae172bb 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -1,10 +1,10 @@ FriendlyName: PayPal -Description: Provides the PayPal payment methods PayPal Express, PayPal Standard and PayPal Direct. +Description: Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal PLUS. SystemName: SmartStore.PayPal Group: Payment -Version: 1.22 -MinAppVersion: 2.1.0 +Version: 2.6.0.5 +MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.PayPal.dll ResourceRootKey: Plugins.SmartStore.PayPal -Url: http://community.smartstore.com/index.php?/files/file/29-paypal-payment-plugins/ \ No newline at end of file +Url: http://community.smartstore.com/marketplace/file/29-paypal-payment-plugins/ \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Extensions/MiscExtensions.cs b/src/Plugins/SmartStore.PayPal/Extensions/MiscExtensions.cs new file mode 100644 index 0000000000..352d70d951 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Extensions/MiscExtensions.cs @@ -0,0 +1,40 @@ +using System.Net; +using System.Web; +using SmartStore.PayPal.Services; +using SmartStore.PayPal.Settings; +using SmartStore.Services.Orders; + +namespace SmartStore.PayPal +{ + internal static class MiscExtensions + { + public static string GetPayPalUrl(this PayPalSettingsBase settings) + { + return settings.UseSandbox ? + "https://www.sandbox.paypal.com/cgi-bin/webscr" : + "https://www.paypal.com/cgi-bin/webscr"; + } + + public static HttpWebRequest GetPayPalWebRequest(this PayPalSettingsBase settings) + { + if (settings.SecurityProtocol.HasValue) + { + ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; + } + + var request = (HttpWebRequest)WebRequest.Create(GetPayPalUrl(settings)); + return request; + } + + public static PayPalSessionData GetPayPalSessionData(this HttpContextBase httpContext, CheckoutState state = null) + { + if (state == null) + state = httpContext.GetCheckoutState(); + + if (!state.CustomProperties.ContainsKey(PayPalPlusProvider.SystemName)) + state.CustomProperties.Add(PayPalPlusProvider.SystemName, new PayPalSessionData()); + + return state.CustomProperties[PayPalPlusProvider.SystemName] as PayPalSessionData; + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Filters/PayPalExpressCheckoutFilter.cs b/src/Plugins/SmartStore.PayPal/Filters/PayPalExpressCheckoutFilter.cs index 8536350363..317fae97a0 100644 --- a/src/Plugins/SmartStore.PayPal/Filters/PayPalExpressCheckoutFilter.cs +++ b/src/Plugins/SmartStore.PayPal/Filters/PayPalExpressCheckoutFilter.cs @@ -6,7 +6,6 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Services; using SmartStore.Services.Common; -using SmartStore.Services.Customers; using SmartStore.Services.Payments; namespace SmartStore.PayPal.Filters @@ -15,19 +14,18 @@ public class PayPalExpressCheckoutFilter : IActionFilter { private static readonly string[] s_interceptableActions = new string[] { "PaymentMethod" }; - private readonly IGenericAttributeService _genericAttributeService; - private readonly HttpContextBase _httpContext; - private readonly ICommonServices _services; - private readonly ICustomerService _customerService; + private readonly IGenericAttributeService _genericAttributeService; + private readonly HttpContextBase _httpContext; + private readonly ICommonServices _services; - public PayPalExpressCheckoutFilter(IGenericAttributeService genericAttributeService, - HttpContextBase httpContext, ICommonServices services, - ICustomerService customerService) + public PayPalExpressCheckoutFilter( + IGenericAttributeService genericAttributeService, + HttpContextBase httpContext, + ICommonServices services) { - _genericAttributeService = genericAttributeService; - _httpContext = httpContext; - _services = services; - _customerService = customerService; + _genericAttributeService = genericAttributeService; + _httpContext = httpContext; + _services = services; } private static bool IsInterceptableAction(string actionName) @@ -37,36 +35,35 @@ private static bool IsInterceptableAction(string actionName) public void OnActionExecuting(ActionExecutingContext filterContext) { - if (filterContext == null || filterContext.ActionDescriptor == null || filterContext.HttpContext == null || filterContext.HttpContext.Request == null) - return; + if (filterContext == null || filterContext.ActionDescriptor == null || filterContext.HttpContext == null || filterContext.HttpContext.Request == null) + return; - string actionName = filterContext.ActionDescriptor.ActionName; + var attr = Convert.ToBoolean(filterContext.HttpContext.GetCheckoutState().CustomProperties["PayPalExpressButtonUsed"]); - var store = _services.StoreContext.CurrentStore; - var customer = _services.WorkContext.CurrentCustomer; - - var attr = Convert.ToBoolean(filterContext.HttpContext.GetCheckoutState().CustomProperties["PayPalExpressButtonUsed"]); + //verify paypalexpressprovider was used + if (attr == true) + { + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; - //verify paypalexpressprovider was used - if (attr == true) { - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, "Payments.PayPalExpress", store.Id); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, PayPalExpressProvider.SystemName, store.Id); - var paymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; - if (paymentRequest == null) - { - _httpContext.Session["OrderPaymentInfo"] = new ProcessPaymentRequest(); - } + var paymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; + if (paymentRequest == null) + { + _httpContext.Session["OrderPaymentInfo"] = new ProcessPaymentRequest(); + } - //delete property for backward navigation - _httpContext.GetCheckoutState().CustomProperties.Remove("PayPalExpressButtonUsed"); + //delete property for backward navigation + _httpContext.GetCheckoutState().CustomProperties.Remove("PayPalExpressButtonUsed"); - filterContext.Result = new RedirectToRouteResult( - new RouteValueDictionary { - { "Controller", "Checkout" }, - { "Action", "Confirm" }, - { "area", null } - }); - } + filterContext.Result = new RedirectToRouteResult( + new RouteValueDictionary { + { "Controller", "Checkout" }, + { "Action", "Confirm" }, + { "area", null } + }); + } } public void OnActionExecuted(ActionExecutedContext filterContext) diff --git a/src/Plugins/SmartStore.PayPal/Filters/PayPalExpressWidgetZoneFilter.cs b/src/Plugins/SmartStore.PayPal/Filters/PayPalExpressWidgetZoneFilter.cs new file mode 100644 index 0000000000..f5eaa49d36 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Filters/PayPalExpressWidgetZoneFilter.cs @@ -0,0 +1,69 @@ +using System; +using System.Web.Mvc; +using SmartStore.PayPal.Settings; +using SmartStore.Services; +using SmartStore.Services.Payments; +using SmartStore.Web.Framework.UI; +using SmartStore.Web.Models.ShoppingCart; + +namespace SmartStore.PayPal.Filters +{ + public class PayPalExpressWidgetZoneFilter : IActionFilter, IResultFilter + { + private readonly Lazy _widgetProvider; + private readonly Lazy _paymentService; + private readonly Lazy _services; + private readonly Lazy _payPalExpressSettings; + + public PayPalExpressWidgetZoneFilter( + Lazy widgetProvider, + Lazy paymentService, + Lazy services, + Lazy payPalExpressSettings) + { + _widgetProvider = widgetProvider; + _paymentService = paymentService; + _services = services; + _payPalExpressSettings = payPalExpressSettings; + } + + public void OnActionExecuting(ActionExecutingContext filterContext) + { + } + + public void OnActionExecuted(ActionExecutedContext filterContext) + { + } + + public void OnResultExecuting(ResultExecutingContext filterContext) + { + if (filterContext.IsChildAction) + return; + + // should only run on a full view rendering result + var result = filterContext.Result as ViewResultBase; + if (result == null) + return; + + var controller = filterContext.RouteData.Values["controller"] as string; + var action = filterContext.RouteData.Values["action"] as string; + + if (action.IsCaseInsensitiveEqual("FlyoutShoppingCart") && controller.IsCaseInsensitiveEqual("ShoppingCart")) + { + var model = filterContext.Controller.ViewData.Model as MiniShoppingCartModel; + + if (model != null && model.DisplayCheckoutButton && _payPalExpressSettings.Value.ShowButtonInMiniShoppingCart) + { + if (_paymentService.Value.IsPaymentMethodActive(PayPalExpressProvider.SystemName, _services.Value.StoreContext.CurrentStore.Id)) + { + _widgetProvider.Value.RegisterAction("mini_shopping_cart_bottom", "MiniShoppingCart", "PayPalExpress", new { area = "SmartStore.PayPal" }); + } + } + } + } + + public void OnResultExecuted(ResultExecutedContext filterContext) + { + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs new file mode 100644 index 0000000000..7c47079830 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs @@ -0,0 +1,49 @@ +using System; +using System.Web.Mvc; +using System.Web.Routing; +using SmartStore.Core.Domain.Customers; +using SmartStore.Services; +using SmartStore.Services.Common; +using SmartStore.Services.Payments; + +namespace SmartStore.PayPal.Filters +{ + public class PayPalPlusCheckoutFilter : IActionFilter + { + private readonly ICommonServices _services; + private readonly IPaymentService _paymentService; + private readonly Lazy _genericAttributeService; + + public PayPalPlusCheckoutFilter( + ICommonServices services, + IPaymentService paymentService, + Lazy genericAttributeService) + { + _services = services; + _paymentService = paymentService; + _genericAttributeService = genericAttributeService; + } + + public void OnActionExecuting(ActionExecutingContext filterContext) + { + if (filterContext == null || filterContext.ActionDescriptor == null || filterContext.HttpContext == null || filterContext.HttpContext.Request == null) + return; + + var store = _services.StoreContext.CurrentStore; + + if (!_paymentService.IsPaymentMethodActive(PayPalPlusProvider.SystemName, store.Id)) + return; + + _genericAttributeService.Value.SaveAttribute(_services.WorkContext.CurrentCustomer, SystemCustomerAttributeNames.SelectedPaymentMethod, PayPalPlusProvider.SystemName, store.Id); + + var routeValues = new RouteValueDictionary(new { action = "PaymentWall", controller = "PayPalPlus" }); + + filterContext.Result = new RedirectToRouteResult("SmartStore.PayPalPlus", routeValues); + } + + public void OnActionExecuted(ActionExecutedContext filterContext) + { + + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs new file mode 100644 index 0000000000..cbe12c7aaf --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs @@ -0,0 +1,58 @@ +using System; +using System.Web; +using System.Web.Mvc; +using SmartStore.Web.Framework.UI; + +namespace SmartStore.PayPal.Filters +{ + public class PayPalPlusWidgetZoneFilter : IActionFilter, IResultFilter + { + private readonly Lazy _httpContext; + private readonly Lazy _widgetProvider; + + public PayPalPlusWidgetZoneFilter( + Lazy httpContext, + Lazy widgetProvider) + { + _httpContext = httpContext; + _widgetProvider = widgetProvider; + } + + public void OnActionExecuting(ActionExecutingContext filterContext) + { + } + + public void OnActionExecuted(ActionExecutedContext filterContext) + { + } + + public void OnResultExecuting(ResultExecutingContext filterContext) + { + if (filterContext.IsChildAction) + return; + + // should only run on a full view rendering result + var result = filterContext.Result as ViewResultBase; + if (result == null) + return; + + var controller = filterContext.RouteData.Values["controller"] as string; + var action = filterContext.RouteData.Values["action"] as string; + + if (action.IsCaseInsensitiveEqual("Completed") && controller.IsCaseInsensitiveEqual("Checkout")) + { + var instruct = _httpContext.Value.Session[PayPalPlusProvider.CheckoutCompletedKey] as string; + + if (instruct.HasValue()) + { + _widgetProvider.Value.RegisterAction("checkout_completed_top", "CheckoutCompleted", "PayPalPlus", new { area = Plugin.SystemName }); + _widgetProvider.Value.RegisterAction("mobile_checkout_completed_top", "CheckoutCompleted", "PayPalPlus", new { area = Plugin.SystemName }); + } + } + } + + public void OnResultExecuted(ResultExecutedContext filterContext) + { + } + } +} diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 0b796c2b89..0568ab1572 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -1,29 +1,100 @@  - PayPal Zahlungsmethoden + PayPal Zahlungsarten - Stellt die PayPal-Zahlungsmethoden PayPal Express, PayPal Standard und PayPal Direct zur Verfügung. + Stellt die PayPal-Zahlungsarten PayPal Standard, PayPal Direct, PayPal Express und PayPal PLUS zur Verfügung. + + API Zugang;Datenaustausch;Sonstiges + + + {0}]]> + + + Mitteilung;Ereignis;Ereignis-ID;Status;Betrag;Zahlungs-ID + + + Sonstiges + + + {0} hat die Forderung gegen Sie im Rahmen eines laufenden Factoringvertrages an die PayPal (Europe) S.àr.l. et Cie, S.C.A. abgetreten. Zahlungen mit schuldbefreiender Wirkung können nur an die PayPal (Europe) S.àr.l. et Cie, S.C.A. geleistet werden. + + + Bitte überweisen Sie den fälligen Betrag auf folgendes Konto. + + + Referenz;Bankleitzahl;Bank;BIC;IBAN;Kontoinhaber;Kontonummer;Betrag;Zahlung fällig am;Details + + + Mitteilung wurde ignoriert, da {0} Aufträge mit der Payment-ID {1} gefunden wurden. + + + Währung {0} entspricht nicht der Leitwährung des Shops {1}. + + + Keine Weiterleitungs-URL von PayPal erhalten. + + + + Client-ID + + + Legt die API Client-ID fest. + + + Secret + + + Legt die API Secret fest. + + + Bitte geben Sie Ihre API Zugangsdaten ein. + + + Experience Profil-ID + + + Legt die Experience Profil-ID fest. Das Profil beinhaltet globale Daten wie z.B. Shop-Name und -Logo. Ein Profil braucht nur einmalig angelegt werden. Über den Button können Sie ein neues Profil erstellen oder ein vorhandenes aktualisieren. + + + Experience Profil-ID + + + Webhook-ID + + + Legt die Webhook-ID fest. Über einen Webhook sendet PayPal Statusänderungen von Zahlungen an Ihren Shop, wodurch der Zahlungsstatus Ihrer Aufträge automatisch aktualisert werden kann. + + - - Ihre Einstellungen wurden erfolgreich gespeichert. - Sandbox benutzen Aktiviert die Nutzung der PayPal Sandbox (Testumgebung). + + Zahlungsstatus über IPN ändern + + + Legt fest, ob durch eingehende IPNs der Zahlunsgstatus eines Auftrags angepasst werden soll. + Transaktionsmodus Bestimmen Sie den Transaktionsmodus. + + Sicherheitsprotokoll + + + Legt das mit der PayPal-API zu verwendende Sicherheitsprotokoll fest. + API Benutzername @@ -54,11 +125,34 @@ Zusätzliche prozentuale Gebühr zum Gesamtbetrag. Es wird ein fester Wert verwendet, falls diese Option nicht aktiviert ist. + + Das Zahlungsmodul Modul konnte nicht geladen werden. + + + PayPal IPN. Wiederkehrende Zahlung. + + + PayPal IPN. Bestellung konnte nicht gefunden werden. + + + PayPal IPN. Benachrichtigung konnte nicht abgerufen werden. + + + Teilerstattung von {0} + + + Zahlartgebühren + + + Rabatt + + + Angewendeter Geschenkgutschein + - - - + + PayPal Express @@ -78,29 +172,14 @@
    1. In Ihr Premier- oder Business-Konto einloggen.
    2. Auf den Register Mein Profil klicken.
    3. +
    4. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
    5. Sofortige Zahlungsbestätigung klicken.
    6. Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
    7. -
    8. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (http://www.yourStore.com/Plugins/PayPalExpress/IPNHandler) eingeben.
    9. +
    10. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalExpress/IPNHandler) eingeben.
    11. Speichern klicken. Danach sollten Sie eine Nachricht über die erfolgreiche Aktivierung von IPN erhalten.
    ]]> -
    - - - PayPal-Express-Modul konnte nicht geladen werden. - - - Neuer Zahlungsstatus - - - PayPal Express IPN. Wiederkehrende Information. - - - PayPal Express IPN. Bestellung konnte nicht gefunden werden. - - - PayPal Express IPN. Benachrichtigung konnte nicht abgerufen werden. - +
    Wiederkehrende Zahlung @@ -113,28 +192,26 @@ Autorisierung sofort, Abbuchung später - oder - - - Checkout-Button auf der Warenkorbseite aktivieren + + Button im Miniwarenkorb anzeigen - - Aktiviert den Checkout-Button auf der Warenkorbseite. + + Legt fest, ob der Checkout-Button im Miniwarenkorb angezeigt werden soll. - Bestätigte Emailadresse + Bestätigte E-Mail-Adresse - Wählen Sie ob sich bei der verwendetet Emailadresse, um eine von PayPal bestätigte Emailadresse handeln muss. + Legt fest, ob es sich bei der verwendeten E-Mail-Adresse, um eine von PayPal bestätigte Adresse handeln muss. Anzeige der Lieferadresse unterdrücken - Bestimmen Sie ob auf der PayPal Seite die Lieferadresse angezeigt werden soll. + Legt fest, ob auf der PayPal Seite die Lieferadresse angezeigt werden soll. Zeitlimit für API-Callback @@ -155,7 +232,6 @@ PayPal Direct - @@ -173,28 +249,14 @@
    1. In Ihr Premier- oder Business-Konto einloggen.
    2. Auf den Register Mein Profil klicken.
    3. +
    4. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
    5. Sofortige Zahlungsbestätigung klicken.
    6. Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
    7. -
    8. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (http://www.yourStore.com/Plugins/PaymentPayPalDirect/IPNHandler) eingeben.
    9. +
    10. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalDirect/IPNHandler) eingeben.
    11. Speichern klicken. Danach sollten Sie eine Nachricht über die erfolgreiche Aktivierung von IPN erhalten.
    ]]>
    - - PayPal-Direct-Modul konnte nicht geladen werden. - - - Neuer Zahlungsstatus - - - PayPal Direct IPN. Wiederkehrende Information. - - - PayPal Direct IPN. Bestellung konnte nicht gefunden werden. - - - PayPal Direct IPN. Benachrichtigung konnte nicht abgerufen werden. - Wiederkehrende Zahlung @@ -213,12 +275,11 @@ PayPal Standard - - Stellen Sie bitte sicher, dass PayPal die Primärwährung Ihres Shops unterstützt, falls Sie dieses Gateway benutzen!

    + Stellen Sie bitte sicher, dass PayPal die Primärwährung Ihres Shops unterstützt, falls Sie dieses Gateway benutzen.

    Sie müssen PDT (Payment Data Transfer) und die automatische Rückleitung in Ihrem PayPal-Profil aktivieren, um PDT nutzen zu können. Ferner benötigen Sie einen PDT-Identitäts-Token, der für jede PDT-Kommunikation mit PayPal erforderlich ist. Folgen Sie den Schritten, um Ihr Konto für PDT zu konfigurieren:

    @@ -227,7 +288,7 @@
  • Klicken Sie auf den Register Mein Profil.
  • Klicken Sie unter Verkäufer/Händler auf Website-Einstellungen.
  • Aktivieren Sie Automatische Rückleitung.
  • -
  • Als Rückleitungs-URL geben Sie bitte die Seite Ihres Shops ein, die die Transaktions-ID von PayPal nach einer Kundenzahlung empfängt (http://www.yourStore.com/Plugins/PaymentPayPalStandard/PDTHandler).
  • +
  • Als Rückleitungs-URL geben Sie bitte die Seite Ihres Shops ein, die die Transaktions-ID von PayPal nach einer Kundenzahlung empfängt (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/PDTHandler).
  • Aktivieren Sie Übertragung der Zahlungsdaten.
  • Klicken Sie auf Speichern.
  • Klicken Sie unter Verkäufer/Händler auf Website-Einstellungen.
  • @@ -241,9 +302,10 @@
    1. In Ihr Premier- oder Business-Konto einloggen.
    2. Auf den Register Mein Profil klicken.
    3. -
    4. Sofortige Zahlungsbestätigung klicken.
    5. +
    6. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
    7. +
    8. Zurück zu Mein Profil und auf Sofortige Zahlungsbestätigung klicken.
    9. Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
    10. -
    11. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (http://www.yourStore.com/Plugins/PaymentPayPalStandard/IPNHandler) eingeben.
    12. +
    13. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/IPNHandler) eingeben.
    14. Speichern klicken. Danach sollten Sie eine Nachricht über die erfolgreiche Aktivierung von IPN erhalten.
    @@ -271,23 +333,17 @@ Aktivieren Sie diese Option, falls der durch PayPal übermittelte Gesamtbetrag überprüft werden soll. - - Zusätzliche Gebühren - - - Zusätzliche Gebühren, die dem Kunden berechnet werden sollen. - - - Zusätzliche Gebühren (prozentual) + + Nur warnen - - Zusätzliche prozentuale Gebühr zum Gesamtbetrag. Es wird ein fester Wert verwendet, falls diese Option nicht aktiviert ist. + + Legt fest, ob im Falle einer Nichtübereinstimmung des Gesamtbetrags lediglich eine Auftragsnotiz angelegt werden soll. Ansonsten wird ein Fehler erzeugt und der Zahlungsstatus wird nicht aktualisiert. - Produktbezeichnung und Gesamtbetrag an PayPal übermitteln + Produktbezeichnungen und Einzelpreise übermitteln - Aktivieren Sie diese Option, falls die Produktbezeichnung und der Gesamtbetrag an PayPal übermitteln werden sollen. + Aktivieren Sie diese Option, falls Produktbezeichnungen und Einzelpreise an PayPal übermittelt werden sollen. IPN aktivieren @@ -304,58 +360,94 @@ URL zum IPN-Handler festlegen. - - PayPal Standard Modul konnte nicht geladen werden. - PayPal Standard PDT. Fehler beim Abruf von mc_gross. - PayPal Standard PDT. Der übermittelte Gesamtbetrag {0} entspricht nicht dem des Shops {1}. + PayPal Standard PDT. Der übermittelte Gesamtbetrag {0} entspricht nicht dem des Shops {1} (ungerundet {2}). Die Differenz beträgt {3}. PayPal Standard PDT ist fehlgeschlagen. - - Neuer Zahlungsstatus - - - PayPal Standard IPN. Wiederkehrende Information. - - - PayPal Standard IPN. Bestellung konnte nicht gefunden werden. - - - PayPal Standard IPN. Benachrichtigung konnte nicht abgerufen werden. - Unvollständige Zugangsdaten für die PayPal API. Bitte geben Sie die erforderlichen Zugangsdaten im Konfigurationsbereich ein. Versandgebühren - - Zahlungsgebühren - Umsatzsteuer +Total: {0} +Währung: {1} +Zahlenderstatus: {2} +Zahlungsstatus: {3} +Ausstehend (Grund): {4} +Transaktions-ID: {5} +Zahlungstyp: {6} +Zahlender-ID: {7} +Empfänger-ID: {8} +Rechnung: {9} +Zahlungsgebühr: {10}]]> + + +
    +
    + + + PayPal PLUS + + + + + + PayPal PLUS bietet die Möglichkeit per PayPal, Kreditkarte, Lastschriftverfahren und per Rechnung zu bezahlen. +Die Bezahlung auf Rechnung steht möglicherweise nicht allen Händlern zur Verfügung. PayPal PLUS unterstützt nur den Transaktionstyp der sofortigen Buchung und ist derzeit nur in Deutschland verfügbar.

    +

    PayPal PLUS ersetzt die Zahlartenliste im Kassenbereich durch die PayPal Payment Wall. Sie können der Payment Wall jedoch bis zu 5 weitere Offline- bzw. Weiterleitungszahlarten hinzufügen.

    +

    So richten Sie PayPal PLUS ein: +

      +
    1. Legen Sie unter My Apps & Credentials eine neue REST API Anwendung an. +Unter Manage your applications finden Sie weitere Informationen zum Thema PayPal Anwendungen (Englisch).
    2. +
    3. Klicken Sie auf den Namen der Anwendung und tragen Sie Client-ID und Secret weiter unten auf dieser Seite ein. Speichern.
    4. +
    5. Optional: Klicken Sie unten bei Experience Profil-ID auf Hinzufügen, um ein Profil zu erstellen. Ihre Shop-Daten (Name, Logo etc.) werden Käufern dadurch beim Bezahlen auf den PayPal-Seiten angezeigt.
    6. +
    7. Optional: Klicken Sie unten bei Webhook-ID auf Hinzufügen, um einen Webhook zu erstellen. Über einen Webhook empfängt der Shop PayPal-Mitteilungen und aktualisiert u.U. den Zahlungsstatus Ihrer Aufträge.
    8. +

    ]]>
    + + Wir können Ihnen leider keine PayPal PLUS Zahlungsart anbieten. Bitte versuchen Sie er später erneut. + + + Entschuldigung, das hat gerade nicht funktioniert. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode. + + + .]]> + + + Weitere Zahlarten + + + Sie können bis zu 5 weitere Zahlarten festlegen. Diese werden zusammen mit den PayPal PLUS Zahlarten im Kassenbereich angeboten. + + + Es können maximal 5 weitere Zahlarten festgelegt werden. + + + Logo der Zahlarten anzeigen + + + Legt fest, ob für die weiteren Zahlarten deren Logo angezeigt werden soll. SSL erforderlich. + + + Beschreibung der Zahlarten anzeigen + + + Legt fest, ob für die weiteren Zahlarten deren Beschreibung angezeigt werden soll. +
    -
    +
    +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 529e7c2e12..700b806aa7 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -4,7 +4,152 @@ PayPal Payment Methods - Provides the PayPal payment methods PayPal Express, PayPal Standard and PayPal Direct. + Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal PLUS. + + + + API access;Data exchange;Miscellaneous + + + {0}]]> + + + Message;Event;Event-ID;State;Amount;Payment-ID + + + Miscellaneous + + + The execution of the payment has been declined by PayPal. + + + {0} hat die Forderung gegen Sie im Rahmen eines laufenden Factoringvertrages an die PayPal (Europe) S.àr.l. et Cie, S.C.A. abgetreten. Zahlungen mit schuldbefreiender Wirkung können nur an die PayPal (Europe) S.àr.l. et Cie, S.C.A. geleistet werden. + + + Please transfer the due amount to the following bank account. + + + Reference;Bank routing number;Bank;BIC;IBAN;Account holder;Account number;Amount;Payment due date;Details + + + Message has been ignored because {0} orders with payment ID {1} found. + + + Currency {0} does not equal primary store currency {1}. + + + No redirect URL received from PayPal. + + + + Client ID + + + Specifies the API client ID. + + + Secret + + + Specifies the API secret. + + + Please enter your API credentials. + + + Experience profile ID + + + Specifies the experience profile ID. The profile contains global data like shop name and logo. A profile needs to be created only once. Using the button, you can create a new profile or update an existing one. + + + Webhook ID + + + Specifies the webhook ID. PayPal sends payment status changes via a webhook to your shop, whereby the payment status of your orders can be automatically synced. + + + + + + Use Sandbox + + + Check the box to enable Sandbox (testing environment). + + + IPN may change payment status + + + Specifies whether received IPNs should change the payment status of an order. + + + Transaction mode + + + Specify the payment transaction mode. + + + Security protocol + + + Specifies the security protocol to use with the PayPal API. + + + API Account Name + + + Specify the API account name. + + + API Account Password + + + Specify the API account password. + + + Signature + + + Enter the signature. + + + Additional fee + + + Enter additional fee to charge your customers. + + + Additional fee. Use percentage. + + + Specifies whether to apply a percentage additional fee to the order total. A fixed value is used if not enabled. + + + The payment module cannot be loaded. + + + PayPal IPN. Recurring payment. + + + PayPal IPN. Order cannot be found. + + + PayPal IPN. Failed to retrieve notification. + + + Partial refund of {0} + + + Payment method fee + + + Discount + + + Giftcard Applied + + @@ -28,72 +173,14 @@
    1. Log in to your Premier or Business account.
    2. Click the Profile subtab.
    3. +
    4. Click Language Encoding and More options and select UTF-8.
    5. Click Instant Payment Notification in the Selling Preferences column.
    6. Click the Edit IPN Settings button to update your settings.
    7. -
    8. Select Receive IPN messages (enabled) and enter the URL of your IPN handler (http://www.yourStore.com/Plugins/PayPalExpress/IPNHandler).
    9. +
    10. Select Receive IPN messages (enabled) and enter the URL of your IPN handler (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalExpress/IPNHandler).
    11. Click Save, and you should get a message that you have successfully activated IPN.
    ]]> -
    - - - Use Sandbox - - - Check to enable Sandbox (testing environment). - - - Transaction mode - - - Specify transaction mode. - - - API Account Name - - - Specify API account name. - - - API Account Password - - - Specify API account password. - - - Signature - - - Specify signature. - - - Additional fee - - - Enter additional fee to charge your customers. - - - Additional fee. Use percentage - - - Determines whether to apply a percentage additional fee to the order total. A fixed value is used if not enabled. - - - - PayPal Express module cannot be loaded. - - - New payment status - - - PayPal Express IPN. Recurring info. - - - PayPal Express IPN. Order cannot be found. - - - PayPal Express IPN. Failed to retrieve notification. - + recurring payment @@ -106,16 +193,14 @@ Authorize immediately, debit later - or - - - Activate checkout button in shopping cart + + Show button in mini shopping cart - - Activates the checkout button in the shopping cart. + + Soecifies to show the checkout button in the mini shopping cart. Confirmed email address @@ -147,7 +232,6 @@ PayPal Direct - @@ -165,70 +249,14 @@
    1. Log in to your Premier or Business account.
    2. Click the Profile subtab.
    3. +
    4. Click Language Encoding and More options and select UTF-8.
    5. Click Instant Payment Notification in the Selling Preferences column.
    6. Click the Edit IPN Settings button to update your settings.
    7. -
    8. Select Receive IPN messages (enabled) and enter the URL of your IPN handler (http://www.yourStore.com/Plugins/PaymentPayPalDirect/IPNHandler).
    9. +
    10. Select Receive IPN messages (enabled) and enter the URL of your IPN handler (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalDirect/IPNHandler).
    11. Click Save, and you should get a message that you have successfully activated IPN.
    ]]>
    - - Use Sandbox - - - Check to enable Sandbox (testing environment). - - - Transaction mode - - - Specify transaction mode. - - - API Account Name - - - Specify API account name. - - - API Account Password - - - Specify API account password. - - - Signature - - - Specify signature. - - - Additional fee - - - Enter additional fee to charge your customers. - - - Additional fee. Use percentage - - - Determines whether to apply a percentage additional fee to the order total. A fixed value is used if not enabled. - - - PayPal Direct module cannot be loaded. - - - New payment status - - - PayPal Direct IPN. Recurring info. - - - PayPal Direct IPN. Order cannot be found. - - - PayPal Direct IPN. Failed to retrieve notification. - recurring payment @@ -257,10 +285,10 @@ you send to PayPal. Follow these steps to configure your account for PDT:

    1. Log in to your PayPal account.
    2. -
    3. Click the Profile subtab.
    4. +
    5. Click the Profile subtab.
    6. Click Website Payment Preferences in the Seller Preferences column.
    7. Under Auto Return for Website Payments, click the On radio button.
    8. -
    9. For the Return URL, enter the URL on your site that will receive the transaction ID posted by PayPal after a customer payment (http://www.yourStore.com/Plugins/PaymentPayPalStandard/PDTHandler).
    10. +
    11. For the Return URL, enter the URL on your site that will receive the transaction ID posted by PayPal after a customer payment (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/PDTHandler).
    12. Under Payment Data Transfer, click the On radio button.
    13. Click Save.
    14. Click Website Payment Preferences in the Seller Preferences column.
    15. @@ -273,10 +301,11 @@ The second way is to configure your paypal account to activate this service; follow these steps:
      1. Log in to your Premier or Business account.
      2. -
      3. Click the Profile subtab.
      4. -
      5. Click Instant Payment Notification in the Selling Preferences column.
      6. +
      7. Click the Profile subtab.
      8. +
      9. Click Language Encoding and More options and select UTF-8.
      10. +
      11. Back to Profile click Instant Payment Notification in the Selling Preferences column.
      12. Click the Edit IPN Settings button to update your settings.
      13. -
      14. Select Receive IPN messages (Enabled) and enter the URL of your IPN handler (http://www.yourStore.com/Plugins/PaymentPayPalStandard/IPNHandler).
      15. +
      16. Select Receive IPN messages (Enabled) and enter the URL of your IPN handler (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/IPNHandler).
      17. Click Save, and you should get a message that you have successfully activated IPN.
      @@ -286,12 +315,6 @@ After confirmation of the order, you will be redirected straight to PayPal. Please provide your data for online banking. - - Use Sandbox - - - Check to enable Sandbox (testing environment). - Business Email @@ -308,31 +331,25 @@ Validate order total - Check if the PDT handler should validate PayPal's order totals. - - - Additional fee - - - Enter additional fee to charge your customers. + Check the box if the PDT handler should validate PayPal's order totals. - - Additional fee. Use percentage + + Only warn - - Determines whether to apply a percentage additional fee to the order total. If not enabled, a fixed value is used. + + Specifies whether to add an order note if the order total is unequal. Otherwise an error will be logged and the payment status is not updated. - Pass product names and order totals to PayPal + Transmit product names and unit prices - Check if product names and order totals should be passed to PayPal. + Check the box if product names and unit prices should be transmitted to PayPal. Enable IPN - Check if IPN (Instant Payment Notification) is enabled. + Check the box if IPN (Instant Payment Notification) is enabled. Leave blank to use the default IPN handler URL. @@ -343,58 +360,94 @@ Specify IPN Handler. - - PayPal Standard module cannot be loaded. - PayPal Standard PDT. Error getting mc_gross. - PayPal Standard PDT. Returned order total {0} doesn't equal order total {1}. + PayPal Standard PDT. Returned order total {0} doesn't equal order total {1} (not rounded {2}). The difference is {3}. PayPal Standard PDT failed. - - New payment status - - - PayPal Standard IPN. Recurring info. - - - PayPal Standard IPN. Order cannot be found. - - - PayPal Standard IPN. Failed to retrieve notification. - Incomplete credentials for the PayPal API. Please enter the required data in the configuration area. Shipping fee - - Payment method fee - Sales tax +Total: {0} +Currency: {1} +Payer status: {2} +Payment status: {3} +Pending reason: {4} +Transaction ID: {5} +Payment type: {6} +Payer ID: {7} +Receiver ID: {8} +Invoice: {9} +Payment fee: {10}]]> + + + PayPal PLUS + + + + + + PayPal PLUS is a solution where PayPal offers PayPal, Credit Card, Direct Debit (ELV) and pay upon invoice as individual payment options on the payment selection page. +Pay upon invoice may not be available for all merchants. PayPal PLUS only supports the transaction type of instant settlement and is only available in Germany at the moment.

      +

      PayPal PLUS replaces the list of payment methods in checkout by the PayPal payment wall. You can add up to 5 offline or redirection payment methods to the payment wall.

      +

      How to set up PayPal PLUS: +

        +
      1. Create a new REST API application under My Apps & Credentials. +Go to Manage your applications to get more information about PayPal applications.
      2. +
      3. Click the name of the application and enter Client-ID and Secret below on this page. Save.
      4. +
      5. Optional: Click Add next to Experience profile ID to create a new profile. Your shop data (name, logo etc.) can now be displayed to customers when paying on PayPal pages.
      6. +
      7. Optional: Click Add next to Webhook-ID to create a webhook. Through a webhook the shop receives PayPal messages und possibly updates the payment state of your orders.
      8. +

      ]]> +
      +
      + + Unfortunately we cannot offer you a PayPal PLUS payment method. Please try again later. + + + I'm sorry, that just didn't work. Please try again or select another payment method. + + + .]]> + + + More payment methods + + + You can set up to 5 more payment methods. These are offered along with the PayPal PLUS payment methods during the checkout process. + + + Maximum of 5 third party payment methods can be specified. + + + Display logo of other payment methods + + + Specifies whether to display the logo for other payment methods. SSL required. + + + Display description of other payment methods + + + Specifies whether to display the description for other payment methods. + +
      +
      + \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index 6918df7103..f81f6c2d7f 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -1,31 +1,54 @@ using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Net; using System.Web.Mvc; using SmartStore.PayPal.Settings; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.PayPal.Models { - public abstract class ApiConfigurationModel: ModelBase + public abstract class ApiConfigurationModel: ModelBase { public string[] ConfigGroups { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPal.UseSandbox")] + [SmartResourceDisplayName("Plugins.Payments.PayPal.UseSandbox")] public bool UseSandbox { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPal.IpnChangesPaymentStatus")] + public bool IpnChangesPaymentStatus { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPal.TransactMode")] public int TransactMode { get; set; } public SelectList TransactModeValues { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPal.SecurityProtocol")] + public SecurityProtocolType? SecurityProtocol { get; set; } + public List AvailableSecurityProtocols { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPal.ApiAccountName")] public string ApiAccountName { get; set; } [SmartResourceDisplayName("Plugins.Payments.PayPal.ApiAccountPassword")] + [DataType(DataType.Password)] public string ApiAccountPassword { get; set; } [SmartResourceDisplayName("Plugins.Payments.PayPal.Signature")] public string Signature { get; set; } + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.ClientId")] + public string ClientId { get; set; } + + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.Secret")] + public string Secret { get; set; } + + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.ExperienceProfileId")] + public string ExperienceProfileId { get; set; } + + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.WebhookId")] + public string WebhookId { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFee")] public decimal AdditionalFee { get; set; } @@ -39,7 +62,9 @@ public void Copy(PayPalDirectPaymentSettings settings, bool fromSettings) { if (fromSettings) { - UseSandbox = settings.UseSandbox; + SecurityProtocol = settings.SecurityProtocol; + UseSandbox = settings.UseSandbox; + IpnChangesPaymentStatus = settings.IpnChangesPaymentStatus; TransactMode = Convert.ToInt32(settings.TransactMode); ApiAccountName = settings.ApiAccountName; ApiAccountPassword = settings.ApiAccountPassword; @@ -49,7 +74,9 @@ public void Copy(PayPalDirectPaymentSettings settings, bool fromSettings) } else { - settings.UseSandbox = UseSandbox; + settings.SecurityProtocol = SecurityProtocol; + settings.UseSandbox = UseSandbox; + settings.IpnChangesPaymentStatus = IpnChangesPaymentStatus; settings.TransactMode = (TransactMode)TransactMode; settings.ApiAccountName = ApiAccountName; settings.ApiAccountPassword = ApiAccountPassword; @@ -62,10 +89,10 @@ public void Copy(PayPalDirectPaymentSettings settings, bool fromSettings) public class PayPalExpressConfigurationModel : ApiConfigurationModel { - [SmartResourceDisplayName("Plugins.Payments.PayPalExpress.Fields.DisplayCheckoutButton")] - public bool DisplayCheckoutButton { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPalExpress.Fields.ShowButtonInMiniShoppingCart")] + public bool ShowButtonInMiniShoppingCart { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPalExpress.Fields.ConfirmedShipment")] + [SmartResourceDisplayName("Plugins.Payments.PayPalExpress.Fields.ConfirmedShipment")] public bool ConfirmedShipment { get; set; } [SmartResourceDisplayName("Plugins.Payments.PayPalExpress.Fields.NoShipmentAddress")] @@ -81,14 +108,16 @@ public void Copy(PayPalExpressPaymentSettings settings, bool fromSettings) { if (fromSettings) { - UseSandbox = settings.UseSandbox; - TransactMode = Convert.ToInt32(settings.TransactMode); - ApiAccountName = settings.ApiAccountName; + SecurityProtocol = settings.SecurityProtocol; + UseSandbox = settings.UseSandbox; + IpnChangesPaymentStatus = settings.IpnChangesPaymentStatus; + TransactMode = Convert.ToInt32(settings.TransactMode); + ApiAccountName = settings.ApiAccountName; ApiAccountPassword = settings.ApiAccountPassword; Signature = settings.Signature; AdditionalFee = settings.AdditionalFee; AdditionalFeePercentage = settings.AdditionalFeePercentage; - DisplayCheckoutButton = settings.DisplayCheckoutButton; + ShowButtonInMiniShoppingCart = settings.ShowButtonInMiniShoppingCart; ConfirmedShipment = settings.ConfirmedShipment; NoShipmentAddress = settings.NoShipmentAddress; CallbackTimeout = settings.CallbackTimeout; @@ -96,22 +125,72 @@ public void Copy(PayPalExpressPaymentSettings settings, bool fromSettings) } else { - settings.UseSandbox = UseSandbox; + settings.SecurityProtocol = SecurityProtocol; + settings.UseSandbox = UseSandbox; + settings.IpnChangesPaymentStatus = IpnChangesPaymentStatus; settings.TransactMode = (TransactMode)TransactMode; - settings.ApiAccountName = ApiAccountName; + settings.ApiAccountName = ApiAccountName; settings.ApiAccountPassword = ApiAccountPassword; settings.Signature = Signature; settings.AdditionalFee = AdditionalFee; settings.AdditionalFeePercentage = AdditionalFeePercentage; - settings.DisplayCheckoutButton = DisplayCheckoutButton; + settings.ShowButtonInMiniShoppingCart = ShowButtonInMiniShoppingCart; settings.ConfirmedShipment = ConfirmedShipment; settings.NoShipmentAddress = NoShipmentAddress; settings.CallbackTimeout = CallbackTimeout; settings.DefaultShippingPrice = DefaultShippingPrice; } } - } + public class PayPalPlusConfigurationModel : ApiConfigurationModel + { + [SmartResourceDisplayName("Plugins.Payments.PayPalPlus.ThirdPartyPaymentMethods")] + public List ThirdPartyPaymentMethods { get; set; } + public List AvailableThirdPartyPaymentMethods { get; set; } + + [SmartResourceDisplayName("Plugins.Payments.PayPalPlus.DisplayPaymentMethodLogo")] + public bool DisplayPaymentMethodLogo { get; set; } + + [SmartResourceDisplayName("Plugins.Payments.PayPalPlus.DisplayPaymentMethodDescription")] + public bool DisplayPaymentMethodDescription { get; set; } + + + public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) + { + if (fromSettings) + { + SecurityProtocol = settings.SecurityProtocol; + UseSandbox = settings.UseSandbox; + TransactMode = (int)Settings.TransactMode.AuthorizeAndCapture; + AdditionalFee = settings.AdditionalFee; + AdditionalFeePercentage = settings.AdditionalFeePercentage; + + ClientId = settings.ClientId; + Secret = settings.Secret; + ExperienceProfileId = settings.ExperienceProfileId; + WebhookId = settings.WebhookId; + ThirdPartyPaymentMethods = settings.ThirdPartyPaymentMethods; + DisplayPaymentMethodLogo = settings.DisplayPaymentMethodLogo; + DisplayPaymentMethodDescription = settings.DisplayPaymentMethodDescription; + } + else + { + settings.SecurityProtocol = SecurityProtocol; + settings.UseSandbox = UseSandbox; + settings.TransactMode = Settings.TransactMode.AuthorizeAndCapture; + settings.AdditionalFee = AdditionalFee; + settings.AdditionalFeePercentage = AdditionalFeePercentage; + + settings.ClientId = ClientId; + settings.Secret = Secret; + settings.ExperienceProfileId = ExperienceProfileId; + settings.WebhookId = WebhookId; + settings.ThirdPartyPaymentMethods = ThirdPartyPaymentMethods; + settings.DisplayPaymentMethodLogo = DisplayPaymentMethodLogo; + settings.DisplayPaymentMethodDescription = DisplayPaymentMethodDescription; + } + } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalDirectPaymentInfoModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalDirectPaymentInfoModel.cs index 6f42e1e694..7f2d0cfbd3 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalDirectPaymentInfoModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalDirectPaymentInfoModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.PayPal.Models { @@ -17,6 +17,7 @@ public PayPalDirectPaymentInfoModel() [SmartResourceDisplayName("Payment.SelectCreditCard")] [AllowHtml] public string CreditCardType { get; set; } + [SmartResourceDisplayName("Payment.SelectCreditCard")] public IList CreditCardTypes { get; set; } @@ -31,9 +32,11 @@ public PayPalDirectPaymentInfoModel() [SmartResourceDisplayName("Payment.ExpirationDate")] [AllowHtml] public string ExpireMonth { get; set; } + [SmartResourceDisplayName("Payment.ExpirationDate")] [AllowHtml] public string ExpireYear { get; set; } + public IList ExpireMonths { get; set; } public IList ExpireYears { get; set; } diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalExpressPaymentInfoModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalExpressPaymentInfoModel.cs index 10b3ce2669..a485049605 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalExpressPaymentInfoModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalExpressPaymentInfoModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.PayPal.Models { diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs new file mode 100644 index 0000000000..280d76ec24 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.PayPal.Models +{ + public class PayPalPlusCheckoutModel : ModelBase + { + public bool UseSandbox { get; set; } + public string BillingAddressCountryCode { get; set; } + public string LanguageCulture { get; set; } + public string ApprovalUrl { get; set; } + public string ErrorMessage { get; set; } + public string PayPalPlusPseudoMessageFlag { get; set; } + public string FullDescription { get; set; } + public string PayPalFee { get; set; } + public string ThirdPartyFees { get; set; } + public bool HasAnyFees { get; set; } + + public List ThirdPartyPaymentMethods { get; set; } + + public class ThirdPartyPaymentMethod + { + public string RedirectUrl { get; set; } + public string MethodName { get; set; } + public string ImageUrl { get; set; } + public string Description { get; set; } + public string PaymentFee { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs index 76f329c55c..9adc991fc6 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs @@ -1,14 +1,24 @@ -using SmartStore.PayPal.Settings; +using System.Collections.Generic; +using System.Net; +using System.Web.Mvc; +using SmartStore.PayPal.Settings; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.PayPal.Models { public class PayPalStandardConfigurationModel : ModelBase { - [SmartResourceDisplayName("Plugins.Payments.PayPal.UseSandbox")] + [SmartResourceDisplayName("Plugins.Payments.PayPal.SecurityProtocol")] + public SecurityProtocolType? SecurityProtocol { get; set; } + public List AvailableSecurityProtocols { get; set; } + + [SmartResourceDisplayName("Plugins.Payments.PayPal.UseSandbox")] public bool UseSandbox { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPal.IpnChangesPaymentStatus")] + public bool IpnChangesPaymentStatus { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.BusinessEmail")] public string BusinessEmail { get; set; } @@ -18,10 +28,13 @@ public class PayPalStandardConfigurationModel : ModelBase [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.PDTValidateOrderTotal")] public bool PdtValidateOrderTotal { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.AdditionalFee")] + [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.PdtValidateOnlyWarn")] + public bool PdtValidateOnlyWarn { get; set; } + + [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFee")] public decimal AdditionalFee { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.AdditionalFeePercentage")] + [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFeePercentage")] public bool AdditionalFeePercentage { get; set; } [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.PassProductNamesAndTotals")] @@ -33,14 +46,17 @@ public class PayPalStandardConfigurationModel : ModelBase [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.IpnUrl")] public string IpnUrl { get; set; } - public void Copy(PayPalStandardPaymentSettings settings, bool fromSettings) + public void Copy(PayPalStandardPaymentSettings settings, bool fromSettings) { if (fromSettings) { + SecurityProtocol = settings.SecurityProtocol; UseSandbox = settings.UseSandbox; + IpnChangesPaymentStatus = settings.IpnChangesPaymentStatus; BusinessEmail = settings.BusinessEmail; PdtToken = settings.PdtToken; PdtValidateOrderTotal = settings.PdtValidateOrderTotal; + PdtValidateOnlyWarn = settings.PdtValidateOnlyWarn; AdditionalFee = settings.AdditionalFee; AdditionalFeePercentage = settings.AdditionalFeePercentage; PassProductNamesAndTotals = settings.PassProductNamesAndTotals; @@ -49,17 +65,19 @@ public void Copy(PayPalStandardPaymentSettings settings, bool fromSettings) } else { + settings.SecurityProtocol = SecurityProtocol; settings.UseSandbox = UseSandbox; + settings.IpnChangesPaymentStatus = IpnChangesPaymentStatus; settings.BusinessEmail = BusinessEmail; settings.PdtToken = PdtToken; settings.PdtValidateOrderTotal = PdtValidateOrderTotal; + settings.PdtValidateOnlyWarn = PdtValidateOnlyWarn; settings.AdditionalFee = AdditionalFee; settings.AdditionalFeePercentage = AdditionalFeePercentage; settings.PassProductNamesAndTotals = PassProductNamesAndTotals; settings.EnableIpn = EnableIpn; settings.IpnUrl = IpnUrl; } - } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Plugin.cs b/src/Plugins/SmartStore.PayPal/Plugin.cs index 4ef737565d..472a7f2680 100644 --- a/src/Plugins/SmartStore.PayPal/Plugin.cs +++ b/src/Plugins/SmartStore.PayPal/Plugin.cs @@ -1,4 +1,6 @@ -using SmartStore.Core.Plugins; +using System; +using SmartStore.Core.Plugins; +using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services.Configuration; using SmartStore.Services.Localization; @@ -9,13 +11,21 @@ public class Plugin : BasePlugin { private readonly ISettingService _settingService; private readonly ILocalizationService _localizationService; + private readonly Lazy _payPalService; public Plugin( ISettingService settingService, - ILocalizationService localizationService) + ILocalizationService localizationService, + Lazy payPalService) { _settingService = settingService; _localizationService = localizationService; + _payPalService = payPalService; + } + + public static string SystemName + { + get { return "SmartStore.PayPal"; } } public override void Install() @@ -23,6 +33,7 @@ public override void Install() _settingService.SaveSetting(new PayPalExpressPaymentSettings()); _settingService.SaveSetting(new PayPalDirectPaymentSettings()); _settingService.SaveSetting(new PayPalStandardPaymentSettings()); + _settingService.SaveSetting(new PayPalPlusPaymentSettings()); _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); @@ -31,9 +42,30 @@ public override void Install() public override void Uninstall() { + try + { + var settings = _settingService.LoadSetting(); + if (settings.WebhookId.HasValue()) + { + var session = new PayPalSessionData(); + var result = _payPalService.Value.EnsureAccessToken(session, settings); + + if (result.Success) + result = _payPalService.Value.DeleteWebhook(settings, session); + + if (!result.Success) + _payPalService.Value.LogError(null, result.ErrorMessage); + } + } + catch (Exception exception) + { + _payPalService.Value.LogError(exception); + } + _settingService.DeleteSetting(); _settingService.DeleteSetting(); _settingService.DeleteSetting(); + _settingService.DeleteSetting(); _localizationService.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); diff --git a/src/Plugins/SmartStore.PayPal/Properties/Settings.Designer.cs b/src/Plugins/SmartStore.PayPal/Properties/Settings.Designer.cs index a04fd43fad..a9236cb586 100644 --- a/src/Plugins/SmartStore.PayPal/Properties/Settings.Designer.cs +++ b/src/Plugins/SmartStore.PayPal/Properties/Settings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.18444 +// Laufzeitversion:4.0.30319.34209 // // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn // der Code erneut generiert wird. diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs index 1227e36887..96a41d0cfa 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using Autofac; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Directory; @@ -8,46 +7,78 @@ using SmartStore.Core.Plugins; using SmartStore.PayPal.Controllers; using SmartStore.PayPal.PayPalSvc; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; -using SmartStore.Services.Configuration; using SmartStore.Services.Customers; -using SmartStore.Services.Directory; using SmartStore.Services.Payments; namespace SmartStore.PayPal { - /// - /// PayPalDirect provider - /// - [SystemName("Payments.PayPalDirect")] + [SystemName("Payments.PayPalDirect")] [FriendlyName("PayPal Direct")] [DisplayOrder(1)] public class PayPalDirectProvider : PayPalProviderBase { - #region Fields - - private readonly ICurrencyService _currencyService; private readonly ICustomerService _customerService; - private readonly CurrencySettings _currencySettings; - #endregion + public PayPalDirectProvider( + ICustomerService customerService) + { + _customerService = customerService; + } - #region Ctor + public static string SystemName { get { return "Payments.PayPalDirect"; } } - public PayPalDirectProvider( ICurrencyService currencyService, - ICustomerService customerService, - CurrencySettings currencySettings, - IComponentContext ctx) + public override bool RequiresInteraction { - _currencyService = currencyService; - _customerService = customerService; - _currencySettings = currencySettings; + get + { + return true; + } } - #endregion + public override RecurringPaymentType RecurringPaymentType + { + get + { + return RecurringPaymentType.Automatic; + } + } - #region Methods + public override PaymentMethodType PaymentMethodType + { + get + { + return PaymentMethodType.Standard; + } + } + + private CreditCardTypeType GetCreditCardType(string creditCardType) + { + var creditCardTypeType = (CreditCardTypeType)Enum.Parse(typeof(CreditCardTypeType), creditCardType); + return creditCardTypeType; + } + + private CountryCodeType GetCountryCodeType(Country country) + { + CountryCodeType payerCountry = CountryCodeType.US; + try + { + payerCountry = (CountryCodeType)Enum.Parse(typeof(CountryCodeType), country.TwoLetterIsoCode); + } + catch { } + + return payerCountry; + } + + protected override string GetControllerName() + { + return "PayPalDirect"; + } + + public override Type GetControllerType() + { + return typeof(PayPalDirectController); + } protected override string GetResourceRootKey() { @@ -63,16 +94,17 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces { var result = new ProcessPaymentResult(); + var store = Services.StoreService.GetStoreById(processPaymentRequest.StoreId); var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); - var settings = CommonServices.Settings.LoadSetting(processPaymentRequest.StoreId); + var settings = Services.Settings.LoadSetting(processPaymentRequest.StoreId); var req = new DoDirectPaymentReq(); req.DoDirectPaymentRequest = new DoDirectPaymentRequestType(); - req.DoDirectPaymentRequest.Version = PayPalHelper.GetApiVersion(); + req.DoDirectPaymentRequest.Version = ApiVersion; var details = new DoDirectPaymentRequestDetailsType(); req.DoDirectPaymentRequest.DoDirectPaymentRequestDetails = details; - details.IPAddress = CommonServices.WebHelper.GetCurrentIpAddress(); + details.IPAddress = Services.WebHelper.GetCurrentIpAddress(); if (details.IPAddress.IsEmpty()) details.IPAddress = "127.0.0.1"; @@ -85,14 +117,14 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces //credit card details.CreditCard = new CreditCardDetailsType(); details.CreditCard.CreditCardNumber = processPaymentRequest.CreditCardNumber; - details.CreditCard.CreditCardType = PayPalHelper.GetPaypalCreditCardType(processPaymentRequest.CreditCardType); + details.CreditCard.CreditCardType = GetCreditCardType(processPaymentRequest.CreditCardType); details.CreditCard.ExpMonthSpecified = true; details.CreditCard.ExpMonth = processPaymentRequest.CreditCardExpireMonth; details.CreditCard.ExpYearSpecified = true; details.CreditCard.ExpYear = processPaymentRequest.CreditCardExpireYear; details.CreditCard.CVV2 = processPaymentRequest.CreditCardCvv2; details.CreditCard.CardOwner = new PayerInfoType(); - details.CreditCard.CardOwner.PayerCountry = PayPalHelper.GetPaypalCountryCodeType(customer.BillingAddress.Country); + details.CreditCard.CardOwner.PayerCountry = GetCountryCodeType(customer.BillingAddress.Country); details.CreditCard.CreditCardTypeSpecified = true; //billing address details.CreditCard.CardOwner.Address = new AddressType(); @@ -104,14 +136,16 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces details.CreditCard.CardOwner.Address.StateOrProvince = customer.BillingAddress.StateProvince.Abbreviation; else details.CreditCard.CardOwner.Address.StateOrProvince = "CA"; - details.CreditCard.CardOwner.Address.Country = PayPalHelper.GetPaypalCountryCodeType(customer.BillingAddress.Country); + details.CreditCard.CardOwner.Address.Country = GetCountryCodeType(customer.BillingAddress.Country); details.CreditCard.CardOwner.Address.PostalCode = customer.BillingAddress.ZipPostalCode; details.CreditCard.CardOwner.Payer = customer.BillingAddress.Email; details.CreditCard.CardOwner.PayerName = new PersonNameType(); details.CreditCard.CardOwner.PayerName.FirstName = customer.BillingAddress.FirstName; details.CreditCard.CardOwner.PayerName.LastName = customer.BillingAddress.LastName; - //order totals - var payPalCurrency = PayPalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)); + + //order totals + var payPalCurrency = GetApiCurrency(store.PrimaryStoreCurrency); + details.PaymentDetails = new PaymentDetailsType(); details.PaymentDetails.OrderTotal = new BasicAmountType(); details.PaymentDetails.OrderTotal.Value = Math.Round(processPaymentRequest.OrderTotal, 2).ToString("N", new CultureInfo("en-us")); @@ -136,14 +170,13 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces } //send request - using (var service = new PayPalAPIAASoapBinding()) + using (var service = GetApiAaService(settings)) { - service.Url = PayPalHelper.GetPaypalServiceUrl(settings); - service.RequesterCredentials = PayPalHelper.GetPaypalApiCredentials(settings); - DoDirectPaymentResponseType response = service.DoDirectPayment(req); + var response = service.DoDirectPayment(req); + + var error = ""; + var success = IsSuccess(response, out error); - string error = ""; - bool success = PayPalHelper.CheckSuccess(Helper, response, out error); if (success) { result.AvsResult = response.AVSCode; @@ -180,25 +213,26 @@ public override ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReque { var result = new ProcessPaymentResult(); + var store = Services.StoreService.GetStoreById(processPaymentRequest.StoreId); var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); - var settings = CommonServices.Settings.LoadSetting(processPaymentRequest.StoreId); + var settings = Services.Settings.LoadSetting(processPaymentRequest.StoreId); var req = new CreateRecurringPaymentsProfileReq(); req.CreateRecurringPaymentsProfileRequest = new CreateRecurringPaymentsProfileRequestType(); - req.CreateRecurringPaymentsProfileRequest.Version = PayPalHelper.GetApiVersion(); + req.CreateRecurringPaymentsProfileRequest.Version = ApiVersion; var details = new CreateRecurringPaymentsProfileRequestDetailsType(); req.CreateRecurringPaymentsProfileRequest.CreateRecurringPaymentsProfileRequestDetails = details; details.CreditCard = new CreditCardDetailsType(); details.CreditCard.CreditCardNumber = processPaymentRequest.CreditCardNumber; - details.CreditCard.CreditCardType = PayPalHelper.GetPaypalCreditCardType(processPaymentRequest.CreditCardType); + details.CreditCard.CreditCardType = GetCreditCardType(processPaymentRequest.CreditCardType); details.CreditCard.ExpMonthSpecified = true; details.CreditCard.ExpMonth = processPaymentRequest.CreditCardExpireMonth; details.CreditCard.ExpYearSpecified = true; details.CreditCard.ExpYear = processPaymentRequest.CreditCardExpireYear; details.CreditCard.CVV2 = processPaymentRequest.CreditCardCvv2; details.CreditCard.CardOwner = new PayerInfoType(); - details.CreditCard.CardOwner.PayerCountry = PayPalHelper.GetPaypalCountryCodeType(customer.BillingAddress.Country); + details.CreditCard.CardOwner.PayerCountry = GetCountryCodeType(customer.BillingAddress.Country); details.CreditCard.CreditCardTypeSpecified = true; details.CreditCard.CardOwner.Address = new AddressType(); @@ -210,7 +244,7 @@ public override ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReque details.CreditCard.CardOwner.Address.StateOrProvince = customer.BillingAddress.StateProvince.Abbreviation; else details.CreditCard.CardOwner.Address.StateOrProvince = "CA"; - details.CreditCard.CardOwner.Address.Country = PayPalHelper.GetPaypalCountryCodeType(customer.BillingAddress.Country); + details.CreditCard.CardOwner.Address.Country = GetCountryCodeType(customer.BillingAddress.Country); details.CreditCard.CardOwner.Address.PostalCode = customer.BillingAddress.ZipPostalCode; details.CreditCard.CardOwner.Payer = customer.BillingAddress.Email; details.CreditCard.CardOwner.PayerName = new PersonNameType(); @@ -224,11 +258,11 @@ public override ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReque //schedule details.ScheduleDetails = new ScheduleDetailsType(); - details.ScheduleDetails.Description = Helper.GetResource("RecurringPayment"); + details.ScheduleDetails.Description = T("Plugins.Payments.PayPalDirect.RecurringPayment"); details.ScheduleDetails.PaymentPeriod = new BillingPeriodDetailsType(); details.ScheduleDetails.PaymentPeriod.Amount = new BasicAmountType(); details.ScheduleDetails.PaymentPeriod.Amount.Value = Math.Round(processPaymentRequest.OrderTotal, 2).ToString("N", new CultureInfo("en-us")); - details.ScheduleDetails.PaymentPeriod.Amount.currencyID = PayPalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)); + details.ScheduleDetails.PaymentPeriod.Amount.currencyID = GetApiCurrency(store.PrimaryStoreCurrency); details.ScheduleDetails.PaymentPeriod.BillingFrequency = processPaymentRequest.RecurringCycleLength; switch (processPaymentRequest.RecurringCyclePeriod) { @@ -245,19 +279,18 @@ public override ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReque details.ScheduleDetails.PaymentPeriod.BillingPeriod = BillingPeriodType.Year; break; default: - throw new SmartException(Helper.GetResource("NotSupportedPeriod")); + throw new SmartException(T("Plugins.Payments.PayPalDirect.NotSupportedPeriod")); } details.ScheduleDetails.PaymentPeriod.TotalBillingCycles = processPaymentRequest.RecurringTotalCycles; details.ScheduleDetails.PaymentPeriod.TotalBillingCyclesSpecified = true; - using (var service = new PayPalAPIAASoapBinding()) + using (var service = GetApiAaService(settings)) { - service.Url = PayPalHelper.GetPaypalServiceUrl(settings); - service.RequesterCredentials = PayPalHelper.GetPaypalApiCredentials(settings); - CreateRecurringPaymentsProfileResponseType response = service.CreateRecurringPaymentsProfile(req); + var response = service.CreateRecurringPaymentsProfile(req); + + var error = ""; + var success = IsSuccess(response, out error); - string error = ""; - bool success = PayPalHelper.CheckSuccess(Helper, response, out error); if (success) { result.NewPaymentStatus = PaymentStatus.Pending; @@ -274,51 +307,5 @@ public override ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReque return result; } - - protected override string GetControllerName() - { - return "PayPalDirect"; - } - - public override Type GetControllerType() - { - return typeof(PayPalDirectController); - } - - #endregion - - #region Properties - - public override bool RequiresInteraction - { - get - { - return true; - } - } - - /// - /// Gets a recurring payment type of payment method - /// - public override RecurringPaymentType RecurringPaymentType - { - get - { - return RecurringPaymentType.Automatic; - } - } - - /// - /// Gets a payment method type - /// - public override PaymentMethodType PaymentMethodType - { - get - { - return PaymentMethodType.Standard; - } - } - - #endregion } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs index d46643b8f7..98c4e8e223 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs @@ -5,49 +5,47 @@ using System.Web; using SmartStore.Core; using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Domain.Tax; using SmartStore.Core.Infrastructure; using SmartStore.Core.Plugins; using SmartStore.PayPal.Controllers; using SmartStore.PayPal.PayPalSvc; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; -using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Common; -using SmartStore.Services.Configuration; using SmartStore.Services.Customers; using SmartStore.Services.Directory; using SmartStore.Services.Orders; using SmartStore.Services.Payments; using SmartStore.Services.Shipping; +using SmartStore.Services.Tax; namespace SmartStore.PayPal { - [SystemName("Payments.PayPalExpress")] + [SystemName("Payments.PayPalExpress")] [FriendlyName("PayPal Express")] [DisplayOrder(0)] - public partial class PayPalExpress : PayPalProviderBase + public partial class PayPalExpressProvider : PayPalProviderBase { private readonly ICurrencyService _currencyService; - private readonly CurrencySettings _currencySettings; private readonly IPriceCalculationService _priceCalculationService; - private readonly IGenericAttributeService _genericAttributeService; + private readonly ITaxService _taxService; + private readonly IGenericAttributeService _genericAttributeService; private readonly IStateProvinceService _stateProvinceService; private readonly IGiftCardService _giftCardService; private readonly IShippingService _shippingService; private readonly ICustomerService _customerService; private readonly ICountryService _countryService; private readonly HttpContextBase _httpContext; - - public PayPalExpress( + + public PayPalExpressProvider( ICurrencyService currencyService, - CurrencySettings currencySettings, IPriceCalculationService priceCalculationService, - IGenericAttributeService genericAttributeService, + ITaxService taxService, + IGenericAttributeService genericAttributeService, IStateProvinceService stateProvinceService, IGiftCardService giftCardService, IShippingService shippingService, @@ -56,8 +54,8 @@ public PayPalExpress( HttpContextBase httpContext) { _currencyService = currencyService; - _currencySettings = currencySettings; _priceCalculationService = priceCalculationService; + _taxService = taxService; _genericAttributeService = genericAttributeService; _stateProvinceService = stateProvinceService; _giftCardService = giftCardService; @@ -67,6 +65,28 @@ public PayPalExpress( _httpContext = httpContext; } + public static string SystemName { get { return "Payments.PayPalExpress"; } } + + public override PaymentMethodType PaymentMethodType + { + get + { + return PaymentMethodType.StandardAndButton; + } + } + + private PaymentActionCodeType GetPaymentAction(PayPalExpressPaymentSettings settings) + { + if (settings.TransactMode == TransactMode.Authorize) + { + return PaymentActionCodeType.Authorization; + } + else + { + return PaymentActionCodeType.Sale; + } + } + protected override string GetResourceRootKey() { return "Plugins.Payments.PayPalExpress"; @@ -80,78 +100,39 @@ protected override string GetResourceRootKey() public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) { var result = new ProcessPaymentResult(); - var doPayment = DoExpressCheckoutPayment(processPaymentRequest); - var settings = CommonServices.Settings.LoadSetting(processPaymentRequest.StoreId); + var settings = Services.Settings.LoadSetting(processPaymentRequest.StoreId); + var doPayment = DoExpressCheckoutPayment(processPaymentRequest); + if (doPayment.Ack == AckCodeType.Success) { - if (PayPalHelper.GetPaymentAction(settings) == PaymentActionCodeType.Authorization) + if (GetPaymentAction(settings) == PaymentActionCodeType.Authorization) { - result.NewPaymentStatus = PaymentStatus.Authorized; - } - else + result.AuthorizationTransactionId = doPayment.DoExpressCheckoutPaymentResponseDetails.PaymentInfo.FirstOrDefault().TransactionID; + result.AuthorizationTransactionResult = doPayment.Ack.ToString(); + + result.NewPaymentStatus = PaymentStatus.Authorized; + } + else { - result.NewPaymentStatus = PaymentStatus.Paid; + result.CaptureTransactionId = doPayment.DoExpressCheckoutPaymentResponseDetails.PaymentInfo.FirstOrDefault().TransactionID; + result.CaptureTransactionResult = doPayment.Ack.ToString(); + + result.NewPaymentStatus = PaymentStatus.Paid; } - result.AuthorizationTransactionId = processPaymentRequest.PaypalToken; - result.CaptureTransactionId = doPayment.DoExpressCheckoutPaymentResponseDetails.PaymentInfo.FirstOrDefault().TransactionID; - result.CaptureTransactionResult = doPayment.Ack.ToString(); - } + + //result.AuthorizationTransactionId = processPaymentRequest.PaypalToken; + //result.CaptureTransactionId = doPayment.DoExpressCheckoutPaymentResponseDetails.PaymentInfo.FirstOrDefault().TransactionID; + //result.CaptureTransactionResult = doPayment.Ack.ToString(); + } else { result.NewPaymentStatus = PaymentStatus.Pending; - } - - return result; - } - - /// - /// Post process payment (used by payment gateways that require redirecting to a third-party URL) - /// - /// Payment info required for an order processing - public override void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) - { - //TODO: - //handle Giropay - //if(!String.IsNullOrEmpty(postProcessPaymentRequest.GiroPayUrl)) - // return re + result.Errors.Each(x => result.AddError(x)); + } - } - - public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest) - { - var result = new DoCaptureResponseType(); - var settings = CommonServices.Settings.LoadSetting(capturePaymentRequest.Order.StoreId); - - // build the request - var req = new DoCaptureReq - { - DoCaptureRequest = new DoCaptureRequestType() - }; - - //execute request - using (var service = new PayPalAPIAASoapBinding()) - { - service.Url = PayPalHelper.GetPaypalServiceUrl(settings); - service.RequesterCredentials = PayPalHelper.GetPaypalApiCredentials(settings); - result = service.DoCapture(req); - } - - var capturePaymentResult = new CapturePaymentResult(); - - if (result.Ack == AckCodeType.Success) - { - capturePaymentResult.CaptureTransactionId = result.DoCaptureResponseDetails.PaymentInfo.TransactionID; - capturePaymentResult.CaptureTransactionResult = "Success"; - } - else - { - capturePaymentResult.CaptureTransactionResult = "Error"; - capturePaymentResult.Errors.Add(result.Errors.FirstOrDefault().LongMessage); - } - - return capturePaymentResult; + return result; } /// @@ -176,87 +157,89 @@ public override Type GetControllerType() return typeof(PayPalExpressController); } - public override PaymentMethodType PaymentMethodType + public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentRequest processPaymentRequest, IList cart) { - get - { - return PaymentMethodType.StandardAndButton; - } - } - - public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentRequest processPaymentRequest, - IList cart) - { - var result = new SetExpressCheckoutResponseType(); - var currentStore = CommonServices.StoreContext.CurrentStore; - var settings = CommonServices.Settings.LoadSetting(processPaymentRequest.StoreId); - - var req = new SetExpressCheckoutReq + var result = new SetExpressCheckoutResponseType(); + var store = Services.StoreService.GetStoreById(processPaymentRequest.StoreId); + var customer = Services.WorkContext.CurrentCustomer; + var settings = Services.Settings.LoadSetting(processPaymentRequest.StoreId); + var payPalCurrency = GetApiCurrency(store.PrimaryStoreCurrency); + var excludingTax = (Services.WorkContext.GetTaxDisplayTypeFor(customer, store.Id) == TaxDisplayType.ExcludingTax); + + var req = new SetExpressCheckoutReq { SetExpressCheckoutRequest = new SetExpressCheckoutRequestType { - Version = PayPalHelper.GetApiVersion(), + Version = ApiVersion, SetExpressCheckoutRequestDetails = new SetExpressCheckoutRequestDetailsType() } }; var details = new SetExpressCheckoutRequestDetailsType { - PaymentAction = PayPalHelper.GetPaymentAction(settings), + PaymentAction = GetPaymentAction(settings), PaymentActionSpecified = true, - CancelURL = CommonServices.WebHelper.GetStoreLocation(currentStore.SslEnabled) + "cart", - ReturnURL = CommonServices.WebHelper.GetStoreLocation(currentStore.SslEnabled) + "Plugins/SmartStore.PayPal/PayPalExpress/GetDetails", + CancelURL = Services.WebHelper.GetStoreLocation(store.SslEnabled) + "cart", + ReturnURL = Services.WebHelper.GetStoreLocation(store.SslEnabled) + "Plugins/SmartStore.PayPal/PayPalExpress/GetDetails", //CallbackURL = _webHelper.GetStoreLocation(currentStore.SslEnabled) + "Plugins/SmartStore.PayPal/PayPalExpress/ShippingOptions?CustomerID=" + _workContext.CurrentCustomer.Id.ToString(), //CallbackTimeout = _payPalExpressPaymentSettings.CallbackTimeout.ToString() ReqConfirmShipping = settings.ConfirmedShipment.ToString(), NoShipping = settings.NoShipmentAddress.ToString() }; - // populate cart - decimal itemTotal = decimal.Zero; + // populate cart + var taxRate = decimal.Zero; + var unitPriceTaxRate = decimal.Zero; + var itemTotal = decimal.Zero; var cartItems = new List(); - foreach (OrganizedShoppingCartItem item in cart) + + foreach (var item in cart) { - decimal shoppingCartUnitPriceWithDiscountBase = _priceCalculationService.GetUnitPrice(item, true); - decimal shoppingCartUnitPriceWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartUnitPriceWithDiscountBase, CommonServices.WorkContext.WorkingCurrency); - decimal priceIncludingTier = shoppingCartUnitPriceWithDiscount; + var product = item.Item.Product; + var unitPrice = _priceCalculationService.GetUnitPrice(item, true); + var shoppingCartUnitPriceWithDiscount = excludingTax + ? _taxService.GetProductPrice(product, unitPrice, false, customer, out taxRate) + : _taxService.GetProductPrice(product, unitPrice, true, customer, out unitPriceTaxRate); - cartItems.Add(new PaymentDetailsItemType() + cartItems.Add(new PaymentDetailsItemType { - Name = item.Item.Product.Name, - Number = item.Item.Product.Sku, + Name = product.Name, + Number = product.Sku, Quantity = item.Item.Quantity.ToString(), - Amount = new BasicAmountType() // this is the per item cost + // this is the per item cost + Amount = new BasicAmountType { - currencyID = PayPalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)), - Value = (priceIncludingTier).ToString("N", new CultureInfo("en-us")) + currencyID = payPalCurrency, + Value = shoppingCartUnitPriceWithDiscount.FormatInvariant() } }); - itemTotal += (item.Item.Quantity * priceIncludingTier); + + itemTotal += (item.Item.Quantity * shoppingCartUnitPriceWithDiscount); }; // additional handling fee var additionalHandlingFee = GetAdditionalHandlingFee(cart); - cartItems.Add(new PaymentDetailsItemType() + cartItems.Add(new PaymentDetailsItemType { - Name = "Zahlartgebhren", + Name = T("Plugins.Payments.PayPal.PaymentMethodFee").Text, Quantity = "1", Amount = new BasicAmountType() { - currencyID = PayPalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)), - Value = (additionalHandlingFee).ToString("N", new CultureInfo("en-us")) + currencyID = payPalCurrency, + Value = additionalHandlingFee.FormatInvariant() } }); + itemTotal += GetAdditionalHandlingFee(cart); //shipping - decimal shippingTotal = decimal.Zero; + var shippingTotal = decimal.Zero; if (cart.RequiresShipping()) { decimal? shoppingCartShippingBase = OrderTotalCalculationService.GetShoppingCartShippingTotal(cart); if (shoppingCartShippingBase.HasValue && shoppingCartShippingBase > 0) { - shippingTotal = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartShippingBase.Value, CommonServices.WorkContext.WorkingCurrency); + shippingTotal = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartShippingBase.Value, Services.WorkContext.WorkingCurrency); } else { @@ -285,28 +268,23 @@ public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentReq //decimal shoppingCartTax = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartTaxBase, CommonServices.WorkContext.WorkingCurrency); // discount - decimal discount = -processPaymentRequest.Discount; - + var discount = -processPaymentRequest.Discount; if (discount != 0) { - cartItems.Add(new PaymentDetailsItemType() + cartItems.Add(new PaymentDetailsItemType { - Name = "Threadrock Discount", + Name = T("Plugins.Payments.PayPal.ThreadrockDiscount").Text, Quantity = "1", - Amount = new BasicAmountType() // this is the total discount + Amount = new BasicAmountType // this is the total discount { - currencyID = PayPalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)), - Value = discount.ToString("N", new CultureInfo("en-us")) + currencyID = payPalCurrency, + Value = discount.FormatInvariant() } }); itemTotal += discount; } - // get customer - int customerId = Convert.ToInt32(CommonServices.WorkContext.CurrentCustomer.Id.ToString()); - var customer = _customerService.GetCustomerById(customerId); - if (!cart.IsRecurring()) { //we don't apply gift cards for recurring products @@ -326,14 +304,14 @@ public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentReq decimal amountToSubtract = -amountCanBeUsed; - cartItems.Add(new PaymentDetailsItemType() + cartItems.Add(new PaymentDetailsItemType { - Name = "Giftcard Applied", + Name = T("Plugins.Payments.PayPal.GiftcardApplied").Text, Quantity = "1", - Amount = new BasicAmountType() + Amount = new BasicAmountType { - currencyID = PayPalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)), - Value = amountToSubtract.ToString("N", new CultureInfo("en-us")) + currencyID = payPalCurrency, + Value = amountToSubtract.FormatInvariant() } }); @@ -349,13 +327,13 @@ public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentReq { ItemTotal = new BasicAmountType { - Value = Math.Round(itemTotal, 2).ToString("N", new CultureInfo("en-us")), - currencyID = PayPalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)) + Value = Math.Round(itemTotal, 2).FormatInvariant(), + currencyID = payPalCurrency }, ShippingTotal = new BasicAmountType { - Value = Math.Round(shippingTotal, 2).ToString("N", new CultureInfo("en-us")), - currencyID = PayPalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)) + Value = Math.Round(shippingTotal, 2).FormatInvariant(), + currencyID = payPalCurrency }, //TaxTotal = new BasicAmountType //{ @@ -364,12 +342,12 @@ public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentReq //}, OrderTotal = new BasicAmountType { - Value = Math.Round(itemTotal + shippingTotal, 2).ToString("N", new CultureInfo("en-us")), - currencyID = PayPalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)) + Value = Math.Round(itemTotal + shippingTotal, 2).FormatInvariant(), + currencyID = payPalCurrency }, Custom = processPaymentRequest.OrderGuid.ToString(), ButtonSource = SmartStoreVersion.CurrentFullVersion, - PaymentAction = PayPalHelper.GetPaymentAction(settings), + PaymentAction = GetPaymentAction(settings), PaymentDetailsItem = cartItems.ToArray() }; details.PaymentDetails = new[] { paymentDetails }; @@ -379,14 +357,11 @@ public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentReq req.SetExpressCheckoutRequest.SetExpressCheckoutRequestDetails.Custom = processPaymentRequest.OrderGuid.ToString(); req.SetExpressCheckoutRequest.SetExpressCheckoutRequestDetails = details; - using (var service = new PayPalAPIAASoapBinding()) + using (var service = GetApiAaService(settings)) { - service.Url = PayPalHelper.GetPaypalServiceUrl(settings); - service.RequesterCredentials = PayPalHelper.GetPaypalApiCredentials(settings); result = service.SetExpressCheckout(req); } - _httpContext.GetCheckoutState().CustomProperties.Add("PayPalExpressButtonUsed", true); return result; } @@ -394,19 +369,17 @@ public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentReq public GetExpressCheckoutDetailsResponseType GetExpressCheckoutDetails(string token) { var result = new GetExpressCheckoutDetailsResponseType(); - var settings = CommonServices.Settings.LoadSetting(CommonServices.StoreContext.CurrentStore.Id); + var settings = Services.Settings.LoadSetting(Services.StoreContext.CurrentStore.Id); - using (var service = new PayPalAPIAASoapBinding()) + using (var service = GetApiAaService(settings)) { var req = new GetExpressCheckoutDetailsReq(); req.GetExpressCheckoutDetailsRequest = new GetExpressCheckoutDetailsRequestType { Token = token, - Version = PayPalHelper.GetApiVersion() + Version = ApiVersion }; - service.Url = PayPalHelper.GetPaypalServiceUrl(settings); - service.RequesterCredentials = PayPalHelper.GetPaypalApiCredentials(settings); result = service.GetExpressCheckoutDetails(req); } return result; @@ -414,14 +387,14 @@ public GetExpressCheckoutDetailsResponseType GetExpressCheckoutDetails(string to public ProcessPaymentRequest SetCheckoutDetails(ProcessPaymentRequest processPaymentRequest, GetExpressCheckoutDetailsResponseDetailsType checkoutDetails) { - int customerId = Convert.ToInt32(CommonServices.WorkContext.CurrentCustomer.Id.ToString()); + int customerId = Convert.ToInt32(Services.WorkContext.CurrentCustomer.Id.ToString()); var customer = _customerService.GetCustomerById(customerId); - var settings = CommonServices.Settings.LoadSetting(CommonServices.StoreContext.CurrentStore.Id); + var settings = Services.Settings.LoadSetting(Services.StoreContext.CurrentStore.Id); - CommonServices.WorkContext.CurrentCustomer = customer; + Services.WorkContext.CurrentCustomer = customer; //var cart = customer.ShoppingCartItems.Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart).ToList(); - var cart = CommonServices.WorkContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, CommonServices.StoreContext.CurrentStore.Id); + var cart = Services.WorkContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, Services.StoreContext.CurrentStore.Id); // get/update billing address string billingFirstName = checkoutDetails.PayerInfo.PayerName.FirstName; @@ -441,7 +414,7 @@ public ProcessPaymentRequest SetCheckoutDetails(ProcessPaymentRequest processPay if (billingCountry != null) billingCountryId = billingCountry.Id; - var billingAddress = customer.Addresses.ToList().FindAddress( + var billingAddress = customer.Addresses.FindAddress( billingFirstName, billingLastName, billingPhoneNumber, billingEmail, string.Empty, string.Empty, billingAddress1, billingAddress2, billingCity, billingStateProvinceId, billingZipPostalCode, billingCountryId); @@ -498,7 +471,7 @@ public ProcessPaymentRequest SetCheckoutDetails(ProcessPaymentRequest processPay if (shippingCountry != null) shippingCountryId = shippingCountry.Id; - var shippingAddress = customer.Addresses.ToList().FindAddress( + var shippingAddress = customer.Addresses.FindAddress( shippingFirstName, shippingLastName, shippingPhoneNumber, shippingEmail, string.Empty, string.Empty, shippingAddress1, shippingAddress2, shippingCity, @@ -541,7 +514,7 @@ public ProcessPaymentRequest SetCheckoutDetails(ProcessPaymentRequest processPay if (checkoutDetails.UserSelectedOptions.ShippingOptionName.Contains(shippingOption.Name) && checkoutDetails.UserSelectedOptions.ShippingOptionName.Contains(shippingOption.Description)) { - _genericAttributeService.SaveAttribute(CommonServices.WorkContext.CurrentCustomer, SystemCustomerAttributeNames.SelectedShippingOption, shippingOption); + _genericAttributeService.SaveAttribute(Services.WorkContext.CurrentCustomer, SystemCustomerAttributeNames.SelectedShippingOption, shippingOption); isShippingSet = true; break; } @@ -569,7 +542,8 @@ public ProcessPaymentRequest SetCheckoutDetails(ProcessPaymentRequest processPay public DoExpressCheckoutPaymentResponseType DoExpressCheckoutPayment(ProcessPaymentRequest processPaymentRequest) { var result = new DoExpressCheckoutPaymentResponseType(); - var settings = CommonServices.Settings.LoadSetting(processPaymentRequest.StoreId); + var store = Services.StoreService.GetStoreById(processPaymentRequest.StoreId); + var settings = Services.Settings.LoadSetting(processPaymentRequest.StoreId); // populate payment details var paymentDetails = new PaymentDetailsType @@ -577,7 +551,7 @@ public DoExpressCheckoutPaymentResponseType DoExpressCheckoutPayment(ProcessPaym OrderTotal = new BasicAmountType { Value = Math.Round(processPaymentRequest.OrderTotal, 2).ToString("N", new CultureInfo("en-us")), - currencyID = PayPalHelper.GetPaypalCurrency(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId)) + currencyID = GetApiCurrency(store.PrimaryStoreCurrency) }, Custom = processPaymentRequest.OrderGuid.ToString(), ButtonSource = SmartStoreVersion.CurrentFullVersion @@ -588,12 +562,12 @@ public DoExpressCheckoutPaymentResponseType DoExpressCheckoutPayment(ProcessPaym { DoExpressCheckoutPaymentRequest = new DoExpressCheckoutPaymentRequestType { - Version = PayPalHelper.GetApiVersion(), + Version = ApiVersion, DoExpressCheckoutPaymentRequestDetails = new DoExpressCheckoutPaymentRequestDetailsType { Token = processPaymentRequest.PaypalToken, PayerID = processPaymentRequest.PaypalPayerId, - PaymentAction = PayPalHelper.GetPaymentAction(settings), + PaymentAction = GetPaymentAction(settings), PaymentActionSpecified = true, PaymentDetails = new PaymentDetailsType[] { @@ -604,14 +578,20 @@ public DoExpressCheckoutPaymentResponseType DoExpressCheckoutPayment(ProcessPaym }; //execute request - using (var service = new PayPalAPIAASoapBinding()) + using (var service = GetApiAaService(settings)) { - service.Url = PayPalHelper.GetPaypalServiceUrl(settings); - service.RequesterCredentials = PayPalHelper.GetPaypalApiCredentials(settings); result = service.DoExpressCheckoutPayment(req); } return result; } - } + + + public class PayPalProcessPaymentRequest : ProcessPaymentRequest + { + /// + /// Gets or sets an order Discount Amount + /// + public decimal Discount { get; set; } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs new file mode 100644 index 0000000000..45e251d2d0 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs @@ -0,0 +1,32 @@ +using System; +using SmartStore.Core.Plugins; +using SmartStore.PayPal.Controllers; +using SmartStore.PayPal.Settings; +using SmartStore.Services.Payments; + +namespace SmartStore.PayPal +{ + [SystemName("Payments.PayPalPlus")] + [FriendlyName("PayPal PLUS")] + [DisplayOrder(1)] + public partial class PayPalPlusProvider : PayPalRestApiProviderBase + { + public static string SystemName + { + get { return "Payments.PayPalPlus"; } + } + + public override PaymentMethodType PaymentMethodType + { + get + { + return PaymentMethodType.StandardAndRedirection; + } + } + + public override Type GetControllerType() + { + return typeof(PayPalPlusController); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs index 687d179faf..1c65bd39dd 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs @@ -1,113 +1,169 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Net; using System.Text; -using System.Web; using System.Web.Routing; -using Autofac; using SmartStore.Core.Configuration; +using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.PayPal.PayPalSvc; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services; using SmartStore.Services.Orders; using SmartStore.Services.Payments; -using SmartStore.Web.Framework.Plugins; namespace SmartStore.PayPal { - public abstract class PayPalProviderBase : PaymentMethodBase, IConfigurable where TSetting : PayPalApiSettingsBase, ISettings, new() + public abstract class PayPalProviderBase : PaymentMethodBase, IConfigurable where TSetting : PayPalApiSettingsBase, ISettings, new() { protected PayPalProviderBase() { Logger = NullLogger.Instance; } + public static string ApiVersion + { + get { return "109"; } + } + public ILogger Logger { get; set; } + public ICommonServices Services { get; set; } + public IOrderService OrderService { get; set; } + public IOrderTotalCalculationService OrderTotalCalculationService { get; set; } - public ICommonServices CommonServices { get; set; } + public override bool SupportCapture + { + get { return true; } + } - public IOrderService OrderService { get; set; } + public override bool SupportPartiallyRefund + { + get { return true; } + } - public IOrderTotalCalculationService OrderTotalCalculationService { get; set; } + public override bool SupportRefund + { + get { return true; } + } - public IComponentContext ComponentContext { get; set; } + public override bool SupportVoid + { + get { return true; } + } protected abstract string GetResourceRootKey(); - private PluginHelper _helper; - public PluginHelper Helper + protected PayPalAPIAASoapBinding GetApiAaService(TSetting settings) { - get + if (settings.SecurityProtocol.HasValue) { - if (_helper == null) - { - _helper = new PluginHelper(this.ComponentContext, "SmartStore.PayPal", GetResourceRootKey()); - } - return _helper; + ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; } + + var service = new PayPalAPIAASoapBinding(); + + service.Url = settings.UseSandbox ? "https://api-3t.sandbox.paypal.com/2.0/" : "https://api-3t.paypal.com/2.0/"; + + service.RequesterCredentials = GetApiCredentials(settings); + + return service; } - /// - /// Verifies IPN - /// - /// Form string - /// Values - /// Result - public bool VerifyIPN(string formString, out Dictionary values) - { - // settings: multistore context not possible here. we need the custom value to determine what store it is. - var settings = CommonServices.Settings.LoadSetting(); - var req = (HttpWebRequest)WebRequest.Create(PayPalHelper.GetPaypalUrl(settings)); + protected PayPalAPISoapBinding GetApiService(TSetting settings) + { + if (settings.SecurityProtocol.HasValue) + { + ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; + } - req.Method = "POST"; - req.ContentType = "application/x-www-form-urlencoded"; - req.UserAgent = HttpContext.Current.Request.UserAgent; + var service = new PayPalAPISoapBinding(); - string formContent = string.Format("{0}&cmd=_notify-validate", formString); - req.ContentLength = formContent.Length; + service.Url = settings.UseSandbox ? "https://api-3t.sandbox.paypal.com/2.0/" : "https://api-3t.paypal.com/2.0/"; - using (var sw = new StreamWriter(req.GetRequestStream(), Encoding.ASCII)) - { - sw.Write(formContent); - } + service.RequesterCredentials = GetApiCredentials(settings); - string response = null; - using (var sr = new StreamReader(req.GetResponse().GetResponseStream())) - { - response = HttpUtility.UrlDecode(sr.ReadToEnd()); - } - bool success = response.Trim().Equals("VERIFIED", StringComparison.OrdinalIgnoreCase); + return service; + } - values = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (string l in formString.Split('&')) - { - string line = HttpUtility.UrlDecode(l).Trim(); - int equalPox = line.IndexOf('='); - if (equalPox >= 0) - values.Add(line.Substring(0, equalPox), line.Substring(equalPox + 1)); - } + protected CustomSecurityHeaderType GetApiCredentials(PayPalApiSettingsBase settings) + { + var customSecurityHeaderType = new CustomSecurityHeaderType(); - return success; - } + customSecurityHeaderType.Credentials = new UserIdPasswordType(); + customSecurityHeaderType.Credentials.Username = settings.ApiAccountName; + customSecurityHeaderType.Credentials.Password = settings.ApiAccountPassword; + customSecurityHeaderType.Credentials.Signature = settings.Signature; + customSecurityHeaderType.Credentials.Subject = ""; - /// - /// Gets additional handling fee - /// - /// Shoping cart - /// Additional handling fee - public override decimal GetAdditionalHandlingFee(IList cart) + return customSecurityHeaderType; + } + + protected CurrencyCodeType GetApiCurrency(Currency currency) + { + var currencyCodeType = CurrencyCodeType.USD; + try + { + currencyCodeType = (CurrencyCodeType)Enum.Parse(typeof(CurrencyCodeType), currency.CurrencyCode, true); + } + catch { } + + return currencyCodeType; + } + + protected bool IsSuccess(AbstractResponseType abstractResponse, out string errorMsg) + { + var success = false; + var sb = new StringBuilder(); + + switch (abstractResponse.Ack) + { + case AckCodeType.Success: + case AckCodeType.SuccessWithWarning: + success = true; + break; + default: + break; + } + + if (null != abstractResponse.Errors) + { + foreach (ErrorType errorType in abstractResponse.Errors) + { + if (errorType.ShortMessage.IsEmpty()) + continue; + + if (sb.Length > 0) + sb.Append(Environment.NewLine); + + sb.Append("{0}: {1}".FormatInvariant(Services.Localization.GetResource("Admin.System.Log.Fields.ShortMessage"), errorType.ShortMessage)); + sb.AppendLine(" ({0}).".FormatInvariant(errorType.ErrorCode)); + + if (errorType.LongMessage.HasValue() && errorType.LongMessage != errorType.ShortMessage) + sb.AppendLine("{0}: {1}".FormatInvariant(Services.Localization.GetResource("Admin.System.Log.Fields.FullMessage"), errorType.LongMessage)); + } + } + + errorMsg = sb.ToString(); + return success; + } + + protected abstract string GetControllerName(); + + /// + /// Gets additional handling fee + /// + /// Shoping cart + /// Additional handling fee + public override decimal GetAdditionalHandlingFee(IList cart) { var result = decimal.Zero; try { - var settings = CommonServices.Settings.LoadSetting(); + var settings = Services.Settings.LoadSetting(); result = this.CalculateAdditionalFee(OrderTotalCalculationService, cart, settings.AdditionalFee, settings.AdditionalFeePercentage); } @@ -124,31 +180,32 @@ public override decimal GetAdditionalHandlingFee(IListCapture payment result public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest) { - var result = new CapturePaymentResult() + var result = new CapturePaymentResult { NewPaymentStatus = capturePaymentRequest.Order.PaymentStatus }; - var settings = CommonServices.Settings.LoadSetting(capturePaymentRequest.Order.StoreId); - string authorizationId = capturePaymentRequest.Order.AuthorizationTransactionId; + var settings = Services.Settings.LoadSetting(capturePaymentRequest.Order.StoreId); + var currencyCode = Services.WorkContext.WorkingCurrency.CurrencyCode ?? "EUR"; + + var authorizationId = capturePaymentRequest.Order.AuthorizationTransactionId; var req = new DoCaptureReq(); req.DoCaptureRequest = new DoCaptureRequestType(); - req.DoCaptureRequest.Version = PayPalHelper.GetApiVersion(); + req.DoCaptureRequest.Version = ApiVersion; req.DoCaptureRequest.AuthorizationID = authorizationId; req.DoCaptureRequest.Amount = new BasicAmountType(); req.DoCaptureRequest.Amount.Value = Math.Round(capturePaymentRequest.Order.OrderTotal, 2).ToString("N", new CultureInfo("en-us")); - req.DoCaptureRequest.Amount.currencyID = (CurrencyCodeType)Enum.Parse(typeof(CurrencyCodeType), Helper.CurrencyCode, true); + req.DoCaptureRequest.Amount.currencyID = (CurrencyCodeType)Enum.Parse(typeof(CurrencyCodeType), currencyCode, true); req.DoCaptureRequest.CompleteType = CompleteCodeType.Complete; - using (var service = new PayPalAPIAASoapBinding()) + using (var service = GetApiAaService(settings)) { - service.Url = PayPalHelper.GetPaypalServiceUrl(settings); - service.RequesterCredentials = PayPalHelper.GetPaypalApiCredentials(settings); - DoCaptureResponseType response = service.DoCapture(req); + var response = service.DoCapture(req); + + var error = ""; + var success = IsSuccess(response, out error); - string error = ""; - bool success = PayPalHelper.CheckSuccess(_helper, response, out error); if (success) { result.NewPaymentStatus = PaymentStatus.Paid; @@ -163,40 +220,64 @@ public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymen return result; } - /// - /// Handles refund - /// - /// RefundPaymentRequest - /// RefundPaymentResult public override RefundPaymentResult Refund(RefundPaymentRequest request) { - var result = new RefundPaymentResult() + // "Transaction refused (10009). You can not refund this type of transaction.": + // merchant must accept the payment in his PayPal account + var result = new RefundPaymentResult { NewPaymentStatus = request.Order.PaymentStatus }; - var settings = CommonServices.Settings.LoadSetting(request.Order.StoreId); - string transactionId = request.Order.CaptureTransactionId; + var settings = Services.Settings.LoadSetting(request.Order.StoreId); - var req = new RefundTransactionReq(); + var transactionId = request.Order.CaptureTransactionId; + + var req = new RefundTransactionReq(); req.RefundTransactionRequest = new RefundTransactionRequestType(); - //NOTE: Specify amount in partial refund - req.RefundTransactionRequest.RefundType = RefundType.Full; + + if (request.IsPartialRefund) + { + var store = Services.StoreService.GetStoreById(request.Order.StoreId); + var currencyCode = store.PrimaryStoreCurrency.CurrencyCode; + + req.RefundTransactionRequest.RefundType = RefundType.Partial; + + req.RefundTransactionRequest.Amount = new BasicAmountType(); + req.RefundTransactionRequest.Amount.Value = Math.Round(request.AmountToRefund, 2).ToString("N", new CultureInfo("en-us")); + req.RefundTransactionRequest.Amount.currencyID = (CurrencyCodeType)Enum.Parse(typeof(CurrencyCodeType), currencyCode, true); + + // see https://developer.paypal.com/docs/classic/express-checkout/digital-goods/ECDGIssuingRefunds/ + // https://developer.paypal.com/docs/classic/api/merchant/RefundTransaction_API_Operation_NVP/ + var memo = Services.Localization.GetResource("Plugins.SmartStore.PayPal.PartialRefundMemo", 0, false, "", true); + if (memo.HasValue()) + { + req.RefundTransactionRequest.Memo = memo.FormatInvariant(req.RefundTransactionRequest.Amount.Value); + } + } + else + { + req.RefundTransactionRequest.RefundType = RefundType.Full; + } + req.RefundTransactionRequest.RefundTypeSpecified = true; - req.RefundTransactionRequest.Version = PayPalHelper.GetApiVersion(); + req.RefundTransactionRequest.Version = ApiVersion; req.RefundTransactionRequest.TransactionID = transactionId; - using (var service = new PayPalAPISoapBinding()) + using (var service = GetApiService(settings)) { - service.Url = PayPalHelper.GetPaypalServiceUrl(settings); - service.RequesterCredentials = PayPalHelper.GetPaypalApiCredentials(settings); - RefundTransactionResponseType response = service.RefundTransaction(req); + var response = service.RefundTransaction(req); + + var error = ""; + var Success = IsSuccess(response, out error); - string error = string.Empty; - bool Success = PayPalHelper.CheckSuccess(_helper, response, out error); if (Success) { - result.NewPaymentStatus = PaymentStatus.Refunded; + if (request.IsPartialRefund) + result.NewPaymentStatus = PaymentStatus.PartiallyRefunded; + else + result.NewPaymentStatus = PaymentStatus.Refunded; + //cancelPaymentResult.RefundTransactionID = response.RefundTransactionID; } else @@ -215,31 +296,30 @@ public override RefundPaymentResult Refund(RefundPaymentRequest request) /// Result public override VoidPaymentResult Void(VoidPaymentRequest request) { - var result = new VoidPaymentResult() + var result = new VoidPaymentResult { NewPaymentStatus = request.Order.PaymentStatus }; - string transactionId = request.Order.AuthorizationTransactionId; - var settings = CommonServices.Settings.LoadSetting(request.Order.StoreId); + var settings = Services.Settings.LoadSetting(request.Order.StoreId); + + var transactionId = request.Order.AuthorizationTransactionId; - if (String.IsNullOrEmpty(transactionId)) + if (transactionId.IsEmpty()) transactionId = request.Order.CaptureTransactionId; var req = new DoVoidReq(); req.DoVoidRequest = new DoVoidRequestType(); - req.DoVoidRequest.Version = PayPalHelper.GetApiVersion(); + req.DoVoidRequest.Version = ApiVersion; req.DoVoidRequest.AuthorizationID = transactionId; - - using (var service = new PayPalAPIAASoapBinding()) + using (var service = GetApiAaService(settings)) { - service.Url = PayPalHelper.GetPaypalServiceUrl(settings); - service.RequesterCredentials = PayPalHelper.GetPaypalApiCredentials(settings); - DoVoidResponseType response = service.DoVoid(req); + var response = service.DoVoid(req); + + var error = ""; + var success = IsSuccess(response, out error); - string error = ""; - bool success = PayPalHelper.CheckSuccess(_helper, response, out error); if (success) { result.NewPaymentStatus = PaymentStatus.Voided; @@ -262,11 +342,11 @@ public override CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurr { var result = new CancelRecurringPaymentResult(); var order = request.Order; - var settings = CommonServices.Settings.LoadSetting(order.StoreId); + var settings = Services.Settings.LoadSetting(order.StoreId); var req = new ManageRecurringPaymentsProfileStatusReq(); req.ManageRecurringPaymentsProfileStatusRequest = new ManageRecurringPaymentsProfileStatusRequestType(); - req.ManageRecurringPaymentsProfileStatusRequest.Version = PayPalHelper.GetApiVersion(); + req.ManageRecurringPaymentsProfileStatusRequest.Version = ApiVersion; var details = new ManageRecurringPaymentsProfileStatusRequestDetailsType(); req.ManageRecurringPaymentsProfileStatusRequest.ManageRecurringPaymentsProfileStatusRequestDetails = details; @@ -274,14 +354,12 @@ public override CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurr //Recurring payments profile ID returned in the CreateRecurringPaymentsProfile response details.ProfileID = order.SubscriptionTransactionId; - using (var service = new PayPalAPIAASoapBinding()) + using (var service = GetApiAaService(settings)) { - service.Url = PayPalHelper.GetPaypalServiceUrl(settings); - service.RequesterCredentials = PayPalHelper.GetPaypalApiCredentials(settings); var response = service.ManageRecurringPaymentsProfileStatus(req); string error = ""; - if (!PayPalHelper.CheckSuccess(_helper, response, out error)) + if (!IsSuccess(response, out error)) { result.AddError(error); } @@ -315,28 +393,6 @@ public override void GetPaymentInfoRoute(out string actionName, out string contr controllerName = GetControllerName(); routeValues = new RouteValueDictionary() { { "area", "SmartStore.PayPal" } }; } - - protected abstract string GetControllerName(); - - public override bool SupportCapture - { - get { return true; } - } - - public override bool SupportPartiallyRefund - { - get { return false; } - } - - public override bool SupportRefund - { - get { return true; } - } - - public override bool SupportVoid - { - get { return true; } - } } } diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs new file mode 100644 index 0000000000..2330c9d336 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.Routing; +using SmartStore.Core.Configuration; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; +using SmartStore.PayPal.Services; +using SmartStore.PayPal.Settings; +using SmartStore.Services; +using SmartStore.Services.Orders; +using SmartStore.Services.Payments; + +namespace SmartStore.PayPal +{ + public abstract class PayPalRestApiProviderBase : PaymentMethodBase, IConfigurable where TSetting : PayPalApiSettingsBase, ISettings, new() + { + protected PayPalRestApiProviderBase() + { + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + public HttpContextBase HttpContext { get; set; } + public ICommonServices Services { get; set; } + public IOrderService OrderService { get; set; } + public IOrderTotalCalculationService OrderTotalCalculationService { get; set; } + public IPayPalService PayPalService { get; set; } + + protected string GetControllerName() + { + return GetControllerType().Name.EmptyNull().Replace("Controller", ""); + } + + public static string CheckoutCompletedKey + { + get { return "PayPalCheckoutCompleted"; } + } + + public override bool SupportCapture + { + get { return true; } + } + + public override bool SupportPartiallyRefund + { + get { return true; } + } + + public override bool SupportRefund + { + get { return true; } + } + + public override bool SupportVoid + { + get { return true; } + } + + public override decimal GetAdditionalHandlingFee(IList cart) + { + var result = decimal.Zero; + try + { + var settings = Services.Settings.LoadSetting(); + + result = this.CalculateAdditionalFee(OrderTotalCalculationService, cart, settings.AdditionalFee, settings.AdditionalFeePercentage); + } + catch (Exception) + { + } + return result; + } + + public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) + { + var result = new ProcessPaymentResult + { + NewPaymentStatus = PaymentStatus.Pending + }; + + HttpContext.Session.SafeRemove(CheckoutCompletedKey); + + var settings = Services.Settings.LoadSetting(processPaymentRequest.StoreId); + var session = HttpContext.GetPayPalSessionData(); + + processPaymentRequest.OrderGuid = session.OrderGuid; + + var apiResult = PayPalService.ExecutePayment(settings, session); + + if (apiResult.Success && apiResult.Json != null) + { + var state = (string)apiResult.Json.state; + string reasonCode = null; + dynamic relatedObject = null; + + if (!state.IsCaseInsensitiveEqual("failed")) + { + // the payment id is required to find the order during webhook message processing + result.AuthorizationTransactionCode = apiResult.Id; + + // intent: "sale" for immediate payment, "authorize" for pre-authorized payments and "order" for an order. + // info required cause API has different endpoints for different intents. + var intent = (string)apiResult.Json.intent; + + if (intent.IsCaseInsensitiveEqual("sale")) + { + relatedObject = apiResult.Json.transactions[0].related_resources[0].sale; + + session.PaymentInstruction = PayPalService.ParsePaymentInstruction(apiResult.Json.payment_instruction) as PayPalPaymentInstruction; + } + else + { + relatedObject = apiResult.Json.transactions[0].related_resources[0].authorization; + } + + if (relatedObject != null) + { + state = (string)relatedObject.state; + reasonCode = (string)relatedObject.reason_code; + + // see PayPalService.Refund() + result.AuthorizationTransactionResult = "{0} ({1})".FormatInvariant(state.NaIfEmpty(), intent.NaIfEmpty()); + result.AuthorizationTransactionId = (string)relatedObject.id; + + result.NewPaymentStatus = PayPalService.GetPaymentStatus(state, reasonCode, PaymentStatus.Authorized); + + if (result.NewPaymentStatus == PaymentStatus.Paid) + { + result.CaptureTransactionResult = result.AuthorizationTransactionResult; + result.CaptureTransactionId = result.AuthorizationTransactionId; + } + } + } + else + { + var failureReason = (string)apiResult.Json.failure_reason; + + result.Errors.Add(T("Plugins.SmartStore.PayPal.PaymentExecuteFailed").Text.Grow(failureReason, " ")); + } + } + + if (!apiResult.Success) + result.Errors.Add(apiResult.ErrorMessage); + + return result; + } + + public override void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) + { + var instruction = PayPalService.CreatePaymentInstruction(HttpContext.GetPayPalSessionData().PaymentInstruction); + + if (instruction.HasValue()) + { + HttpContext.Session[CheckoutCompletedKey] = instruction; + + OrderService.AddOrderNote(postProcessPaymentRequest.Order, instruction, true); + } + } + + public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest) + { + var result = new CapturePaymentResult + { + NewPaymentStatus = capturePaymentRequest.Order.PaymentStatus + }; + + var settings = Services.Settings.LoadSetting(capturePaymentRequest.Order.StoreId); + var session = new PayPalSessionData(); + + var apiResult = PayPalService.EnsureAccessToken(session, settings); + if (apiResult.Success) + { + apiResult = PayPalService.Capture(settings, session, capturePaymentRequest); + + if (apiResult.Success) + result.NewPaymentStatus = PaymentStatus.Paid; + } + + if (!apiResult.Success) + result.Errors.Add(apiResult.ErrorMessage); + + return result; + } + + public override RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequest) + { + var result = new RefundPaymentResult + { + NewPaymentStatus = refundPaymentRequest.Order.PaymentStatus + }; + + var settings = Services.Settings.LoadSetting(refundPaymentRequest.Order.StoreId); + var session = new PayPalSessionData(); + + var apiResult = PayPalService.EnsureAccessToken(session, settings); + if (apiResult.Success) + { + apiResult = PayPalService.Refund(settings, session, refundPaymentRequest); + if (apiResult.Success) + { + if (refundPaymentRequest.IsPartialRefund) + result.NewPaymentStatus = PaymentStatus.PartiallyRefunded; + else + result.NewPaymentStatus = PaymentStatus.Refunded; + } + } + + if (!apiResult.Success) + result.Errors.Add(apiResult.ErrorMessage); + + return result; + } + + public override VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest) + { + var result = new VoidPaymentResult + { + NewPaymentStatus = voidPaymentRequest.Order.PaymentStatus + }; + + var settings = Services.Settings.LoadSetting(voidPaymentRequest.Order.StoreId); + var session = new PayPalSessionData(); + + var apiResult = PayPalService.EnsureAccessToken(session, settings); + if (apiResult.Success) + { + apiResult = PayPalService.Void(settings, session, voidPaymentRequest); + if (apiResult.Success) + { + result.NewPaymentStatus = PaymentStatus.Voided; + } + } + + if (!apiResult.Success) + result.Errors.Add(apiResult.ErrorMessage); + + return result; + } + + public override void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + { + actionName = "Configure"; + controllerName = GetControllerName(); + routeValues = new RouteValueDictionary { { "area", "SmartStore.PayPal" } }; + } + + public override void GetPaymentInfoRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + { + actionName = "PaymentInfo"; + controllerName = GetControllerName(); + routeValues = new RouteValueDictionary { { "area", "SmartStore.PayPal" } }; + } + } +} + diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs index f2e2331ce6..4632649153 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs @@ -4,58 +4,53 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Net; using System.Text; using System.Web; using System.Web.Routing; using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.PayPal.Controllers; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services; -using SmartStore.Services.Directory; using SmartStore.Services.Localization; using SmartStore.Services.Orders; using SmartStore.Services.Payments; namespace SmartStore.PayPal { - /// - /// PayPalStandard provider - /// - [SystemName("Payments.PayPalStandard")] + [SystemName("Payments.PayPalStandard")] [FriendlyName("PayPal Standard")] [DisplayOrder(2)] public partial class PayPalStandardProvider : PaymentPluginBase, IConfigurable { - private readonly ICurrencyService _currencyService; - private readonly CurrencySettings _currencySettings; private readonly IOrderTotalCalculationService _orderTotalCalculationService; - private readonly HttpContextBase _httpContext; - private readonly ICommonServices _commonServices; + private readonly ICommonServices _services; private readonly ILogger _logger; - public PayPalStandardProvider(ICurrencyService currencyService, - HttpContextBase httpContext, - CurrencySettings currencySettings, + public PayPalStandardProvider( IOrderTotalCalculationService orderTotalCalculationService, - ICommonServices commonServices, + ICommonServices services, ILogger logger) { - _currencyService = currencyService; - _currencySettings = currencySettings; _orderTotalCalculationService = orderTotalCalculationService; - _httpContext = httpContext; - _commonServices = commonServices; + _services = services; _logger = logger; } + public static string SystemName { get { return "Payments.PayPalStandard"; } } + + public override PaymentMethodType PaymentMethodType + { + get + { + return PaymentMethodType.Redirection; + } + } + /// /// Process a payment /// @@ -66,7 +61,7 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces var result = new ProcessPaymentResult(); result.NewPaymentStatus = PaymentStatus.Pending; - var settings = _commonServices.Settings.LoadSetting(processPaymentRequest.StoreId); + var settings = _services.Settings.LoadSetting(processPaymentRequest.StoreId); if (settings.BusinessEmail.IsEmpty() || settings.PdtToken.IsEmpty()) { @@ -85,10 +80,11 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay if (postProcessPaymentRequest.Order.PaymentStatus == PaymentStatus.Paid) return; - var settings = _commonServices.Settings.LoadSetting(postProcessPaymentRequest.Order.StoreId); + var store = _services.StoreService.GetStoreById(postProcessPaymentRequest.Order.StoreId); + var settings = _services.Settings.LoadSetting(postProcessPaymentRequest.Order.StoreId); var builder = new StringBuilder(); - builder.Append(PayPalHelper.GetPaypalUrl(settings)); + builder.Append(settings.GetPayPalUrl()); string orderNumber = postProcessPaymentRequest.Order.GetOrderNumber(); string cmd = (settings.PassProductNamesAndTotals ? "_cart" : "_xclick"); @@ -200,11 +196,11 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay if (cartTotal > postProcessPaymentRequest.Order.OrderTotal) { - /* Take the difference between what the order total is and what it should be and use that as the "discount". - * The difference equals the amount of the gift card and/or reward points used. - */ + // Take the difference between what the order total is and what it should be and use that as the "discount". + // The difference equals the amount of the gift card and/or reward points used. decimal discountTotal = cartTotal - postProcessPaymentRequest.Order.OrderTotal; discountTotal = Math.Round(discountTotal, 2); + //gift card or rewared point amount applied to cart in SmartStore.NET - shows in Paypal as "discount" builder.AppendFormat("&discount_amount_cart={0}", discountTotal.ToString("0.00", CultureInfo.InvariantCulture)); } @@ -220,7 +216,7 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay builder.AppendFormat("&custom={0}", postProcessPaymentRequest.Order.OrderGuid); builder.AppendFormat("&charset={0}", "utf-8"); - builder.Append(string.Format("&no_note=1¤cy_code={0}", HttpUtility.UrlEncode(_currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode))); + builder.Append(string.Format("&no_note=1¤cy_code={0}", HttpUtility.UrlEncode(store.PrimaryStoreCurrency.CurrencyCode))); builder.AppendFormat("&invoice={0}", HttpUtility.UrlEncode(orderNumber)); builder.AppendFormat("&rm=2", new object[0]); @@ -239,8 +235,8 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay builder.AppendFormat("&no_shipping=1", new object[0]); } - string returnUrl = _commonServices.WebHelper.GetStoreLocation(false) + "Plugins/PaymentPayPalStandard/PDTHandler"; - string cancelReturnUrl = _commonServices.WebHelper.GetStoreLocation(false) + "Plugins/PaymentPayPalStandard/CancelOrder"; + var returnUrl = _services.WebHelper.GetStoreLocation(store.SslEnabled) + "Plugins/SmartStore.PayPal/PayPalStandard/PDTHandler"; + var cancelReturnUrl = _services.WebHelper.GetStoreLocation(store.SslEnabled) + "Plugins/SmartStore.PayPal/PayPalStandard/CancelOrder"; builder.AppendFormat("&return={0}&cancel_return={1}", HttpUtility.UrlEncode(returnUrl), HttpUtility.UrlEncode(cancelReturnUrl)); //Instant Payment Notification (server to server message) @@ -248,7 +244,7 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay { string ipnUrl; if (String.IsNullOrWhiteSpace(settings.IpnUrl)) - ipnUrl = _commonServices.WebHelper.GetStoreLocation(false) + "Plugins/PaymentPayPalStandard/IPNHandler"; + ipnUrl = _services.WebHelper.GetStoreLocation(store.SslEnabled) + "Plugins/SmartStore.PayPal/PayPalStandard/IPNHandler"; else ipnUrl = settings.IpnUrl; builder.AppendFormat("¬ify_url={0}", ipnUrl); @@ -285,7 +281,7 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay builder.AppendFormat("&zip={0}", HttpUtility.UrlEncode(address.ZipPostalCode)); builder.AppendFormat("&email={0}", HttpUtility.UrlEncode(address.Email)); - _httpContext.Response.Redirect(builder.ToString()); + postProcessPaymentRequest.RedirectUrl = builder.ToString(); } /// @@ -315,7 +311,7 @@ public override decimal GetAdditionalHandlingFee(IList(_commonServices.StoreContext.CurrentStore.Id); + var settings = _services.Settings.LoadSetting(_services.StoreContext.CurrentStore.Id); result = this.CalculateAdditionalFee(_orderTotalCalculationService, cart, settings.AdditionalFee, settings.AdditionalFeePercentage); } @@ -325,7 +321,6 @@ public override decimal GetAdditionalHandlingFee(IList /// Gets PDT details /// @@ -335,22 +330,29 @@ public override decimal GetAdditionalHandlingFee(IListResult public bool GetPDTDetails(string tx, PayPalStandardPaymentSettings settings, out Dictionary values, out string response) { - var req = (HttpWebRequest)WebRequest.Create(PayPalHelper.GetPaypalUrl(settings)); - req.Method = "POST"; - req.ContentType = "application/x-www-form-urlencoded"; + var request = settings.GetPayPalWebRequest(); + request.Method = "POST"; + request.ContentType = "application/x-www-form-urlencoded"; - string formContent = string.Format("cmd=_notify-synch&at={0}&tx={1}", settings.PdtToken, tx); - req.ContentLength = formContent.Length; + var formContent = string.Format("cmd=_notify-synch&at={0}&tx={1}", settings.PdtToken, tx); + request.ContentLength = formContent.Length; - using (var sw = new StreamWriter(req.GetRequestStream(), Encoding.ASCII)) - sw.Write(formContent); + using (var sw = new StreamWriter(request.GetRequestStream(), Encoding.ASCII)) + { + sw.Write(formContent); + } response = null; - using (var sr = new StreamReader(req.GetResponse().GetResponseStream())) - response = HttpUtility.UrlDecode(sr.ReadToEnd()); + using (var sr = new StreamReader(request.GetResponse().GetResponseStream())) + { + response = HttpUtility.UrlDecode(sr.ReadToEnd()); + } values = new Dictionary(StringComparer.OrdinalIgnoreCase); - bool firstLine = true, success = false; + + var firstLine = true; + var success = false; + foreach (string l in response.Split('\n')) { string line = l.Trim(); @@ -408,10 +410,10 @@ public List GetLineItems(PostProcessPaymentRequest postProcessPa var order = postProcessPaymentRequest.Order; var lst = new List(); - // order items - foreach (var orderItem in order.OrderItems) + // order items... checkout attributes are included in order total + foreach (var orderItem in order.OrderItems) { - var item = new PayPalLineItem() + var item = new PayPalLineItem { Type = PayPalItemType.CartItem, Name = orderItem.Product.GetLocalized(x => x.Name), @@ -423,30 +425,10 @@ public List GetLineItems(PostProcessPaymentRequest postProcessPa cartTotal += orderItem.PriceExclTax; } - // checkout attributes.... are included in order total - //foreach (var caValue in checkoutAttributeValues) - //{ - // var attributePrice = _taxService.GetCheckoutAttributePrice(caValue, false, order.Customer); - - // if (attributePrice > decimal.Zero && caValue.CheckoutAttribute != null) - // { - // var item = new PayPalLineItem() - // { - // Type = PayPalItemType.CheckoutAttribute, - // Name = caValue.CheckoutAttribute.GetLocalized(x => x.Name), - // Quantity = 1, - // Amount = attributePrice - // }; - // lst.Add(item); - - // cartTotal += attributePrice; - // } - //} - // shipping if (order.OrderShippingExclTax > decimal.Zero) { - var item = new PayPalLineItem() + var item = new PayPalLineItem { Type = PayPalItemType.Shipping, Name = T("Plugins.Payments.PayPalStandard.ShippingFee").Text, @@ -461,10 +443,10 @@ public List GetLineItems(PostProcessPaymentRequest postProcessPa // payment fee if (order.PaymentMethodAdditionalFeeExclTax > decimal.Zero) { - var item = new PayPalLineItem() + var item = new PayPalLineItem { Type = PayPalItemType.PaymentFee, - Name = T("Plugins.Payments.PayPalStandard.PaymentMethodFee").Text, + Name = T("Plugins.Payments.PayPal.PaymentMethodFee").Text, Quantity = 1, Amount = order.PaymentMethodAdditionalFeeExclTax }; @@ -476,7 +458,7 @@ public List GetLineItems(PostProcessPaymentRequest postProcessPa // tax if (order.OrderTax > decimal.Zero) { - var item = new PayPalLineItem() + var item = new PayPalLineItem { Type = PayPalItemType.Tax, Name = T("Plugins.Payments.PayPalStandard.SalesTax").Text, @@ -511,16 +493,18 @@ public void AdjustLineItemAmounts(List paypalItems, PostProcessP return; decimal totalSmartStore = Math.Round(postProcessPaymentRequest.Order.OrderSubtotalExclTax, 2); - decimal totalPayPal = decimal.Zero; - decimal delta, portion, rest; + decimal totalPayPal = decimal.Zero; + decimal delta, portion, rest; + + // calculate what PayPal calculates + cartItems.Each(x => totalPayPal += (x.AmountRounded * x.Quantity)); + totalPayPal = Math.Round(totalPayPal, 2, MidpointRounding.AwayFromZero); - // calculate what PayPal calculates - cartItems.Each(x => totalPayPal += (x.AmountRounded * x.Quantity)); - totalPayPal = Math.Round(totalPayPal, 2, MidpointRounding.AwayFromZero); + // calculate difference + delta = Math.Round(totalSmartStore - totalPayPal, 2); + //"SM: {0}, PP: {1}, delta: {2}".FormatInvariant(totalSmartStore, totalPayPal, delta).Dump(); - // calculate difference - delta = Math.Round(totalSmartStore - totalPayPal, 2); - if (delta == decimal.Zero) + if (delta == decimal.Zero) return; // prepare lines... only lines with quantity = 1 are adjustable. if there is no one, create one. @@ -551,56 +535,12 @@ public void AdjustLineItemAmounts(List paypalItems, PostProcessP restItem.Amount = restItem.Amount + rest; } - //"SM: {0}, PP: {1}, delta: {2} (portion: {3}, rest: {4})".FormatWith(totalSmartStore, totalPayPal, delta, portion, rest).Dump(); - } - catch (Exception exc) - { - _logger.Error(exc.Message, exc); - } - } - - - /// - /// Verifies IPN - /// - /// Form string - /// Values - /// Result - public bool VerifyIPN(string formString, out Dictionary values) - { - // settings: multistore context not possible here. we need the custom value to determine what store it is. - var settings = _commonServices.Settings.LoadSetting(); - - var req = (HttpWebRequest)WebRequest.Create(PayPalHelper.GetPaypalUrl(settings)); - req.Method = "POST"; - req.ContentType = "application/x-www-form-urlencoded"; - req.UserAgent = HttpContext.Current.Request.UserAgent; - - string formContent = string.Format("{0}&cmd=_notify-validate", formString); - req.ContentLength = formContent.Length; - - using (var sw = new StreamWriter(req.GetRequestStream(), Encoding.ASCII)) - { - sw.Write(formContent); - } - - string response = null; - using (var sr = new StreamReader(req.GetResponse().GetResponseStream())) - { - response = HttpUtility.UrlDecode(sr.ReadToEnd()); - } - bool success = response.Trim().Equals("VERIFIED", StringComparison.OrdinalIgnoreCase); - - values = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (string l in formString.Split('&')) + //"SM: {0}, PP: {1}, delta: {2} (portion: {3}, rest: {4})".FormatInvariant(totalSmartStore, totalPayPal, delta, portion, rest).Dump(); + } + catch (Exception exception) { - string line = HttpUtility.UrlDecode(l).Trim(); - int equalPox = line.IndexOf('='); - if (equalPox >= 0) - values.Add(line.Substring(0, equalPox), line.Substring(equalPox + 1)); + _logger.Error(exception.Message, exception); } - - return success; } /// @@ -628,17 +568,47 @@ public override void GetPaymentInfoRoute(out string actionName, out string contr controllerName = "PayPalStandard"; routeValues = new RouteValueDictionary() { { "area", "SmartStore.PayPal" } }; } + } - #region Properties - public override PaymentMethodType PaymentMethodType + public class PayPalLineItem : ICloneable + { + public PayPalItemType Type { get; set; } + public string Name { get; set; } + public int Quantity { get; set; } + public decimal Amount { get; set; } + + public decimal AmountRounded { get { - return PaymentMethodType.Redirection; + return Math.Round(Amount, 2); } } - #endregion + public PayPalLineItem Clone() + { + var item = new PayPalLineItem + { + Type = this.Type, + Name = this.Name, + Quantity = this.Quantity, + Amount = this.Amount + }; + return item; + } + + object ICloneable.Clone() + { + return this.Clone(); + } + } + + public enum PayPalItemType + { + CartItem = 0, + Shipping, + PaymentFee, + Tax } } diff --git a/src/Plugins/SmartStore.PayPal/RouteProvider.cs b/src/Plugins/SmartStore.PayPal/RouteProvider.cs index b6c0707bee..74e44ce093 100644 --- a/src/Plugins/SmartStore.PayPal/RouteProvider.cs +++ b/src/Plugins/SmartStore.PayPal/RouteProvider.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.PayPal { @@ -29,8 +29,17 @@ public void RegisterRoutes(RouteCollection routes) ) .DataTokens["area"] = "SmartStore.PayPal"; - //Legacay Routes - routes.MapRoute("SmartStore.PayPalExpress.IPN", + routes.MapRoute("SmartStore.PayPalPlus", + "Plugins/SmartStore.PayPal/{controller}/{action}", + new { controller = "PayPalPlus", action = "Index" }, + new[] { "SmartStore.PayPal.Controllers" } + ) + .DataTokens["area"] = Plugin.SystemName; + + + + //Legacay Routes + routes.MapRoute("SmartStore.PayPalExpress.IPN", "Plugins/PaymentPayPalExpress/IPNHandler", new { controller = "PayPalExpress", action = "IPNHandler" }, new[] { "SmartStore.PayPal.Controllers" } diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs new file mode 100644 index 0000000000..2f4b94c1c9 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Net; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Domain.Stores; +using SmartStore.PayPal.Settings; +using SmartStore.Services.Payments; + +namespace SmartStore.PayPal.Services +{ + public interface IPayPalService + { + void AddOrderNote(PayPalSettingsBase settings, Order order, string anyString, bool isIpn = false); + + void LogError(Exception exception, string shortMessage = null, string fullMessage = null, bool notify = false, IList errors = null, bool isWarning = false); + + PayPalPaymentInstruction ParsePaymentInstruction(dynamic json); + + string CreatePaymentInstruction(PayPalPaymentInstruction instruct); + + PaymentStatus GetPaymentStatus(string state, string reasonCode, PaymentStatus defaultStatus); + + PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data); + + PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings); + + PayPalResponse GetPayment(PayPalApiSettingsBase settings, PayPalSessionData session); + + PayPalResponse CreatePayment( + PayPalApiSettingsBase settings, + PayPalSessionData session, + List cart, + string providerSystemName, + string returnUrl, + string cancelUrl); + + PayPalResponse PatchShipping( + PayPalApiSettingsBase settings, + PayPalSessionData session, + List cart, + string providerSystemName); + + PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessionData session); + + PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData session, RefundPaymentRequest request); + + PayPalResponse Capture(PayPalApiSettingsBase settings, PayPalSessionData session, CapturePaymentRequest request); + + PayPalResponse Void(PayPalApiSettingsBase settings, PayPalSessionData session, VoidPaymentRequest request); + + PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session, Store store); + + PayPalResponse DeleteCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session); + + PayPalResponse CreateWebhook(PayPalApiSettingsBase settings, PayPalSessionData session, string url); + + PayPalResponse DeleteWebhook(PayPalApiSettingsBase settings, PayPalSessionData session); + + HttpStatusCode ProcessWebhook( + PayPalApiSettingsBase settings, + NameValueCollection headers, + string rawJson, + string providerSystemName); + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs new file mode 100644 index 0000000000..d5bba1b72c --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs @@ -0,0 +1,27 @@ + +namespace SmartStore.PayPal.Services +{ + public enum PayPalPaymentInstructionItem + { + Reference = 0, + BankRoutingNumber, + Bank, + Bic, + Iban, + AccountHolder, + AccountNumber, + Amount, + PaymentDueDate, + Details + } + + public enum PayPalMessage + { + Message = 0, + Event, + EventId, + State, + Amount, + PaymentId + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs deleted file mode 100644 index fd1c3a32de..0000000000 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs +++ /dev/null @@ -1,244 +0,0 @@ -using System; -using System.Net; -using System.Text; -using System.Web.Routing; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Payments; -using SmartStore.PayPal.PayPalSvc; -using SmartStore.PayPal.Settings; -using SmartStore.Web.Framework.Plugins; - -namespace SmartStore.PayPal.Services -{ - /// - /// Represents paypal helper - /// - public static class PayPalHelper - { - /// - /// Gets a payment status - /// - /// PayPal payment status - /// PayPal pending reason - /// Payment status - public static PaymentStatus GetPaymentStatus(string paymentStatus, string pendingReason) - { - var result = PaymentStatus.Pending; - - if (paymentStatus == null) - paymentStatus = string.Empty; - - if (pendingReason == null) - pendingReason = string.Empty; - - switch (paymentStatus.ToLowerInvariant()) - { - case "pending": - switch (pendingReason.ToLowerInvariant()) - { - case "authorization": - result = PaymentStatus.Authorized; - break; - default: - result = PaymentStatus.Pending; - break; - } - break; - case "processed": - case "completed": - case "canceled_reversal": - result = PaymentStatus.Paid; - break; - case "denied": - case "expired": - case "failed": - case "voided": - result = PaymentStatus.Voided; - break; - case "refunded": - case "reversed": - result = PaymentStatus.Refunded; - break; - default: - break; - } - return result; - } - - /// - /// Checks response - /// - /// response - /// Error message if exists - /// True - response OK; otherwise, false - public static bool CheckSuccess(PluginHelper helper, AbstractResponseType abstractResponse, out string errorMsg) - { - bool success = false; - StringBuilder sb = new StringBuilder(); - switch (abstractResponse.Ack) - { - case AckCodeType.Success: - case AckCodeType.SuccessWithWarning: - success = true; - break; - default: - break; - } - if (null != abstractResponse.Errors) - { - foreach (ErrorType errorType in abstractResponse.Errors) - { - if (sb.Length <= 0) - { - sb.Append(Environment.NewLine); - } - sb.AppendLine("{0}: {1}".FormatWith(helper.GetResource("Admin.System.Log.Fields.FullMessage"), errorType.LongMessage)); - sb.AppendLine("{0}: {1}".FormatWith(helper.GetResource("Admin.System.Log.Fields.ShortMessage"), errorType.ShortMessage)); - sb.Append("Code: ").Append(errorType.ErrorCode).Append(Environment.NewLine); - } - } - errorMsg = sb.ToString(); - return success; - } - - /// - /// Get Paypal currency code - /// - /// Currency - /// Paypal currency code - public static CurrencyCodeType GetPaypalCurrency(Currency currency) - { - CurrencyCodeType currencyCodeType = CurrencyCodeType.USD; - try - { - currencyCodeType = (CurrencyCodeType)Enum.Parse(typeof(CurrencyCodeType), currency.CurrencyCode, true); - } - catch - { - } - return currencyCodeType; - } - - public static string CheckIfButtonExists(string buttonUrl) - { - - HttpWebResponse response = null; - var request = (HttpWebRequest)WebRequest.Create(buttonUrl); - request.Method = "HEAD"; - - try - { - response = (HttpWebResponse)request.GetResponse(); - return buttonUrl; - } - catch (WebException) - { - /* A WebException will be thrown if the status of the response is not `200 OK` */ - return "https://www.paypalobjects.com/en_US/i/btn/btn_xpressCheckout.gif"; - } - finally - { - if (response != null) - { - response.Close(); - } - } - - - } - - public static bool CurrentPageIsBasket(RouteData routeData) - { - return routeData.GetRequiredString("controller").IsCaseInsensitiveEqual("ShoppingCart") - && routeData.GetRequiredString("action").IsCaseInsensitiveEqual("Cart"); - } - - //TODO: join the following two methods, with help of payment method type - - /// - /// Gets Paypal URL - /// - /// - public static string GetPaypalUrl(PayPalSettingsBase settings) - { - return settings.UseSandbox ? - "https://www.sandbox.paypal.com/cgi-bin/webscr" : - "https://www.paypal.com/cgi-bin/webscr"; - } - - /// - /// Gets Paypal URL - /// - /// - public static string GetPaypalServiceUrl(PayPalSettingsBase settings) - { - return settings.UseSandbox ? - "https://api-3t.sandbox.paypal.com/2.0/" : - "https://api-3t.paypal.com/2.0/"; - } - - public static string GetApiVersion() - { - return "109"; - } - - /// - /// Gets API credentials - /// - /// - public static CustomSecurityHeaderType GetPaypalApiCredentials(PayPalApiSettingsBase settings) - { - CustomSecurityHeaderType customSecurityHeaderType = new CustomSecurityHeaderType(); - - customSecurityHeaderType.Credentials = new UserIdPasswordType(); - customSecurityHeaderType.Credentials.Username = settings.ApiAccountName; - customSecurityHeaderType.Credentials.Password = settings.ApiAccountPassword; - customSecurityHeaderType.Credentials.Signature = settings.Signature; - customSecurityHeaderType.Credentials.Subject = ""; - - return customSecurityHeaderType; - } - /// - /// Get Paypal country code - /// - /// Country - /// Paypal country code - public static CountryCodeType GetPaypalCountryCodeType(Country country) - { - CountryCodeType payerCountry = CountryCodeType.US; - try - { - payerCountry = (CountryCodeType)Enum.Parse(typeof(CountryCodeType), country.TwoLetterIsoCode); - } - catch - { - } - return payerCountry; - } - - /// - /// Get Paypal credit card type - /// - /// Credit card type - /// Paypal credit card type - public static CreditCardTypeType GetPaypalCreditCardType(string creditCardType) - { - var creditCardTypeType = (CreditCardTypeType)Enum.Parse(typeof(CreditCardTypeType), creditCardType); - return creditCardTypeType; - } - - public static PaymentActionCodeType GetPaymentAction(PayPalExpressPaymentSettings payPalExpressPaymentSettings) - { - if (payPalExpressPaymentSettings.TransactMode == TransactMode.Authorize) - { - return PaymentActionCodeType.Authorization; - } - else - { - return PaymentActionCodeType.Sale; - } - } - - } -} - diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalProcessPaymentRequest.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalProcessPaymentRequest.cs deleted file mode 100644 index 22984ffeac..0000000000 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalProcessPaymentRequest.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using SmartStore.Services.Payments; - -namespace SmartStore.PayPal.Services -{ - public class PayPalProcessPaymentRequest : ProcessPaymentRequest - { - /// - /// Gets or sets an order Discount Amount - /// - public decimal Discount { get; set; } - } -} diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs new file mode 100644 index 0000000000..62e53f01d5 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -0,0 +1,1177 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Web; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Discounts; +using SmartStore.Core.Domain.Logging; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Domain.Tax; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.PayPal.Settings; +using SmartStore.Services; +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; +using SmartStore.Services.Tax; + +namespace SmartStore.PayPal.Services +{ + public class PayPalService : IPayPalService + { + private readonly Lazy> _orderRepository; + private readonly ICommonServices _services; + private readonly IOrderService _orderService; + private readonly IOrderProcessingService _orderProcessingService; + private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly IPaymentService _paymentService; + private readonly IPriceCalculationService _priceCalculationService; + private readonly ITaxService _taxService; + private readonly ICurrencyService _currencyService; + private readonly Lazy _pictureService; + private readonly Lazy _companyInfoSettings; + + public PayPalService( + Lazy> orderRepository, + ICommonServices services, + IOrderService orderService, + IOrderProcessingService orderProcessingService, + IOrderTotalCalculationService orderTotalCalculationService, + IPaymentService paymentService, + IPriceCalculationService priceCalculationService, + ITaxService taxService, + ICurrencyService currencyService, + Lazy pictureService, + Lazy companyInfoSettings) + { + _orderRepository = orderRepository; + _services = services; + _orderService = orderService; + _orderProcessingService = orderProcessingService; + _orderTotalCalculationService = orderTotalCalculationService; + _paymentService = paymentService; + _priceCalculationService = priceCalculationService; + _taxService = taxService; + _currencyService = currencyService; + _pictureService = pictureService; + _companyInfoSettings = companyInfoSettings; + + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + private Dictionary CreateAddress(Address addr, bool addRecipientName) + { + var dic = new Dictionary(); + + dic.Add("line1", addr.Address1.Truncate(100)); + + if (addr.Address2.HasValue()) + { + dic.Add("line2", addr.Address2.Truncate(100)); + } + + dic.Add("city", addr.City.Truncate(50)); + + if (addr.CountryId != 0 && addr.Country != null) + { + dic.Add("country_code", addr.Country.TwoLetterIsoCode); + } + + dic.Add("postal_code", addr.ZipPostalCode.Truncate(20)); + + if (addr.StateProvinceId != 0 && addr.StateProvince != null) + { + dic.Add("state", addr.StateProvince.Abbreviation.Truncate(100)); + } + + if (addRecipientName) + { + dic.Add("recipient_name", addr.GetFullName().Truncate(50)); + } + + return dic; + } + + private Dictionary CreateAmount( + Store store, + Customer customer, + List cart, + string providerSystemName, + List> items) + { + var amount = new Dictionary(); + var amountDetails = new Dictionary(); + var language = _services.WorkContext.WorkingLanguage; + var currency = _services.WorkContext.WorkingCurrency; + var currencyCode = store.PrimaryStoreCurrency.CurrencyCode; + var includingTax = (_services.WorkContext.GetTaxDisplayTypeFor(customer, store.Id) == TaxDisplayType.IncludingTax); + + Discount orderAppliedDiscount; + List appliedGiftCards; + int redeemedRewardPoints = 0; + decimal redeemedRewardPointsAmount; + decimal orderDiscountInclTax; + decimal totalOrderItems = decimal.Zero; + var taxTotal = decimal.Zero; + + var shipping = Math.Round(_orderTotalCalculationService.GetShoppingCartShippingTotal(cart) ?? decimal.Zero, 2); + + var additionalHandlingFee = _paymentService.GetAdditionalHandlingFee(cart, providerSystemName); + var paymentFeeBase = _taxService.GetPaymentMethodAdditionalFee(additionalHandlingFee, customer); + var paymentFee = Math.Round(_currencyService.ConvertFromPrimaryStoreCurrency(paymentFeeBase, currency), 2); + + var total = Math.Round(_orderTotalCalculationService.GetShoppingCartTotal(cart, out orderDiscountInclTax, out orderAppliedDiscount, out appliedGiftCards, + out redeemedRewardPoints, out redeemedRewardPointsAmount) ?? decimal.Zero, 2); + + // line items + foreach (var item in cart) + { + decimal unitPriceTaxRate = decimal.Zero; + decimal unitPrice = _priceCalculationService.GetUnitPrice(item, true); + decimal productPrice = _taxService.GetProductPrice(item.Item.Product, unitPrice, includingTax, customer, out unitPriceTaxRate); + + if (items != null && productPrice != decimal.Zero) + { + var line = new Dictionary(); + line.Add("quantity", item.Item.Quantity); + line.Add("name", item.Item.Product.GetLocalized(x => x.Name, language.Id, true, false).Truncate(127)); + line.Add("price", productPrice.FormatInvariant()); + line.Add("currency", currencyCode); + line.Add("sku", item.Item.Product.Sku.Truncate(50)); + items.Add(line); + } + + totalOrderItems += (Math.Round(productPrice, 2) * item.Item.Quantity); + } + + if (items != null && paymentFee != decimal.Zero) + { + var line = new Dictionary(); + line.Add("quantity", "1"); + line.Add("name", T("Order.PaymentMethodAdditionalFee").Text.Truncate(127)); + line.Add("price", paymentFee.FormatInvariant()); + line.Add("currency", currencyCode); + items.Add(line); + + totalOrderItems += Math.Round(paymentFee, 2); + } + + if (!includingTax) + { + // "To avoid rounding errors we recommend not submitting tax amounts on line item basis. + // Calculated tax amounts for the entire shopping basket may be submitted in the amount objects. + // In this case the item amounts will be treated as amounts excluding tax. + // In a B2C scenario, where taxes are included, no taxes should be submitted to PayPal." + + SortedDictionary taxRates = null; + taxTotal = Math.Round(_orderTotalCalculationService.GetTaxTotal(cart, out taxRates), 2); + + amountDetails.Add("tax", taxTotal.FormatInvariant()); + } + + var itemsPlusMisc = (totalOrderItems + taxTotal + shipping); + + if (total != itemsPlusMisc) + { + var otherAmount = Math.Round(total - itemsPlusMisc, 2); + totalOrderItems += otherAmount; + + if (items != null && otherAmount != decimal.Zero) + { + // e.g. discount applied to cart total + var line = new Dictionary(); + line.Add("quantity", "1"); + line.Add("name", T("Plugins.SmartStore.PayPal.Other").Text.Truncate(127)); + line.Add("price", otherAmount.FormatInvariant()); + line.Add("currency", currencyCode); + items.Add(line); + } + } + + // fill amount object + amountDetails.Add("shipping", shipping.FormatInvariant()); + amountDetails.Add("subtotal", totalOrderItems.FormatInvariant()); + + //if (paymentFee != decimal.Zero) + //{ + // amountDetails.Add("handling_fee", paymentFee.FormatInvariant()); + //} + + amount.Add("total", total.FormatInvariant()); + amount.Add("currency", currencyCode); + amount.Add("details", amountDetails); + + return amount; + } + + private string ToInfoString(dynamic json) + { + var sb = new StringBuilder(); + + try + { + string[] strings = T("Plugins.SmartStore.PayPal.MessageStrings").Text.SplitSafe(";"); + var message = (string)json.summary; + var eventType = (string)json.event_type; + var eventId = (string)json.id; + string state = null; + string amount = null; + string paymentId = null; + + if (json.resource != null) + { + state = (string)json.resource.state; + paymentId = (string)json.resource.parent_payment; + + if (json.resource.amount != null) + amount = string.Concat((string)json.resource.amount.total, " ", (string)json.resource.amount.currency); + } + + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.Message), message.NaIfEmpty())); + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.Event), eventType.NaIfEmpty())); + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.EventId), eventId.NaIfEmpty())); + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.PaymentId), paymentId.NaIfEmpty())); + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.State), state.NaIfEmpty())); + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.Amount), amount.NaIfEmpty())); + } + catch { } + + return sb.ToString(); + } + + public static string GetApiUrl(bool sandbox) + { + return sandbox ? "https://api.sandbox.paypal.com" : "https://api.paypal.com"; + } + + public static Dictionary GetSecurityProtocols() + { + var dic = new Dictionary(); + + foreach (SecurityProtocolType protocol in Enum.GetValues(typeof(SecurityProtocolType))) + { + string friendlyName = null; + switch (protocol) + { + case SecurityProtocolType.Ssl3: + friendlyName = "SSL 3.0"; + break; + case SecurityProtocolType.Tls: + friendlyName = "TLS 1.0"; + break; + case SecurityProtocolType.Tls11: + friendlyName = "TLS 1.1"; + break; + case SecurityProtocolType.Tls12: + friendlyName = "TLS 1.2"; + break; + default: + friendlyName = protocol.ToString().ToUpper(); + break; + } + + dic.Add(protocol, friendlyName); + } + return dic; + } + + public void AddOrderNote(PayPalSettingsBase settings, Order order, string anyString, bool isIpn = false) + { + try + { + if (order == null || anyString.IsEmpty() || (settings != null && !settings.AddOrderNotes)) + return; + + string[] orderNoteStrings = T("Plugins.SmartStore.PayPal.OrderNoteStrings").Text.SplitSafe(";"); + var faviconUrl = "{0}Plugins/{1}/Content/favicon.png".FormatInvariant(_services.WebHelper.GetStoreLocation(false), Plugin.SystemName); + + var sb = new StringBuilder(); + sb.AppendFormat("", faviconUrl); + + var note = orderNoteStrings.SafeGet(0).FormatInvariant(anyString); + + sb.AppendFormat("{0}", note); + + if (isIpn) + order.HasNewPaymentNotification = true; + + _orderService.AddOrderNote(order, sb.ToString()); + } + catch { } + } + + public void LogError(Exception exception, string shortMessage = null, string fullMessage = null, bool notify = false, IList errors = null, bool isWarning = false) + { + try + { + if (exception != null) + { + shortMessage = exception.ToAllMessages(); + fullMessage = exception.ToString(); + } + + if (shortMessage.HasValue()) + { + shortMessage = "PayPal. " + shortMessage; + Logger.InsertLog(isWarning ? LogLevel.Warning : LogLevel.Error, shortMessage, fullMessage.EmptyNull()); + + if (notify) + { + if (isWarning) + _services.Notifier.Warning(new LocalizedString(shortMessage)); + else + _services.Notifier.Error(new LocalizedString(shortMessage)); + } + } + } + catch (Exception) { } + + if (errors != null && shortMessage.HasValue()) + { + errors.Add(shortMessage); + } + } + + public PayPalPaymentInstruction ParsePaymentInstruction(dynamic json) + { + if (json == null) + return null; + + DateTime dt; + var result = new PayPalPaymentInstruction(); + + try + { + result.ReferenceNumber = (string)json.reference_number; + result.Type = (string)json.instruction_type; + result.Note = (string)json.note; + + if (DateTime.TryParse((string)json.payment_due_date, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt)) + { + result.DueDate = dt; + } + + if (json.amount != null) + { + result.AmountCurrencyCode = (string)json.amount.currency; + result.Amount = decimal.Parse((string)json.amount.value, CultureInfo.InvariantCulture); + } + + var rbi = json.recipient_banking_instruction; + + if (rbi != null) + { + result.RecipientBanking = new PayPalPaymentInstruction.RecipientBankingInstruction(); + result.RecipientBanking.BankName = (string)rbi.bank_name; + result.RecipientBanking.AccountHolderName = (string)rbi.account_holder_name; + result.RecipientBanking.AccountNumber = (string)rbi.account_number; + result.RecipientBanking.RoutingNumber = (string)rbi.routing_number; + result.RecipientBanking.Iban = (string)rbi.international_bank_account_number; + result.RecipientBanking.Bic = (string)rbi.bank_identifier_code; + } + + if (json.links != null) + { + result.Link = (string)json.links[0].href; + } + } + catch { } + + return result; + } + + public string CreatePaymentInstruction(PayPalPaymentInstruction instruct) + { + if (instruct == null || instruct.RecipientBanking == null) + return null; + + if (!instruct.IsManualBankTransfer && !instruct.IsPayUponInvoice) + return null; + + var sb = new StringBuilder("
      "); + var paragraphTemplate = "
      {0}
      "; + var rowTemplate = "{0}: {1}
      "; + var instructStrings = T("Plugins.SmartStore.PayPal.PaymentInstructionStrings").Text.SplitSafe(";"); + + if (instruct.IsManualBankTransfer) + { + sb.AppendFormat(paragraphTemplate, T("Plugins.SmartStore.PayPal.ManualBankTransferNote")); + + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Reference), instruct.ReferenceNumber); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.AccountNumber), instruct.RecipientBanking.AccountNumber); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.AccountHolder), instruct.RecipientBanking.AccountHolderName); + + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Bank), instruct.RecipientBanking.BankName); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Iban), instruct.RecipientBanking.Iban); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Bic), instruct.RecipientBanking.Bic); + } + else if (instruct.IsPayUponInvoice) + { + string amount = null; + var culture = new CultureInfo(_services.WorkContext.WorkingLanguage.LanguageCulture ?? "de-DE"); + + try + { + var currency = _currencyService.GetCurrencyByCode(instruct.AmountCurrencyCode); + var format = (currency != null && currency.CustomFormatting.HasValue() ? currency.CustomFormatting : "C"); + + amount = instruct.Amount.ToString(format, culture); + } + catch { } + + if (amount.IsEmpty()) + { + amount = string.Concat(instruct.Amount.ToString("N"), " ", instruct.AmountCurrencyCode); + } + + var intro = T("Plugins.SmartStore.PayPal.PayUponInvoiceLegalNote", _companyInfoSettings.Value.CompanyName.NaIfEmpty()); + + // /v1/payments/payment//payment-instruction not working anymore. Serves 401 unauthorized. + //if (instruct.Link.HasValue()) + //{ + // intro = "{0} {2}.".FormatInvariant(intro, instruct.Link, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Details)); + //} + + sb.AppendFormat(paragraphTemplate, intro); + + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Bank), instruct.RecipientBanking.BankName); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.AccountHolder), instruct.RecipientBanking.AccountHolderName); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Iban), instruct.RecipientBanking.Iban); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Bic), instruct.RecipientBanking.Bic); + sb.Append("
      "); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Amount), amount); + if (instruct.DueDate.HasValue) + { + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.PaymentDueDate), instruct.DueDate.Value.ToString("d", culture)); + } + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Reference), instruct.ReferenceNumber); + } + + sb.Append("
      "); + + return sb.ToString(); + } + + public PaymentStatus GetPaymentStatus(string state, string reasonCode, PaymentStatus defaultStatus) + { + var result = defaultStatus; + + if (state == null) + state = string.Empty; + + if (reasonCode == null) + reasonCode = string.Empty; + + switch (state.ToLowerInvariant()) + { + case "authorized": + result = PaymentStatus.Authorized; + break; + case "pending": + switch (reasonCode.ToLowerInvariant()) + { + case "authorization": + result = PaymentStatus.Authorized; + break; + default: + result = PaymentStatus.Pending; + break; + } + break; + case "completed": + case "captured": + case "partially_captured": + case "canceled_reversal": + result = PaymentStatus.Paid; + break; + case "denied": + case "expired": + case "failed": + case "voided": + result = PaymentStatus.Voided; + break; + case "reversed": + case "refunded": + result = PaymentStatus.Refunded; + break; + case "partially_refunded": + result = PaymentStatus.PartiallyRefunded; + break; + } + + return result; + } + + public PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data) + { + var isJson = (data.HasValue() && (data.StartsWith("{") || data.StartsWith("["))); + var encoding = (isJson ? Encoding.UTF8 : Encoding.ASCII); + var result = new PayPalResponse(); + HttpWebResponse webResponse = null; + + var url = GetApiUrl(settings.UseSandbox) + path.EnsureStartsWith("/"); + + if (method.IsCaseInsensitiveEqual("GET") && data.HasValue()) + url = url.EnsureEndsWith("?") + data; + + if (settings.SecurityProtocol.HasValue) + ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; + + var request = (HttpWebRequest)WebRequest.Create(url); + request.Method = method; + request.Accept = "application/json"; + request.ContentType = (isJson ? "application/json" : "application/x-www-form-urlencoded"); + + try + { + if (HttpContext.Current != null && HttpContext.Current.Request != null) + request.UserAgent = HttpContext.Current.Request.UserAgent; + else + request.UserAgent = Plugin.SystemName; + } + catch { } + + if (path.EmptyNull().EndsWith("/token")) + { + // see https://github.com/paypal/sdk-core-dotnet/blob/master/Source/SDK/OAuthTokenCredential.cs + byte[] credentials = Encoding.UTF8.GetBytes("{0}:{1}".FormatInvariant(settings.ClientId, settings.Secret)); + + request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(credentials)); + } + else + { + request.Headers["Authorization"] = "Bearer " + accessToken.EmptyNull(); + } + + request.Headers["PayPal-Partner-Attribution-Id"] = "SmartStoreAG_Cart_PayPalPlus"; + + if (data.HasValue() && (method.IsCaseInsensitiveEqual("POST") || method.IsCaseInsensitiveEqual("PUT") || method.IsCaseInsensitiveEqual("PATCH"))) + { + byte[] bytes = encoding.GetBytes(data); + + request.ContentLength = bytes.Length; + + using (var stream = request.GetRequestStream()) + { + stream.Write(bytes, 0, bytes.Length); + } + } + else + { + request.ContentLength = 0; + } + + try + { + webResponse = request.GetResponse() as HttpWebResponse; + result.Success = ((int)webResponse.StatusCode < 400); + } + catch (WebException wexc) + { + result.Success = false; + result.ErrorMessage = wexc.ToString(); + webResponse = wexc.Response as HttpWebResponse; + } + catch (Exception exception) + { + result.Success = false; + result.ErrorMessage = exception.ToString(); + LogError(exception); + } + + try + { + if (webResponse != null) + { + using (var reader = new StreamReader(webResponse.GetResponseStream(), Encoding.UTF8)) + { + var rawResponse = reader.ReadToEnd(); + if (rawResponse.HasValue()) + { + try + { + if (rawResponse.StartsWith("[")) + result.Json = JArray.Parse(rawResponse); + else + result.Json = JObject.Parse(rawResponse); + + if (result.Json != null) + { + if (!result.Success) + { + var name = (string)result.Json.name; + var message = (string)result.Json.message; + + if (name.IsEmpty()) + name = (string)result.Json.error; + + if (message.IsEmpty()) + message = (string)result.Json.error_description; + + result.ErrorMessage = "{0} ({1}).".FormatInvariant(message.NaIfEmpty(), name.NaIfEmpty()); + } + } + } + catch + { + if (!result.Success) + result.ErrorMessage = rawResponse; + } + } + } + + if (!result.Success) + { + if (result.ErrorMessage.IsEmpty()) + result.ErrorMessage = webResponse.StatusDescription; + + LogError(null, result.ErrorMessage, string.Concat(data.NaIfEmpty(), "\r\n\r\n", result.Json == null ? "" : result.Json.ToString()), false); + } + } + } + catch (Exception exception) + { + LogError(exception); + } + finally + { + if (webResponse != null) + { + webResponse.Close(); + webResponse.Dispose(); + } + } + + return result; + } + + public PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings) + { + if (session.AccessToken.IsEmpty() || DateTime.UtcNow >= session.TokenExpiration) + { + var result = CallApi("POST", "/v1/oauth2/token", null, settings, "grant_type=client_credentials"); + + if (result.Success) + { + session.AccessToken = (string)result.Json.access_token; + + var expireSeconds = ((string)result.Json.expires_in).ToInt(5 * 60); + + session.TokenExpiration = DateTime.UtcNow.AddSeconds(expireSeconds); + } + else + { + return result; + } + } + + return new PayPalResponse + { + Success = session.AccessToken.HasValue() + }; + } + + public PayPalResponse GetPayment(PayPalApiSettingsBase settings, PayPalSessionData session) + { + var result = CallApi("GET", "/v1/payments/payment/" + session.PaymentId, session.AccessToken, settings, null); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + return result; + } + + public PayPalResponse CreatePayment( + PayPalApiSettingsBase settings, + PayPalSessionData session, + List cart, + string providerSystemName, + string returnUrl, + string cancelUrl) + { + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; + + //var dateOfBirth = customer.GetAttribute(SystemCustomerAttributeNames.DateOfBirth); + + var data = new Dictionary(); + var redirectUrls = new Dictionary(); + var payer = new Dictionary(); + //var payerInfo = new Dictionary(); + var transaction = new Dictionary(); + var items = new List>(); + var itemList = new Dictionary(); + + // "PayPal PLUS only supports transaction type “Sale” (instant settlement)" + if (providerSystemName == PayPalPlusProvider.SystemName) + data.Add("intent", "sale"); + else + data.Add("intent", settings.TransactMode == TransactMode.AuthorizeAndCapture ? "sale" : "authorize"); + + if (settings.ExperienceProfileId.HasValue()) + data.Add("experience_profile_id", settings.ExperienceProfileId); + + // redirect urls + if (returnUrl.HasValue()) + redirectUrls.Add("return_url", returnUrl); + + if (cancelUrl.HasValue()) + redirectUrls.Add("cancel_url", cancelUrl); + + if (redirectUrls.Any()) + data.Add("redirect_urls", redirectUrls); + + // payer, payer_info + // paypal review: do not transmit + //if (dateOfBirth.HasValue) + //{ + // payerInfo.Add("birth_date", dateOfBirth.Value.ToString("yyyy-MM-dd")); + //} + //if (customer.BillingAddress != null) + //{ + // payerInfo.Add("billing_address", CreateAddress(customer.BillingAddress, false)); + //} + + payer.Add("payment_method", "paypal"); + //payer.Add("payer_info", payerInfo); + data.Add("payer", payer); + + var amount = CreateAmount(store, customer, cart, providerSystemName, items); + + itemList.Add("items", items); + + transaction.Add("amount", amount); + transaction.Add("item_list", itemList); + transaction.Add("invoice_number", session.OrderGuid.ToString()); + + data.Add("transactions", new List> { transaction }); + + var result = CallApi("POST", "/v1/payments/payment", session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + //Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + (result.Json != null ? result.Json.ToString() : "")); + + return result; + } + + public PayPalResponse PatchShipping( + PayPalApiSettingsBase settings, + PayPalSessionData session, + List cart, + string providerSystemName) + { + var data = new List>(); + var amountTotal = new Dictionary(); + + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; + + if (customer.ShippingAddress != null) + { + var shippingAddress = new Dictionary(); + shippingAddress.Add("op", "add"); + shippingAddress.Add("path", "/transactions/0/item_list/shipping_address"); + shippingAddress.Add("value", CreateAddress(customer.ShippingAddress, true)); + data.Add(shippingAddress); + } + + // update of whole amount object required. patching single amount values not possible (MALFORMED_REQUEST). + var amount = CreateAmount(store, customer, cart, providerSystemName, null); + + amountTotal.Add("op", "replace"); + amountTotal.Add("path", "/transactions/0/amount"); + amountTotal.Add("value", amount); + data.Add(amountTotal); + + var result = CallApi("PATCH", "/v1/payments/payment/{0}".FormatInvariant(session.PaymentId), session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + //Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + (result.Json != null ? result.Json.ToString() : "")); + + return result; + } + + public PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessionData session) + { + var data = new Dictionary(); + data.Add("payer_id", session.PayerId); + + var result = CallApi("POST", "/v1/payments/payment/{0}/execute".FormatInvariant(session.PaymentId), session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + + //Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + result.Json.ToString()); + } + + return result; + } + + public PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData session, RefundPaymentRequest request) + { + var data = new Dictionary(); + var store = _services.StoreService.GetStoreById(request.Order.StoreId); + var isSale = request.Order.AuthorizationTransactionResult.Contains("(sale)"); + + var path = "/v1/payments/{0}/{1}/refund".FormatInvariant(isSale ? "sale" : "capture", request.Order.CaptureTransactionId); + + var amount = new Dictionary(); + amount.Add("total", request.AmountToRefund.FormatInvariant()); + amount.Add("currency", store.PrimaryStoreCurrency.CurrencyCode); + + data.Add("amount", amount); + + var result = CallApi("POST", path, session.AccessToken, settings, data.Any() ? JsonConvert.SerializeObject(data) : null); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + //Logger.InsertLog(LogLevel.Information, "PayPal Refund", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + (result.Json != null ? result.Json.ToString() : "")); + + return result; + } + + public PayPalResponse Capture(PayPalApiSettingsBase settings, PayPalSessionData session, CapturePaymentRequest request) + { + var data = new Dictionary(); + //var isAuthorize = request.Order.AuthorizationTransactionCode.IsCaseInsensitiveEqual("authorize"); + + var path = "/v1/payments/authorization/{0}/capture".FormatInvariant(request.Order.AuthorizationTransactionId); + + var store = _services.StoreService.GetStoreById(request.Order.StoreId); + + var amount = new Dictionary(); + amount.Add("total", request.Order.OrderTotal.FormatInvariant()); + amount.Add("currency", store.PrimaryStoreCurrency.CurrencyCode); + + data.Add("amount", amount); + + var result = CallApi("POST", path, session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + return result; + } + + public PayPalResponse Void(PayPalApiSettingsBase settings, PayPalSessionData session, VoidPaymentRequest request) + { + var path = "/v1/payments/authorization/{0}/void".FormatInvariant(request.Order.AuthorizationTransactionId); + + var result = CallApi("POST", path, session.AccessToken, settings, null); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + return result; + } + + public PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session, Store store) + { + PayPalResponse result; + var name = store.Name; + var logo = _pictureService.Value.GetPictureById(store.LogoPictureId); + var path = "/v1/payment-experience/web-profiles"; + + var data = new Dictionary(); + var presentation = new Dictionary(); + var inpuFields = new Dictionary(); + + // find existing profile id, only one profile per profile name possible + if (settings.ExperienceProfileId.IsEmpty()) + { + result = CallApi("GET", path, session.AccessToken, settings, null); + if (result.Success && result.Json != null) + { + foreach (var profile in result.Json) + { + var profileName = (string)profile.name; + if (profileName.IsCaseInsensitiveEqual(name)) + { + settings.ExperienceProfileId = (string)profile.id; + break; + } + } + } + } + + presentation.Add("brand_name", name); + presentation.Add("locale_code", _services.WorkContext.WorkingLanguage.UniqueSeoCode.EmptyNull().ToUpper()); + + if (logo != null) + presentation.Add("logo_image", _pictureService.Value.GetPictureUrl(logo, showDefaultPicture: false, storeLocation: store.Url)); + + inpuFields.Add("allow_note", false); + inpuFields.Add("no_shipping", 0); + inpuFields.Add("address_override", 1); + + data.Add("name", name); + data.Add("presentation", presentation); + data.Add("input_fields", inpuFields); + + if (settings.ExperienceProfileId.HasValue()) + path = string.Concat(path, "/", HttpUtility.UrlPathEncode(settings.ExperienceProfileId)); + + result = CallApi(settings.ExperienceProfileId.HasValue() ? "PUT" : "POST", path, session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success) + { + if (result.Json != null) + result.Id = (string)result.Json.id; + else + result.Id = settings.ExperienceProfileId; + } + + return result; + } + + public PayPalResponse DeleteCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session) + { + var result = CallApi("DELETE", "/v1/payment-experience/web-profiles/" + settings.ExperienceProfileId, session.AccessToken, settings, null); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + return result; + } + + public PayPalResponse CreateWebhook(PayPalApiSettingsBase settings, PayPalSessionData session, string url) + { + var data = new Dictionary(); + var events = new List>(); + + events.Add(new Dictionary { { "name", "PAYMENT.AUTHORIZATION.VOIDED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.CAPTURE.COMPLETED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.CAPTURE.DENIED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.CAPTURE.PENDING" } }); + events.Add(new Dictionary { { "name", "PAYMENT.CAPTURE.REFUNDED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.CAPTURE.REVERSED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.SALE.COMPLETED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.SALE.DENIED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.SALE.PENDING" } }); + events.Add(new Dictionary { { "name", "PAYMENT.SALE.REFUNDED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.SALE.REVERSED" } }); + + data.Add("url", url); + data.Add("event_types", events); + + var result = CallApi("POST", "/v1/notifications/webhooks", session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + return result; + } + + public PayPalResponse DeleteWebhook(PayPalApiSettingsBase settings, PayPalSessionData session) + { + var result = CallApi("DELETE", "/v1/notifications/webhooks/" + settings.WebhookId, session.AccessToken, settings, null); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + return result; + } + + /// return 503 (HttpStatusCode.ServiceUnavailable) to ask paypal to resend it at later time again + public HttpStatusCode ProcessWebhook( + PayPalApiSettingsBase settings, + NameValueCollection headers, + string rawJson, + string providerSystemName) + { + if (rawJson.IsEmpty()) + return HttpStatusCode.OK; + + dynamic json = JObject.Parse(rawJson); + var eventType = (string)json.event_type; + + //foreach (var key in headers.AllKeys)"{0}: {1}".FormatInvariant(key, headers[key]).Dump(); + //string data = JsonConvert.SerializeObject(json, Formatting.Indented);data.Dump(); + + + // validating against PayPal SDK failing using sandbox, so better we do not use it: + //var apiContext = new global::PayPal.Api.APIContext + //{ + // AccessToken = "I do not have one here", + // Config = new Dictionary + // { + // { "mode", settings.UseSandbox ? "sandbox" : "live" }, + // { "clientId", settings.ClientId }, + // { "clientSecret", settings.Secret }, + // { "webhook.id", setting.WebhookId }, + // } + //}; + //var result = global::PayPal.Api.WebhookEvent.ValidateReceivedEvent(apiContext, headers, rawJson, webhookId); + //} + + var paymentId = (string)json.resource.parent_payment; + if (paymentId.IsEmpty()) + { + LogError(null, T("Plugins.SmartStore.PayPal.FoundOrderForPayment", 0, "".NaIfEmpty()), JsonConvert.SerializeObject(json, Formatting.Indented), isWarning: true); + return HttpStatusCode.OK; + } + + var orders = _orderRepository.Value.Table + .Where(x => x.PaymentMethodSystemName == providerSystemName && x.AuthorizationTransactionCode == paymentId) + .ToList(); + + if (orders.Count != 1) + { + LogError(null, T("Plugins.SmartStore.PayPal.FoundOrderForPayment", orders.Count, paymentId), JsonConvert.SerializeObject(json, Formatting.Indented), isWarning: true); + return HttpStatusCode.OK; + } + + var order = orders.First(); + var store = _services.StoreService.GetStoreById(order.StoreId); + + var total = decimal.Zero; + var currency = (string)json.resource.amount.currency; + var primaryCurrency = store.PrimaryStoreCurrency.CurrencyCode; + + if (!primaryCurrency.IsCaseInsensitiveEqual(currency)) + { + LogError(null, T("Plugins.SmartStore.PayPal.CurrencyNotEqual", currency.NaIfEmpty(), primaryCurrency), JsonConvert.SerializeObject(json, Formatting.Indented), isWarning: true); + return HttpStatusCode.OK; + } + + eventType = eventType.Substring(eventType.LastIndexOf('.') + 1); + + var newPaymentStatus = GetPaymentStatus(eventType, "authorization", order.PaymentStatus); + + var isValidTotal = decimal.TryParse((string)json.resource.amount.total, NumberStyles.Currency, CultureInfo.InvariantCulture, out total); + + if (newPaymentStatus == PaymentStatus.Refunded && (Math.Abs(order.OrderTotal) - Math.Abs(total)) > decimal.Zero) + { + newPaymentStatus = PaymentStatus.PartiallyRefunded; + } + + switch (newPaymentStatus) + { + case PaymentStatus.Pending: + break; + case PaymentStatus.Authorized: + if (_orderProcessingService.CanMarkOrderAsAuthorized(order)) + _orderProcessingService.MarkAsAuthorized(order); + break; + case PaymentStatus.Paid: + if (_orderProcessingService.CanMarkOrderAsPaid(order)) + _orderProcessingService.MarkOrderAsPaid(order); + break; + case PaymentStatus.Refunded: + if (_orderProcessingService.CanRefundOffline(order)) + _orderProcessingService.RefundOffline(order); + break; + case PaymentStatus.PartiallyRefunded: + if (_orderProcessingService.CanPartiallyRefundOffline(order, Math.Abs(total))) + _orderProcessingService.PartiallyRefundOffline(order, Math.Abs(total)); + break; + case PaymentStatus.Voided: + if (_orderProcessingService.CanVoidOffline(order)) + _orderProcessingService.VoidOffline(order); + break; + } + + AddOrderNote(settings, order, (string)ToInfoString(json), true); + + return HttpStatusCode.OK; + } + } + + + public class PayPalResponse + { + public bool Success { get; set; } + public dynamic Json { get; set; } + public string ErrorMessage { get; set; } + public string Id { get; set; } + } + + public class PayPalSessionData + { + public PayPalSessionData() + { + OrderGuid = Guid.NewGuid(); + } + + public string AccessToken { get; set; } + public DateTime TokenExpiration { get; set; } + public string PaymentId { get; set; } + public string PayerId { get; set; } + public string ApprovalUrl { get; set; } + public Guid OrderGuid { get; private set; } + public PayPalPaymentInstruction PaymentInstruction { get; set; } + } + + public class PayPalPaymentInstruction + { + public string ReferenceNumber { get; set; } + public string Type { get; set; } + public decimal Amount { get; set; } + public string AmountCurrencyCode { get; set; } + public DateTime? DueDate { get; set; } + public string Note { get; set; } + public string Link { get; set; } + + public RecipientBankingInstruction RecipientBanking { get; set; } + + public bool IsManualBankTransfer + { + get { return Type.IsCaseInsensitiveEqual("MANUAL_BANK_TRANSFER"); } + } + + public bool IsPayUponInvoice + { + get { return Type.IsCaseInsensitiveEqual("PAY_UPON_INVOICE"); } + } + + public class RecipientBankingInstruction + { + public string BankName { get; set; } + public string AccountHolderName { get; set; } + public string AccountNumber { get; set; } + public string RoutingNumber { get; set; } + public string Iban { get; set; } + public string Bic { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalStandardCore.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalStandardCore.cs deleted file mode 100644 index eb7d70877d..0000000000 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalStandardCore.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Globalization; - -namespace SmartStore.PayPal.Services -{ - public class PayPalLineItem : ICloneable - { - public PayPalItemType Type { get; set; } - public string Name { get; set; } - public int Quantity { get; set; } - public decimal Amount { get; set; } - - public decimal AmountRounded - { - get - { - return Math.Round(Amount, 2); - } - } - - public PayPalLineItem Clone() - { - var item = new PayPalLineItem() - { - Type = this.Type, - Name = this.Name, - Quantity = this.Quantity, - Amount = this.Amount - }; - return item; - } - - object ICloneable.Clone() - { - return this.Clone(); - } - } - - - public enum PayPalItemType : int - { - CartItem = 0, - CheckoutAttribute, - Shipping, - PaymentFee, - Tax - } -} diff --git a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs index 9e3adea8da..8109422c68 100644 --- a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs +++ b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs @@ -1,20 +1,37 @@ +using System.Collections.Generic; +using System.Net; using SmartStore.Core.Configuration; -using SmartStore.PayPal; +using SmartStore.PayPal.Services; namespace SmartStore.PayPal.Settings { public abstract class PayPalSettingsBase { - public bool UseSandbox { get; set; } - /// - /// Gets or sets a value indicating whether to "additional fee" is specified as percentage. true - percentage, false - fixed value. - /// - public bool AdditionalFeePercentage { get; set; } - /// - /// Additional fee - /// + public PayPalSettingsBase() + { + SecurityProtocol = SecurityProtocolType.Tls12; + IpnChangesPaymentStatus = true; + AddOrderNotes = true; + } + + public SecurityProtocolType? SecurityProtocol { get; set; } + + public bool UseSandbox { get; set; } + + public bool AddOrderNotes { get; set; } + + /// + /// Gets or sets a value indicating whether to "additional fee" is specified as percentage. true - percentage, false - fixed value. + /// + public bool AdditionalFeePercentage { get; set; } + public decimal AdditionalFee { get; set; } - } + + /// + /// Gets or sets a value indicating whether an IPN should change the payment status + /// + public bool IpnChangesPaymentStatus { get; set; } + } public abstract class PayPalApiSettingsBase : PayPalSettingsBase { @@ -22,14 +39,35 @@ public abstract class PayPalApiSettingsBase : PayPalSettingsBase public string ApiAccountName { get; set; } public string ApiAccountPassword { get; set; } public string Signature { get; set; } + + /// + /// PayPal client id + /// + public string ClientId { get; set; } + + /// + /// PayPal secret + /// + public string Secret { get; set; } + + /// + /// PayPal experience profile id + /// + public string ExperienceProfileId { get; set; } + + /// + /// PayPal webhook id + /// + public string WebhookId { get; set; } } + public class PayPalDirectPaymentSettings : PayPalApiSettingsBase, ISettings { public PayPalDirectPaymentSettings() { + UseSandbox = true; TransactMode = TransactMode.Authorize; - UseSandbox = true; } } @@ -44,12 +82,17 @@ public PayPalExpressPaymentSettings() /// /// Determines whether the checkout button is displayed beneath the cart /// - public bool DisplayCheckoutButton { get; set; } + //public bool DisplayCheckoutButton { get; set; } - /// - /// Determines whether the shipment address has to be confirmed by PayPal - /// - public bool ConfirmedShipment { get; set; } + /// + /// Specifies whether to display the checkout button in mini shopping cart + /// + public bool ShowButtonInMiniShoppingCart { get; set; } + + /// + /// Determines whether the shipment address has to be confirmed by PayPal + /// + public bool ConfirmedShipment { get; set; } /// /// Determines whether the shipment address is transmitted to PayPal @@ -67,12 +110,34 @@ public PayPalExpressPaymentSettings() public decimal DefaultShippingPrice { get; set; } } - public class PayPalStandardPaymentSettings : PayPalSettingsBase, ISettings + public class PayPalPlusPaymentSettings : PayPalApiSettingsBase, ISettings + { + public PayPalPlusPaymentSettings() + { + UseSandbox = true; + } + + /// + /// Specifies other payment methods to be offered in payment wall + /// + public List ThirdPartyPaymentMethods { get; set; } + + /// + /// Specifies whether to display the logo of a third party payment method + /// + public bool DisplayPaymentMethodLogo { get; set; } + + /// + /// Specifies whether to display the description of a third party payment method + /// + public bool DisplayPaymentMethodDescription { get; set; } + } + + public class PayPalStandardPaymentSettings : PayPalSettingsBase, ISettings { public PayPalStandardPaymentSettings() { UseSandbox = true; - PdtValidateOrderTotal = true; EnableIpn = true; } @@ -80,22 +145,18 @@ public PayPalStandardPaymentSettings() public string PdtToken { get; set; } public bool PassProductNamesAndTotals { get; set; } public bool PdtValidateOrderTotal { get; set; } + public bool PdtValidateOnlyWarn { get; set; } public bool EnableIpn { get; set; } public string IpnUrl { get; set; } } - /// - /// Represents payment processor transaction mode - /// - public enum TransactMode : int + + /// + /// Represents payment processor transaction mode + /// + public enum TransactMode { - /// - /// Authorize - /// Authorize = 1, - /// - /// Authorize and capture - /// AuthorizeAndCapture = 2 } } diff --git a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj index 1321f66c11..e82c4a4097 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -1,5 +1,5 @@  - + @@ -20,7 +20,7 @@ Properties SmartStore.PayPal SmartStore.PayPal - v4.5 + v4.5.1 512 ..\..\ @@ -34,6 +34,7 @@ + true @@ -55,52 +56,60 @@ false - - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - - ..\..\packages\Autofac.Mvc5.3.3.1\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll - - ..\..\packages\FluentValidation.5.0.0.1\lib\Net40\FluentValidation.dll + + ..\..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.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 + False + - + + + + + + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - + @@ -109,23 +118,33 @@ Properties\AssemblyVersionInfo.cs + + + + + + + + + + - - + + + - @@ -134,6 +153,7 @@ Settings.settings + @@ -165,6 +185,9 @@ + + PreserveNewest + PreserveNewest @@ -176,9 +199,6 @@ PreserveNewest - - Always - Always @@ -261,6 +281,21 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + Designer diff --git a/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs b/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs new file mode 100644 index 0000000000..b7355b4299 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs @@ -0,0 +1,33 @@ +using System; +using FluentValidation; +using SmartStore.PayPal.Models; +using SmartStore.Services.Localization; +using SmartStore.Web.Framework.Validators; + +namespace SmartStore.PayPal.Validators +{ + public class PayPalPlusConfigValidator : SmartValidatorBase + { + public PayPalPlusConfigValidator(ILocalizationService localize, Func addRule) + { + if (addRule("ClientId")) + { + RuleFor(x => x.ClientId).NotEmpty() + .WithMessage(localize.GetResource("Plugins.SmartStore.PayPal.ValidateClientIdAndSecret")); + } + + if (addRule("Secret")) + { + RuleFor(x => x.Secret).NotEmpty() + .WithMessage(localize.GetResource("Plugins.SmartStore.PayPal.ValidateClientIdAndSecret")); + } + + if (addRule("ThirdPartyPaymentMethods")) + { + RuleFor(x => x.ThirdPartyPaymentMethods) + .Must(x => x == null || x.Count <= 5) + .WithMessage(localize.GetResource("Plugins.Payments.PayPalPlus.ValidateThirdPartyPaymentMethods")); + } + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml index febf02c057..128ae981b6 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml @@ -6,21 +6,19 @@ Html.AddCssFileParts(true, "~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css"); } - - - - - -
      -
      - - @Html.Raw(T("Plugins.Payments.PayPalDirect.AdminInstruction")) -
      -
      - - - -
      +
      +
      +
      + + @Html.Raw(T("Plugins.Payments.PayPalDirect.AdminInstruction")) +
      +
      +
      + + PayPal + +
      +
      @Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) @@ -36,6 +34,14 @@ @Html.DropDownListFor(model => model.TransactMode, Model.TransactModeValues)
    + @Html.SmartLabelFor(model => model.SecurityProtocol) + + @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified")) +
    @Html.SmartLabelFor(model => model.UseSandbox) @@ -73,6 +79,15 @@ @Html.ValidationMessageFor(model => model.Signature)
    + @Html.SmartLabelFor(model => model.IpnChangesPaymentStatus) + + @Html.SettingEditorFor(model => model.IpnChangesPaymentStatus) + @Html.ValidationMessageFor(model => model.IpnChangesPaymentStatus) +
    @Html.SmartLabelFor(model => model.AdditionalFee) diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/PaymentInfo.Mobile.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/PaymentInfo.Mobile.cshtml index 66dd7a45af..aeb9300a95 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/PaymentInfo.Mobile.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/PaymentInfo.Mobile.cshtml @@ -6,7 +6,7 @@ @using SmartStore.Web.Framework; @Html.SmartLabelFor(model => model.CreditCardTypes, false) -@Html.DropDownListFor(model => model.CreditCardType, Model.CreditCardTypes) +@Html.DropDownListFor(model => model.CreditCardType, Model.CreditCardTypes, new { data_native_menu = "false" }) @Html.SmartLabelFor(model => model.CardholderName, false) @Html.TextBoxFor(model => model.CardholderName, new { autocomplete = "off" }) @Html.ValidationMessageFor(model => model.CardholderName) @@ -16,8 +16,8 @@ @Html.SmartLabelFor(model => model.ExpireMonth, false)
    - @Html.DropDownListFor(model => model.ExpireMonth, Model.ExpireMonths) - @Html.DropDownListFor(model => model.ExpireYear, Model.ExpireYears) + @Html.DropDownListFor(model => model.ExpireMonth, Model.ExpireMonths, new { data_native_menu = "false" }) + @Html.DropDownListFor(model => model.ExpireYear, Model.ExpireYears, new { data_native_menu = "false" })
    @Html.SmartLabelFor(model => model.CardCode, false) diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml index 3ab559894a..5738f2df4a 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml @@ -7,21 +7,19 @@ Html.AddCssFileParts(true, "~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css"); } - - - - - -
    -
    - - @Html.Raw(T("Plugins.Payments.PayPalExpress.AdminInstruction")) -
    -
    - - - -
    +
    +
    +
    + + @Html.Raw(T("Plugins.Payments.PayPalExpress.AdminInstruction")) +
    +
    +
    + + PayPal + +
    +
    @Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) @@ -37,6 +35,14 @@ @Html.DropDownListFor(model => model.TransactMode, Model.TransactModeValues)
    + @Html.SmartLabelFor(model => model.SecurityProtocol) + + @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified")) +
    @Html.SmartLabelFor(model => model.UseSandbox) @@ -74,43 +80,33 @@ @Html.ValidationMessageFor(model => model.Signature)
    - @Html.SmartLabelFor(model => model.AdditionalFee) - - @Html.SettingEditorFor(model => model.AdditionalFee) - @Html.ValidationMessageFor(model => model.AdditionalFee) -
    - @Html.SmartLabelFor(model => model.AdditionalFeePercentage) - - @Html.SettingEditorFor(model => model.AdditionalFeePercentage) - @Html.ValidationMessageFor(model => model.AdditionalFeePercentage) -
    - @Html.SmartLabelFor(model => model.DisplayCheckoutButton) - - @Html.SettingEditorFor(model => model.DisplayCheckoutButton) - @Html.ValidationMessageFor(model => model.DisplayCheckoutButton) -
    - @Html.SmartLabelFor(model => model.ConfirmedShipment) - - @Html.SettingEditorFor(model => model.ConfirmedShipment) - @Html.ValidationMessageFor(model => model.ConfirmedShipment) -
    + @Html.SmartLabelFor(model => model.CallbackTimeout) + + @Html.SettingEditorFor(model => model.CallbackTimeout) + @Html.ValidationMessageFor(model => model.CallbackTimeout) +
    + @Html.SmartLabelFor(model => model.IpnChangesPaymentStatus) + + @Html.SettingEditorFor(model => model.IpnChangesPaymentStatus) + @Html.ValidationMessageFor(model => model.IpnChangesPaymentStatus) +
    + @Html.SmartLabelFor(model => model.ShowButtonInMiniShoppingCart) + + @Html.SettingEditorFor(model => model.ShowButtonInMiniShoppingCart) + @Html.ValidationMessageFor(model => model.ShowButtonInMiniShoppingCart) +
    @Html.SmartLabelFor(model => model.NoShipmentAddress) @@ -120,15 +116,15 @@ @Html.ValidationMessageFor(model => model.NoShipmentAddress)
    - @Html.SmartLabelFor(model => model.CallbackTimeout) - - @Html.SettingEditorFor(model => model.CallbackTimeout) - @Html.ValidationMessageFor(model => model.CallbackTimeout) -
    + @Html.SmartLabelFor(model => model.ConfirmedShipment) + + @Html.SettingEditorFor(model => model.ConfirmedShipment) + @Html.ValidationMessageFor(model => model.ConfirmedShipment) +
    @Html.SmartLabelFor(model => model.DefaultShippingPrice) @@ -138,9 +134,29 @@ @Html.ValidationMessageFor(model => model.DefaultShippingPrice)
    + @Html.SmartLabelFor(model => model.AdditionalFee) + + @Html.SettingEditorFor(model => model.AdditionalFee) + @Html.ValidationMessageFor(model => model.AdditionalFee) +
    + @Html.SmartLabelFor(model => model.AdditionalFeePercentage) + + @Html.SettingEditorFor(model => model.AdditionalFeePercentage) + @Html.ValidationMessageFor(model => model.AdditionalFeePercentage) +
      + +   + diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/MiniShoppingCart.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/MiniShoppingCart.cshtml new file mode 100644 index 0000000000..3296cbd446 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/MiniShoppingCart.cshtml @@ -0,0 +1,10 @@ +@model SmartStore.PayPal.Models.PayPalExpressPaymentInfoModel +
    +
    + @T("Plugins.Payments.PayPalExpress.SelectionText") +
    +
    + + +
    +
    \ 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 new file mode 100644 index 0000000000..129922ca27 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -0,0 +1,207 @@ +@using SmartStore.PayPal; +@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")) +
    +
    +
    + + PayPal + +
    +
    + +@Html.Action("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") + + + @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) +
    +
    +
    + @Model.ConfigGroups.SafeGet(2) +
    +
    +
    + @Html.SmartLabelFor(model => model.ThirdPartyPaymentMethods) + + @Html.SettingOverrideCheckbox(model => model.ThirdPartyPaymentMethods) + @Html.ListBoxFor(x => x.ThirdPartyPaymentMethods, + new MultiSelectList(Model.AvailableThirdPartyPaymentMethods, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(model => model.ThirdPartyPaymentMethods) +
    + @Html.SmartLabelFor(model => model.DisplayPaymentMethodLogo) + + @Html.SettingEditorFor(model => model.DisplayPaymentMethodLogo) + @Html.ValidationMessageFor(model => model.DisplayPaymentMethodLogo) +
    + @Html.SmartLabelFor(model => model.DisplayPaymentMethodDescription) + + @Html.SettingEditorFor(model => model.DisplayPaymentMethodDescription) + @Html.ValidationMessageFor(model => model.DisplayPaymentMethodDescription) +
    + @Html.SmartLabelFor(model => model.AdditionalFee) + + @Html.SettingEditorFor(model => model.AdditionalFee) + @Html.ValidationMessageFor(model => model.AdditionalFee) +
    + @Html.SmartLabelFor(model => model.AdditionalFeePercentage) + + @Html.SettingEditorFor(model => model.AdditionalFeePercentage) + @Html.ValidationMessageFor(model => model.AdditionalFeePercentage) +
    +   + + +
    +} \ 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 new file mode 100644 index 0000000000..266ae25887 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml @@ -0,0 +1,66 @@ +@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 new file mode 100644 index 0000000000..461b02a3ab --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml @@ -0,0 +1,69 @@ +@using SmartStore.Web.Models.Checkout; +@using SmartStore.PayPal.Models; +@model PayPalPlusCheckoutModel +@{ + Layout = "~/Views/Shared/_Checkout.cshtml"; + Html.AddTitleParts(T("PageTitle.Checkout").Text); +} +@section orderProgress +{ + @Html.Action("CheckoutProgress", "Checkout", new { step = CheckoutProgressStep.Payment }) +} + +@Html.Partial("PaymentWallScripting") + +
    +
    +

    @T("Checkout.SelectPaymentMethod")

    +
    +
    + @Html.Widget("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) + { + + } + +
    + +
    + +  @T("Common.Back") + + + +
    + + @Html.Widget("checkout_payment_method_bottom") +
    +
    \ 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 new file mode 100644 index 0000000000..c1f2cdf6f0 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml @@ -0,0 +1,117 @@ +@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 561f35a72d..5846c1ed43 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml @@ -5,27 +5,33 @@ Layout = ""; } - - - - - -
    -
    - - @Html.Raw(T("Plugins.Payments.PayPalStandard.AdminInstruction")) -
    -
    - - - -
    +
    +
    +
    + + @Html.Raw(T("Plugins.Payments.PayPalStandard.AdminInstruction")) +
    +
    +
    + + PayPal + +
    +
    @Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) @using (Html.BeginForm()) { + + + + + + + + - + + + + + @@ -108,7 +132,7 @@ @Html.ValidationMessageFor(model => model.IpnUrl) - + @@ -145,7 +146,7 @@ data: $(this.form).serialize(), dataType: 'json', success: function (data) { - alert('Saved'); + EventBroker.publish('message', { title: '@T("Admin.Common.DataEditSuccess")', type: 'success' }); }, error: function (xhr, ajaxOptions, thrownError) { alert('Failed to add record.'); diff --git a/src/Plugins/SmartStore.Shipping/Views/Web.config b/src/Plugins/SmartStore.Shipping/Views/Web.config index 31dc8df754..e9d36a3c51 100644 --- a/src/Plugins/SmartStore.Shipping/Views/Web.config +++ b/src/Plugins/SmartStore.Shipping/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.Shipping/packages.config b/src/Plugins/SmartStore.Shipping/packages.config index 4acdbeae52..1a1ea4e95b 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 188ce3a0f8..c0f82db177 100644 --- a/src/Plugins/SmartStore.Shipping/web.config +++ b/src/Plugins/SmartStore.Shipping/web.config @@ -1,116 +1,116 @@ - + - - + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs b/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs index 8a98764bf9..a147a1855d 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs @@ -3,15 +3,16 @@ using System.Web.Routing; using SmartStore.Core; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Localization; using SmartStore.Core.Plugins; -using SmartStore.ShippingByWeight.Data; -using SmartStore.ShippingByWeight.Data.Migrations; -using SmartStore.ShippingByWeight.Services; using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Localization; using SmartStore.Services.Shipping; using SmartStore.Services.Shipping.Tracking; +using SmartStore.ShippingByWeight.Data; +using SmartStore.ShippingByWeight.Data.Migrations; +using SmartStore.ShippingByWeight.Services; namespace SmartStore.ShippingByWeight { @@ -27,11 +28,12 @@ public class ByWeightShippingComputationMethod : BasePlugin, IShippingRateComput private readonly ShippingByWeightObjectContext _objectContext; private readonly ILocalizationService _localizationService; private readonly IPriceFormatter _priceFormatter; - private readonly ICommonServices _commonServices; + private readonly ICommonServices _services; #endregion #region Ctor + public ByWeightShippingComputationMethod(IShippingService shippingService, IStoreContext storeContext, IShippingByWeightService shippingByWeightService, @@ -40,7 +42,7 @@ public ByWeightShippingComputationMethod(IShippingService shippingService, ShippingByWeightObjectContext objectContext, ILocalizationService localizationService, IPriceFormatter priceFormatter, - ICommonServices commonServices) + ICommonServices services) { this._shippingService = shippingService; this._storeContext = storeContext; @@ -50,17 +52,22 @@ public ByWeightShippingComputationMethod(IShippingService shippingService, this._objectContext = objectContext; this._localizationService = localizationService; this._priceFormatter = priceFormatter; - this._commonServices = commonServices; - } - #endregion + this._services = services; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + #endregion - #region Utilities + #region Utilities - private decimal? GetRate(decimal subTotal, decimal weight, int shippingMethodId, int storeId, int countryId) + private decimal? GetRate(decimal subTotal, decimal weight, int shippingMethodId, int storeId, int countryId, string zip) { decimal? shippingTotal = null; - var shippingByWeightRecord = _shippingByWeightService.FindRecord(shippingMethodId, storeId, countryId, weight); + var shippingByWeightRecord = _shippingByWeightService.FindRecord(shippingMethodId, storeId, countryId, weight, zip); if (shippingByWeightRecord == null) { if (_shippingByWeightSettings.LimitMethodsToCreated) @@ -109,47 +116,50 @@ public ByWeightShippingComputationMethod(IShippingService shippingService, /// /// Gets available shipping options /// - /// A request for getting shipping options + /// A request for getting shipping options /// Represents a response of getting shipping rate options - public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest getShippingOptionRequest) + public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest request) { - if (getShippingOptionRequest == null) + if (request == null) throw new ArgumentNullException("getShippingOptionRequest"); var response = new GetShippingOptionResponse(); - if (getShippingOptionRequest.Items == null || getShippingOptionRequest.Items.Count == 0) + if (request.Items == null || request.Items.Count == 0) { - response.AddError("No shipment items"); + response.AddError(T("Admin.System.Warnings.NoShipmentItems")); return response; } - - int storeId = _storeContext.CurrentStore.Id; + + int storeId = request.StoreId > 0 ? request.StoreId : _storeContext.CurrentStore.Id; decimal subTotal = decimal.Zero; int countryId = 0; + string zip = null; - if (getShippingOptionRequest.ShippingAddress != null) + if (request.ShippingAddress != null) { - countryId = getShippingOptionRequest.ShippingAddress.CountryId ?? 0; + countryId = request.ShippingAddress.CountryId ?? 0; + zip = request.ShippingAddress.ZipPostalCode; } - foreach (var shoppingCartItem in getShippingOptionRequest.Items) + foreach (var shoppingCartItem in request.Items) { if (shoppingCartItem.Item.IsFreeShipping || !shoppingCartItem.Item.IsShipEnabled) continue; subTotal += _priceCalculationService.GetSubTotal(shoppingCartItem, true); } - decimal weight = _shippingService.GetShoppingCartTotalWeight(getShippingOptionRequest.Items); + decimal weight = _shippingService.GetShoppingCartTotalWeight(request.Items); - var shippingMethods = _shippingService.GetAllShippingMethods(countryId); + var shippingMethods = _shippingService.GetAllShippingMethods(request); foreach (var shippingMethod in shippingMethods) { - var record = _shippingByWeightService.FindRecord(shippingMethod.Id, storeId, countryId, weight); + var record = _shippingByWeightService.FindRecord(shippingMethod.Id, storeId, countryId, weight, zip); - decimal? rate = GetRate(subTotal, weight, shippingMethod.Id, storeId, countryId); + decimal? rate = GetRate(subTotal, 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) @@ -248,6 +258,11 @@ public IShipmentTracker ShipmentTracker } } + public bool IsActive + { + get { return true; } + } + #endregion } } diff --git a/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs b/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs index 6d7c63bf12..383d77dfa2 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs @@ -1,17 +1,15 @@ -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Web.Mvc; +using System.Web.Mvc; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Directory; +using SmartStore.Services; +using SmartStore.Services.Directory; +using SmartStore.Services.Shipping; using SmartStore.ShippingByWeight.Domain; using SmartStore.ShippingByWeight.Models; using SmartStore.ShippingByWeight.Services; -using SmartStore.Services.Configuration; -using SmartStore.Services.Directory; -using SmartStore.Services.Shipping; -using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.ShippingByWeight.Controllers @@ -21,61 +19,67 @@ namespace SmartStore.ShippingByWeight.Controllers public class ShippingByWeightController : PluginControllerBase { private readonly IShippingService _shippingService; - private readonly IStoreService _storeService; private readonly ICountryService _countryService; private readonly ShippingByWeightSettings _shippingByWeightSettings; private readonly IShippingByWeightService _shippingByWeightService; - private readonly ISettingService _settingService; - - private readonly ICurrencyService _currencyService; - private readonly CurrencySettings _currencySettings; private readonly IMeasureService _measureService; private readonly MeasureSettings _measureSettings; private readonly AdminAreaSettings _adminAreaSettings; - - public ShippingByWeightController(IShippingService shippingService, - IStoreService storeService, ICountryService countryService, ShippingByWeightSettings shippingByWeightSettings, - IShippingByWeightService shippingByWeightService, ISettingService settingService, - ICurrencyService currencyService, CurrencySettings currencySettings, - IMeasureService measureService, MeasureSettings measureSettings, - AdminAreaSettings adminAreaSettings) + private readonly ICommonServices _services; + + public ShippingByWeightController( + IShippingService shippingService, + ICountryService countryService, + ShippingByWeightSettings shippingByWeightSettings, + IShippingByWeightService shippingByWeightService, + IMeasureService measureService, + MeasureSettings measureSettings, + AdminAreaSettings adminAreaSettings, + ICommonServices services) { this._shippingService = shippingService; - this._storeService = storeService; this._countryService = countryService; this._shippingByWeightSettings = shippingByWeightSettings; this._shippingByWeightService = shippingByWeightService; - this._settingService = settingService; - - this._currencyService = currencyService; - this._currencySettings = currencySettings; this._measureService = measureService; this._measureSettings = measureSettings; this._adminAreaSettings = adminAreaSettings; + this._services = services; } public ActionResult Configure() { var shippingMethods = _shippingService.GetAllShippingMethods(); - if (shippingMethods.Count == 0) - return Content("No shipping methods can be loaded"); + if (shippingMethods.Count == 0) + { + return Content(T("Admin.Configuration.Shipping.Methods.NoMethodsLoaded")); + } var model = new ShippingByWeightListModel(); - foreach (var sm in shippingMethods) - model.AvailableShippingMethods.Add(new SelectListItem() { Text = sm.Name, Value = sm.Id.ToString() }); + var countries = _countryService.GetAllCountries(true); + var allStores = _services.StoreService.GetAllStores(); + + foreach (var sm in shippingMethods) + { + model.AvailableShippingMethods.Add(new SelectListItem { Text = sm.Name, Value = sm.Id.ToString() }); + } //stores - model.AvailableStores.Add(new SelectListItem() { Text = "*", Value = "0" }); - foreach (var store in _storeService.GetAllStores()) - model.AvailableStores.Add(new SelectListItem() { Text = store.Name, Value = store.Id.ToString() }); - - model.AvailableCountries.Add(new SelectListItem() { Text = "*", Value = "0" }); - var countries = _countryService.GetAllCountries(true); - foreach (var c in countries) - model.AvailableCountries.Add(new SelectListItem() { Text = c.Name, Value = c.Id.ToString() }); + model.AvailableStores.Add(new SelectListItem { Text = "*", Value = "0" }); + foreach (var store in allStores) + { + model.AvailableStores.Add(new SelectListItem { Text = store.Name, Value = store.Id.ToString() }); + } + + model.AvailableCountries.Add(new SelectListItem { Text = "*", Value = "0" }); + foreach (var c in countries) + { + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString() }); + } + model.LimitMethodsToCreated = _shippingByWeightSettings.LimitMethodsToCreated; model.CalculatePerWeightUnit = _shippingByWeightSettings.CalculatePerWeightUnit; - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; model.BaseWeightIn = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name; model.GridPageSize = _adminAreaSettings.GridPageSize; @@ -107,6 +111,7 @@ public ActionResult RateUpdate(ShippingByWeightModel model, GridCommand command) sbw.From = model.From; sbw.To = model.To; sbw.UsePercentage = model.UsePercentage; + sbw.Zip = model.Zip == "*" ? null : model.Zip; sbw.ShippingChargeAmount = model.ShippingChargeAmount; sbw.ShippingChargePercentage = model.ShippingChargePercentage; sbw.SmallQuantitySurcharge = model.SmallQuantitySurcharge; @@ -139,6 +144,7 @@ public ActionResult AddShippingByWeightRecord(ShippingByWeightListModel model) StoreId = model.AddStoreId, ShippingMethodId = model.AddShippingMethodId, CountryId = model.AddCountryId, + Zip = model.AddZip, //StateProvinceId = 0, From = model.AddFrom, To = model.AddTo, @@ -160,7 +166,7 @@ public ActionResult SaveGeneralSettings(ShippingByWeightListModel model) //save settings _shippingByWeightSettings.LimitMethodsToCreated = model.LimitMethodsToCreated; _shippingByWeightSettings.CalculatePerWeightUnit = model.CalculatePerWeightUnit; - _settingService.SaveSetting(_shippingByWeightSettings); + _services.Settings.SaveSetting(_shippingByWeightSettings); return Configure(); } diff --git a/src/Plugins/SmartStore.ShippingByWeight/Data/Migrations/201506290747117_ZipCode.Designer.cs b/src/Plugins/SmartStore.ShippingByWeight/Data/Migrations/201506290747117_ZipCode.Designer.cs new file mode 100644 index 0000000000..f921102152 --- /dev/null +++ b/src/Plugins/SmartStore.ShippingByWeight/Data/Migrations/201506290747117_ZipCode.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.ShippingByWeight.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.0-30225")] + public sealed partial class ZipCode : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ZipCode)); + + string IMigrationMetadata.Id + { + get { return "201506290747117_ZipCode"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Plugins/SmartStore.ShippingByWeight/Data/Migrations/201506290747117_ZipCode.cs b/src/Plugins/SmartStore.ShippingByWeight/Data/Migrations/201506290747117_ZipCode.cs new file mode 100644 index 0000000000..e0ee37f6b9 --- /dev/null +++ b/src/Plugins/SmartStore.ShippingByWeight/Data/Migrations/201506290747117_ZipCode.cs @@ -0,0 +1,18 @@ +namespace SmartStore.ShippingByWeight.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ZipCode : DbMigration + { + public override void Up() + { + AddColumn("dbo.ShippingByWeight", "Zip", c => c.String()); + } + + public override void Down() + { + DropColumn("dbo.ShippingByWeight", "Zip"); + } + } +} diff --git a/src/Plugins/SmartStore.ShippingByWeight/Data/Migrations/201506290747117_ZipCode.resx b/src/Plugins/SmartStore.ShippingByWeight/Data/Migrations/201506290747117_ZipCode.resx new file mode 100644 index 0000000000..aa32749020 --- /dev/null +++ b/src/Plugins/SmartStore.ShippingByWeight/Data/Migrations/201506290747117_ZipCode.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 + + + H4sIAAAAAAAEAM1ZzW7jNhC+F+g7CDq1QNZykksayLtInKQIuk7SKLsFeqOlsc2WPypJBfaz9dBH6it0qD/LkiVbjrPozaZmvm9mSM3Hsf/9+x//05Iz5xWUplKM3NPB0HVAhDKiYj5yEzP7cOF++vj9d/5txJfO18Lu3Nqhp9Ajd2FMfOl5OlwAJ3rAaaikljMzCCX3SCS9s+HwJ+/01AOEcBHLcfznRBjKIf2CX8dShBCbhLCJjIDpfB2fBCmq80A46JiEMHIDTpQJjFQwCBY0jjHQ69VvQOcLM7ghhrjOFaME4wqAzVyHCCENMRj15RcNgVFSzIMYFwh7WcWAdjPCNOTZXK7N901seGYT89aOBVSYaCN5T8DT87xSXt39oHq7ZSWxlrdYc7OyWaf1xArV6vcMoVSR69SpL8dMWbcdxUcXKhrrGeiJ0+F6Uh6ss8HZYDgYnjjjhJlEwUhAYhRhJ85TMmU0/AVWL/JPECORMFZNDtPDZxsLuPSkZAzKrJ5hlqd8j+l5m35e3bF0q/hk6d8Lc37mOg9ITqYMyrNTKVWa4c8gQBED0RMxBpSwGJBWv8Fe40rddxN2g4wlvl5q9VaY32lcAOB7g/vlOhOy/AxibhYjFz+6zh1dQlSs5KBfBMX+gU5GJTs5iqMwAbOQ0VsjvlOSFwg3EFJOmOs8KfyUt7cL1wlCYvH6g7/Id4PGzvQEKsQjQuZQsFxLyYCI3mBFSccLouZbcI8e/SbhFben7/3IEI39mpD0bQoSFaak34buZaFALySLjkTne+uW3GzUKIoGGyqolm79OP0DQmOtYGm2NG08VHnf1vm7uJlpxhKA6VQD7TrrIDNRbWnx23Mrs1hrupeJeiH+Xov6+xOSslRuA/mKE2RXgfGHoL8q8gzDC/UWcSyjLZmwH+O7U3tqW2wEd1RpY68cU2J3dhzxhln3nrXsR8HdvS11rVvvUuFvP5enuPPO1LajNY71DtxhUTi2lbQ+UEbeeqVoAKVXO8KI2qK1Y8kSLtr0usu7VM8qRLm4P05FQKtIleX9sVIVraKkCz1yaijkRnKNp/sjZ2pZRctW9kewklj1t9/3966pXhWo9qh/tZrit61qTatDmQrVa2cpLHowtEjdBkeLzYEsFYVrZanYNFl8r9Yh6m3Ka/Sp2m283ga7NKVuUrKX2lLTED/v57vHzEaDz0ys1MtXGqXNfaUN8LyB/sXGjILd4MJgQgSdgTbZwOKi/lzUZtP/z5zoaR2xg4bFbz6DUVvjnVNW32ve5tiVcrx16DoEpDJyiVeSvs0/cLL8sQr0lrHqkJiqQ1X0fkPV0aG3DlVT2r8Auwaqo0feNVAdn6xzoHpfusZA9Ua6fgNV8w5/yIDUYz7KJASznNpTn2VRtz14imoqm+9Vf2b1b0DT+RrC/ugqcBjBGq9BC5t7MZPFxmEBqhEVJrV9xU5DIqzjlTJ0RkKDj0PQOv3V6CthCZrc8ilE9+IxMXFirrQGPmUbP4j5Xjd/Oipuxuw/xvabPkYKGCbFFOBRXCfUnsk87rstB60Fwh6pXJYwqsBYeZqvSqQHKfYEyst3AzEIK2ovwGOGYPpRBOQVDokN++FnmJNwVVxQ2kF2b8Rm2f0bSuaKcJ1jrP3tXwee/e/g4381Mg/3bRgAAA== + + + dbo + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.ShippingByWeight/Data/ShippingByWeightRecordMap.cs b/src/Plugins/SmartStore.ShippingByWeight/Data/ShippingByWeightRecordMap.cs index 72eb28c4f0..8f906b6226 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Data/ShippingByWeightRecordMap.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Data/ShippingByWeightRecordMap.cs @@ -9,6 +9,7 @@ public ShippingByWeightRecordMap() { this.ToTable("ShippingByWeight"); this.HasKey(x => x.Id); + this.Property(x => x.Zip).IsOptional(); } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.ShippingByWeight/Description.txt b/src/Plugins/SmartStore.ShippingByWeight/Description.txt index c513533142..218a277c2b 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: 1.62 -MinAppVersion: 2.1.0 +Version: 2.6.0 +MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.ShippingByWeight.dll ResourceRootKey: Plugins.Shipping.ByWeight \ No newline at end of file diff --git a/src/Plugins/SmartStore.ShippingByWeight/Domain/ShippingByWeightRecord.cs b/src/Plugins/SmartStore.ShippingByWeight/Domain/ShippingByWeightRecord.cs index 50dc8a46c1..b46608072d 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Domain/ShippingByWeightRecord.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Domain/ShippingByWeightRecord.cs @@ -22,10 +22,10 @@ public partial class ShippingByWeightRecord : BaseEntity ///// //public int StateProvinceId { get; set; } - ///// - ///// Gets or sets the zip - ///// - //public string Zip { get; set; } + /// + /// Gets or sets the zip + /// + public string Zip { get; set; } /// /// Gets or sets the shipping method identifier diff --git a/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.de-de.xml b/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.de-de.xml index d4601b5452..1cb41d9656 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.de-de.xml @@ -9,6 +9,12 @@ Das Bestimmungsland. Wählen Sie Stern (*), wenn das Land des Kunden bei der Auswertung keine Rolle spielen soll. + + PLZ-(Bereich) + + + PLZ-(Bereich) des Kunden, entweder als spezifische Werte (getrennt durch Komma) oder als Muster (z.B. 4000-49999 für das PLZ-Gebiet 4). In einem Muster lassen sich auch Wildcards verwenden, wie Stern (*) oder Fragezeichen (?). Sie können auch mehrere Wildcards angeben (durch Komma getrennt). Lassen Sie das Feld leer, wenn die Gebühr unabhängig von PLZ für alle Kunden im definierten (Bundes)land gelten soll. + Versandart diff --git a/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.en-us.xml b/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.en-us.xml index dc2700ea71..c168a3ef5f 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.en-us.xml @@ -8,6 +8,12 @@ If an asterisk is selected, then this shipping rate will apply to all customers, regardless of the country. + + Zip-(Range) + + + Zip/postal code (range), either as specific values (comma devided) or range pattern (e.g. 4000-4999). You can also define wildcard chars like * or ?. If zip is empty, then this shipping rate will apply to all customers from the given country or state / province, regardless of the zip code. You can also enter multiple ranges (comma devided) + Shipping method @@ -30,7 +36,7 @@ Use percentage - Check to use 'charge percentage' value. + Check the box to use 'charge percentage' value. Charge percentage (of subtotal) diff --git a/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightListModel.cs b/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightListModel.cs index 48cf4723a2..eb7710b070 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightListModel.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightListModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.ShippingByWeight.Models { @@ -19,6 +19,8 @@ public ShippingByWeightListModel() [SmartResourceDisplayName("Plugins.Shipping.ByWeight.Fields.Country")] public int AddCountryId { get; set; } + [SmartResourceDisplayName("Plugins.Shipping.ByWeight.Fields.Zip")] + public string AddZip { get; set; } [SmartResourceDisplayName("Plugins.Shipping.ByWeight.Fields.ShippingMethod")] public int AddShippingMethodId { get; set; } [SmartResourceDisplayName("Plugins.Shipping.ByWeight.Fields.From")] diff --git a/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightModel.cs b/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightModel.cs index a6ba8904bd..516bf71071 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightModel.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.ShippingByWeight.Models { @@ -26,6 +26,9 @@ public class ShippingByWeightModel : EntityModelBase [SmartResourceDisplayName("Plugins.Shipping.ByWeight.Fields.To")] public decimal To { get; set; } + [SmartResourceDisplayName("Plugins.Shipping.ByWeight.Fields.Zip")] + public string Zip { get; set; } + [SmartResourceDisplayName("Plugins.Shipping.ByWeight.Fields.UsePercentage")] public bool UsePercentage { get; set; } diff --git a/src/Plugins/SmartStore.ShippingByWeight/RouteProvider.cs b/src/Plugins/SmartStore.ShippingByWeight/RouteProvider.cs index 89fe6dab66..89c87cf6b2 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/RouteProvider.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/RouteProvider.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.ShippingByWeight { diff --git a/src/Plugins/SmartStore.ShippingByWeight/Services/IShippingByWeightService.cs b/src/Plugins/SmartStore.ShippingByWeight/Services/IShippingByWeightService.cs index aab79f76a2..76237a224a 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Services/IShippingByWeightService.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Services/IShippingByWeightService.cs @@ -23,7 +23,7 @@ public partial interface IShippingByWeightService /// IList GetShippingByWeightModels(int pageIndex, int pageSize, out int totalCount); - ShippingByWeightRecord FindRecord(int shippingMethodId, int storeId, int countryId, decimal weight); + ShippingByWeightRecord FindRecord(int shippingMethodId, int storeId, int countryId, decimal weight, string zip); ShippingByWeightRecord GetById(int shippingByWeightRecordId); diff --git a/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs b/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs index d518e3124b..f60212f80c 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs @@ -8,6 +8,7 @@ using SmartStore.Services.Directory; using SmartStore.Services.Shipping; using SmartStore.Services.Stores; +using SmartStore.Utilities; namespace SmartStore.ShippingByWeight.Services { @@ -82,7 +83,7 @@ public virtual IList GetShippingByWeightModels(int pageIn var shippingMethod = _shippingService.GetShippingMethodById(x.ShippingMethodId); var country = _countryService.GetCountryById(x.CountryId); - var model = new ShippingByWeightModel() + var model = new ShippingByWeightModel { Id = x.Id, StoreId = x.StoreId, @@ -90,6 +91,7 @@ public virtual IList GetShippingByWeightModels(int pageIn CountryId = x.CountryId, From = x.From, To = x.To, + Zip = (x.Zip.HasValue() ? x.Zip : "*"), UsePercentage = x.UsePercentage, ShippingChargePercentage = x.ShippingChargePercentage, ShippingChargeAmount = x.ShippingChargeAmount, @@ -107,7 +109,7 @@ public virtual IList GetShippingByWeightModels(int pageIn return result; } - public virtual ShippingByWeightRecord FindRecord(int shippingMethodId, int storeId, int countryId, decimal weight) + public virtual ShippingByWeightRecord FindRecord(int shippingMethodId, int storeId, int countryId, decimal weight, string zip) { var existingRecords = GetShippingByWeightRecords() .Where(x => x.ShippingMethodId == shippingMethodId && weight >= x.From && weight <= x.To) @@ -147,7 +149,17 @@ public virtual ShippingByWeightRecord FindRecord(int shippingMethodId, int store } } - return matchedByCountry.FirstOrDefault(); + //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); + } + } + + return matchedByZip.LastOrDefault(); } public virtual ShippingByWeightRecord GetById(int shippingByWeightRecordId) @@ -183,6 +195,34 @@ public virtual void UpdateShippingByWeightRecord(ShippingByWeightRecord shipping _sbwRepository.Update(shippingByWeightRecord); } + private bool ZipMatches(string zip, string pattern) + { + if (pattern.IsEmpty() || pattern == "*") + { + return true; // catch all + } + + var patterns = pattern.Contains(",") + ? pattern.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()) + : new string[] { pattern }; + + try + { + foreach (var entry in patterns) + { + var wildcard = new Wildcard(entry); + if (wildcard.IsMatch(zip)) + return true; + } + } + catch + { + return zip.IsCaseInsensitiveEqual(pattern); + } + + return false; + } + #endregion } } diff --git a/src/Plugins/SmartStore.ShippingByWeight/SmartStore.ShippingByWeight.csproj b/src/Plugins/SmartStore.ShippingByWeight/SmartStore.ShippingByWeight.csproj index 55b1f4624f..990c0f2f61 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/SmartStore.ShippingByWeight.csproj +++ b/src/Plugins/SmartStore.ShippingByWeight/SmartStore.ShippingByWeight.csproj @@ -1,5 +1,5 @@  - + @@ -20,7 +20,7 @@ Properties SmartStore.ShippingByWeight SmartStore.ShippingByWeight - v4.5 + v4.5.1 512 @@ -42,6 +42,7 @@ + true @@ -81,22 +82,19 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll - False + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - - ..\..\packages\Autofac.Mvc5.3.3.1\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.dll - False + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll - False + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll @@ -104,37 +102,40 @@ - + + + + + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - + False ..\..\..\lib\Telerik\Telerik.Web.Mvc.dll @@ -155,6 +156,10 @@ 201406041606037_SmallQuantitySurcharge.cs + + + 201506290747117_ZipCode.cs + @@ -216,6 +221,9 @@ Designer PreserveNewest + + 201506290747117_ZipCode.cs + diff --git a/src/Plugins/SmartStore.ShippingByWeight/Views/ShippingByWeight/Configure.cshtml b/src/Plugins/SmartStore.ShippingByWeight/Views/ShippingByWeight/Configure.cshtml index f7c10bcbdb..3da335400c 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Views/ShippingByWeight/Configure.cshtml +++ b/src/Plugins/SmartStore.ShippingByWeight/Views/ShippingByWeight/Configure.cshtml @@ -1,11 +1,10 @@ -@{ - Layout = ""; -} -@model ShippingByWeightListModel -@using SmartStore.ShippingByWeight.Models; +@using SmartStore.ShippingByWeight.Models; @using SmartStore.Web.Framework; @using Telerik.Web.Mvc.UI; -@using System.Linq; +@model ShippingByWeightListModel +@{ + Layout = ""; +}
    + @Html.SmartLabelFor(model => model.SecurityProtocol) + + @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified")) +
    @Html.SmartLabelFor(model => model.UseSandbox) @@ -63,6 +69,15 @@ @Html.ValidationMessageFor(model => model.PdtValidateOrderTotal)
    + @Html.SmartLabelFor(model => model.PdtValidateOnlyWarn) + + @Html.SettingEditorFor(model => model.PdtValidateOnlyWarn) + @Html.ValidationMessageFor(model => model.PdtValidateOnlyWarn) +
    @Html.SmartLabelFor(model => model.AdditionalFee) @@ -99,7 +114,16 @@ @Html.ValidationMessageFor(model => model.EnableIpn)
    + @Html.SmartLabelFor(model => model.IpnChangesPaymentStatus) + + @Html.SettingEditorFor(model => model.IpnChangesPaymentStatus) + @Html.ValidationMessageFor(model => model.IpnChangesPaymentStatus) +
    @Html.SmartLabelFor(model => model.IpnUrl)
    @T("Plugins.Payments.PayPalStandard.Fields.EnableIpn.Hint2") @@ -132,7 +156,11 @@ $(document).ready(function () { $("#@Html.FieldIdFor(model => model.EnableIpn)").change(function () { - $('.ipn-url').toggle($(this).is(':checked')); + $('.ipn-handling').toggle($(this).is(':checked')); + }).trigger('change'); + + $("#@Html.FieldIdFor(model => model.PdtValidateOrderTotal)").change(function () { + $('#PdtValidateOnlyWarnContainer').toggle($(this).is(':checked')); }).trigger('change'); }); diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml index c5b73f2d11..ee77251801 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml @@ -5,7 +5,7 @@
    - PayPal + PayPal
    @T("Plugins.Payments.PayPalStandard.Fields.RedirectionTip") diff --git a/src/Plugins/SmartStore.PayPal/Views/Web.config b/src/Plugins/SmartStore.PayPal/Views/Web.config index 31dc8df754..e9d36a3c51 100644 --- a/src/Plugins/SmartStore.PayPal/Views/Web.config +++ b/src/Plugins/SmartStore.PayPal/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/Reference.cs b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/Reference.cs index 1e10b576a7..b8c6682313 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.18444 +// Laufzeitversion:4.0.30319.34209 // // Ä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.18444. +// Der Quellcode wurde automatisch mit Microsoft.VSDesigner generiert. Version 4.0.30319.34209. // #pragma warning disable 1591 @@ -23,7 +23,7 @@ namespace SmartStore.PayPal.PayPalSvc { /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [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.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute(Name="PayPalAPIAASoapBinding", Namespace="urn:ebay:api:PayPalAPI")] @@ -11776,7 +11776,6 @@ public string Build { } /// - //[System.Xml.Serialization.XmlAnyElementAttribute(Order=6)] [System.Xml.Serialization.XmlIgnoreAttribute()] public System.Xml.XmlElement Any { get { @@ -27114,11 +27113,11 @@ public ExternalRememberMeOptOutRequestType ExternalRememberMeOptOutRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void RefundTransactionCompletedEventHandler(object sender, RefundTransactionCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class RefundTransactionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27140,11 +27139,11 @@ public RefundTransactionResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void InitiateRecoupCompletedEventHandler(object sender, InitiateRecoupCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class InitiateRecoupCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27166,11 +27165,11 @@ public InitiateRecoupResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void CompleteRecoupCompletedEventHandler(object sender, CompleteRecoupCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CompleteRecoupCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27192,11 +27191,11 @@ public CompleteRecoupResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void CancelRecoupCompletedEventHandler(object sender, CancelRecoupCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CancelRecoupCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27218,11 +27217,11 @@ public CancelRecoupResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void GetTransactionDetailsCompletedEventHandler(object sender, GetTransactionDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetTransactionDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27244,11 +27243,11 @@ public GetTransactionDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void BMCreateButtonCompletedEventHandler(object sender, BMCreateButtonCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMCreateButtonCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27270,11 +27269,11 @@ public BMCreateButtonResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void BMUpdateButtonCompletedEventHandler(object sender, BMUpdateButtonCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMUpdateButtonCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27296,11 +27295,11 @@ public BMUpdateButtonResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void BMManageButtonStatusCompletedEventHandler(object sender, BMManageButtonStatusCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMManageButtonStatusCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27322,11 +27321,11 @@ public BMManageButtonStatusResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void BMGetButtonDetailsCompletedEventHandler(object sender, BMGetButtonDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMGetButtonDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27348,11 +27347,11 @@ public BMGetButtonDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void BMSetInventoryCompletedEventHandler(object sender, BMSetInventoryCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMSetInventoryCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27374,11 +27373,11 @@ public BMSetInventoryResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void BMGetInventoryCompletedEventHandler(object sender, BMGetInventoryCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMGetInventoryCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27400,11 +27399,11 @@ public BMGetInventoryResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void BMButtonSearchCompletedEventHandler(object sender, BMButtonSearchCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMButtonSearchCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27426,11 +27425,11 @@ public BMButtonSearchResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void BillUserCompletedEventHandler(object sender, BillUserCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BillUserCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27452,11 +27451,11 @@ public BillUserResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void TransactionSearchCompletedEventHandler(object sender, TransactionSearchCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class TransactionSearchCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27478,11 +27477,11 @@ public TransactionSearchResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void MassPayCompletedEventHandler(object sender, MassPayCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class MassPayCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27504,11 +27503,11 @@ public MassPayResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void BillAgreementUpdateCompletedEventHandler(object sender, BillAgreementUpdateCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BillAgreementUpdateCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27530,11 +27529,11 @@ public BAUpdateResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void AddressVerifyCompletedEventHandler(object sender, AddressVerifyCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class AddressVerifyCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27556,11 +27555,11 @@ public AddressVerifyResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void EnterBoardingCompletedEventHandler(object sender, EnterBoardingCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class EnterBoardingCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27582,11 +27581,11 @@ public EnterBoardingResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void GetBoardingDetailsCompletedEventHandler(object sender, GetBoardingDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetBoardingDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27608,11 +27607,11 @@ public GetBoardingDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void CreateMobilePaymentCompletedEventHandler(object sender, CreateMobilePaymentCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CreateMobilePaymentCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27634,11 +27633,11 @@ public CreateMobilePaymentResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void GetMobileStatusCompletedEventHandler(object sender, GetMobileStatusCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetMobileStatusCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27660,11 +27659,11 @@ public GetMobileStatusResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void SetMobileCheckoutCompletedEventHandler(object sender, SetMobileCheckoutCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SetMobileCheckoutCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27686,11 +27685,11 @@ public SetMobileCheckoutResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoMobileCheckoutPaymentCompletedEventHandler(object sender, DoMobileCheckoutPaymentCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoMobileCheckoutPaymentCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27712,11 +27711,11 @@ public DoMobileCheckoutPaymentResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void GetBalanceCompletedEventHandler(object sender, GetBalanceCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetBalanceCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27738,11 +27737,11 @@ public GetBalanceResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void GetPalDetailsCompletedEventHandler(object sender, GetPalDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetPalDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27764,11 +27763,11 @@ public GetPalDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoExpressCheckoutPaymentCompletedEventHandler(object sender, DoExpressCheckoutPaymentCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoExpressCheckoutPaymentCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27790,11 +27789,11 @@ public DoExpressCheckoutPaymentResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoUATPExpressCheckoutPaymentCompletedEventHandler(object sender, DoUATPExpressCheckoutPaymentCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoUATPExpressCheckoutPaymentCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27816,11 +27815,11 @@ public DoUATPExpressCheckoutPaymentResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void SetAuthFlowParamCompletedEventHandler(object sender, SetAuthFlowParamCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SetAuthFlowParamCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27842,11 +27841,11 @@ public SetAuthFlowParamResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void GetAuthDetailsCompletedEventHandler(object sender, GetAuthDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetAuthDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27868,11 +27867,11 @@ public GetAuthDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void SetAccessPermissionsCompletedEventHandler(object sender, SetAccessPermissionsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SetAccessPermissionsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27894,11 +27893,11 @@ public SetAccessPermissionsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void UpdateAccessPermissionsCompletedEventHandler(object sender, UpdateAccessPermissionsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class UpdateAccessPermissionsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27920,11 +27919,11 @@ public UpdateAccessPermissionsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void GetAccessPermissionDetailsCompletedEventHandler(object sender, GetAccessPermissionDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetAccessPermissionDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27946,11 +27945,11 @@ public GetAccessPermissionDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void GetIncentiveEvaluationCompletedEventHandler(object sender, GetIncentiveEvaluationCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetIncentiveEvaluationCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27972,11 +27971,11 @@ public GetIncentiveEvaluationResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void SetExpressCheckoutCompletedEventHandler(object sender, SetExpressCheckoutCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SetExpressCheckoutCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27998,11 +27997,11 @@ public SetExpressCheckoutResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void ExecuteCheckoutOperationsCompletedEventHandler(object sender, ExecuteCheckoutOperationsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ExecuteCheckoutOperationsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28024,11 +28023,11 @@ public ExecuteCheckoutOperationsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void GetExpressCheckoutDetailsCompletedEventHandler(object sender, GetExpressCheckoutDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetExpressCheckoutDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28050,11 +28049,11 @@ public GetExpressCheckoutDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoDirectPaymentCompletedEventHandler(object sender, DoDirectPaymentCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoDirectPaymentCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28076,11 +28075,11 @@ public DoDirectPaymentResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void ManagePendingTransactionStatusCompletedEventHandler(object sender, ManagePendingTransactionStatusCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ManagePendingTransactionStatusCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28102,11 +28101,11 @@ public ManagePendingTransactionStatusResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoCancelCompletedEventHandler(object sender, DoCancelCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoCancelCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28128,11 +28127,11 @@ public DoCancelResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoCaptureCompletedEventHandler(object sender, DoCaptureCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoCaptureCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28154,11 +28153,11 @@ public DoCaptureResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoReauthorizationCompletedEventHandler(object sender, DoReauthorizationCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoReauthorizationCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28180,11 +28179,11 @@ public DoReauthorizationResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoVoidCompletedEventHandler(object sender, DoVoidCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoVoidCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28206,11 +28205,11 @@ public DoVoidResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoAuthorizationCompletedEventHandler(object sender, DoAuthorizationCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoAuthorizationCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28232,11 +28231,11 @@ public DoAuthorizationResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void UpdateAuthorizationCompletedEventHandler(object sender, UpdateAuthorizationCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class UpdateAuthorizationCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28258,11 +28257,11 @@ public UpdateAuthorizationResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void SetCustomerBillingAgreementCompletedEventHandler(object sender, SetCustomerBillingAgreementCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SetCustomerBillingAgreementCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28284,11 +28283,11 @@ public SetCustomerBillingAgreementResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void GetBillingAgreementCustomerDetailsCompletedEventHandler(object sender, GetBillingAgreementCustomerDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetBillingAgreementCustomerDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28310,11 +28309,11 @@ public GetBillingAgreementCustomerDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void CreateBillingAgreementCompletedEventHandler(object sender, CreateBillingAgreementCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CreateBillingAgreementCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28336,11 +28335,11 @@ public CreateBillingAgreementResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoReferenceTransactionCompletedEventHandler(object sender, DoReferenceTransactionCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoReferenceTransactionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28362,11 +28361,11 @@ public DoReferenceTransactionResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoNonReferencedCreditCompletedEventHandler(object sender, DoNonReferencedCreditCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoNonReferencedCreditCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28388,11 +28387,11 @@ public DoNonReferencedCreditResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void DoUATPAuthorizationCompletedEventHandler(object sender, DoUATPAuthorizationCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoUATPAuthorizationCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28414,11 +28413,11 @@ public DoUATPAuthorizationResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void CreateRecurringPaymentsProfileCompletedEventHandler(object sender, CreateRecurringPaymentsProfileCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CreateRecurringPaymentsProfileCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28440,11 +28439,11 @@ public CreateRecurringPaymentsProfileResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void GetRecurringPaymentsProfileDetailsCompletedEventHandler(object sender, GetRecurringPaymentsProfileDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetRecurringPaymentsProfileDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28466,11 +28465,11 @@ public GetRecurringPaymentsProfileDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void ManageRecurringPaymentsProfileStatusCompletedEventHandler(object sender, ManageRecurringPaymentsProfileStatusCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ManageRecurringPaymentsProfileStatusCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28492,11 +28491,11 @@ public ManageRecurringPaymentsProfileStatusResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void BillOutstandingAmountCompletedEventHandler(object sender, BillOutstandingAmountCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BillOutstandingAmountCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28518,11 +28517,11 @@ public BillOutstandingAmountResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void UpdateRecurringPaymentsProfileCompletedEventHandler(object sender, UpdateRecurringPaymentsProfileCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class UpdateRecurringPaymentsProfileCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28544,11 +28543,11 @@ public UpdateRecurringPaymentsProfileResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void ReverseTransactionCompletedEventHandler(object sender, ReverseTransactionCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ReverseTransactionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28570,11 +28569,11 @@ public ReverseTransactionResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] public delegate void ExternalRememberMeOptOutCompletedEventHandler(object sender, ExternalRememberMeOptOutCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ExternalRememberMeOptOutCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { diff --git a/src/Plugins/SmartStore.PayPal/changelog.md b/src/Plugins/SmartStore.PayPal/changelog.md index d264816dfd..e074479a1d 100644 --- a/src/Plugins/SmartStore.PayPal/changelog.md +++ b/src/Plugins/SmartStore.PayPal/changelog.md @@ -1,9 +1,56 @@ -#Release Notes# +#Release Notes -##Paypal 1.22## -###Bugfixes### +##Paypal 2.6.0.5 +###Bugfixes +* PayPal PLUS: Fixes "Cannot perform runtime binding on a null reference" when rendering the payment wall. + +##Paypal 2.6.0.4 +###Bugfixes +* PayPal PLUS: Excluding tax issue. Fixes "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. Fixes "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 + +##Paypal 2.5.0.1 +###Bugfixes +* PayPal Standard: The order amount transmitted to PayPal was wrong if gift cards or reward points were applied + +##Paypal 2.2.0.4 +###New Features +* Option for API security protocol +* Option to display express checkout button in mini shopping cart +* Support for partial refunds +* Option whether IPD may change the payment status of an order +###Bugfixes +* "The request was aborted: Could not create SSL/TLS secure channel." See https://devblog.paypal.com/upcoming-security-changes-notice/ +* PayPal Express: Void and refund out of function ("The transaction id is not valid") + +##Paypal 2.2.0.3 +###New Features +* Option to add order note when order total validation fails + +##PayPal 2.2.0.2 +###Improvements +* Redirecting to payment provider performed by core instead of plugin + +##Paypal 2.2.0.1 +###New Features +* Supports order list label for new incoming IPNs + +##Paypal 1.22 +###Bugfixes * PayPal Standard provider now using shipping rather than billing address if shipping is required -##Paypal 1.21## -###Improvements### +##Paypal 1.21 +###Improvements * Multistore configuration \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/packages.config b/src/Plugins/SmartStore.PayPal/packages.config index d9685c04ab..eb91eb3011 100644 --- a/src/Plugins/SmartStore.PayPal/packages.config +++ b/src/Plugins/SmartStore.PayPal/packages.config @@ -1,10 +1,11 @@  - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/web.config b/src/Plugins/SmartStore.PayPal/web.config index 7eff4fe661..f6daa0fb4b 100644 --- a/src/Plugins/SmartStore.PayPal/web.config +++ b/src/Plugins/SmartStore.PayPal/web.config @@ -18,7 +18,7 @@ - + @@ -42,96 +42,104 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs b/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs index c751879157..5c5e03f8ab 100644 --- a/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs +++ b/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs @@ -1,14 +1,13 @@ using System.Web.Mvc; using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Directory; +using SmartStore.Services; +using SmartStore.Services.Directory; +using SmartStore.Services.Shipping; using SmartStore.Shipping.Domain; using SmartStore.Shipping.Models; using SmartStore.Shipping.Services; -using SmartStore.Services.Configuration; -using SmartStore.Services.Directory; -using SmartStore.Services.Shipping; -using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Shipping.Controllers @@ -17,34 +16,26 @@ namespace SmartStore.Shipping.Controllers public class ByTotalController : PluginControllerBase { private readonly IShippingService _shippingService; - private readonly IStoreService _storeService; - private readonly ISettingService _settingService; private readonly IShippingByTotalService _shippingByTotalService; private readonly ShippingByTotalSettings _shippingByTotalSettings; private readonly ICountryService _countryService; - private readonly ICurrencyService _currencyService; - private readonly CurrencySettings _currencySettings; private readonly AdminAreaSettings _adminAreaSettings; + private readonly ICommonServices _services; - public ByTotalController(IShippingService shippingService, - IStoreService storeService, - ISettingService settingService, + public ByTotalController( + IShippingService shippingService, IShippingByTotalService shippingByTotalService, ShippingByTotalSettings shippingByTotalSettings, ICountryService countryService, - ICurrencyService currencyService, - CurrencySettings currencySettings, - AdminAreaSettings adminAreaSettings) + AdminAreaSettings adminAreaSettings, + ICommonServices services) { this._shippingService = shippingService; - this._storeService = storeService; - this._settingService = settingService; this._shippingByTotalService = shippingByTotalService; this._shippingByTotalSettings = shippingByTotalSettings; this._countryService = countryService; - this._currencyService = currencyService; - this._currencySettings = currencySettings; this._adminAreaSettings = adminAreaSettings; + this._services = services; } public ActionResult Configure() @@ -52,33 +43,35 @@ public ActionResult Configure() var shippingMethods = _shippingService.GetAllShippingMethods(); if (shippingMethods.Count == 0) { - return Content("No shipping methods can be loaded"); + return Content(T("Admin.Configuration.Shipping.Methods.NoMethodsLoaded")); } var model = new ByTotalListModel(); + var allStores = _services.StoreService.GetAllStores(); + foreach (var sm in shippingMethods) { - model.AvailableShippingMethods.Add(new SelectListItem() { Text = sm.Name, Value = sm.Id.ToString() }); + model.AvailableShippingMethods.Add(new SelectListItem { Text = sm.Name, Value = sm.Id.ToString() }); } //stores - model.AvailableStores.Add(new SelectListItem() { Text = "*", Value = "0" }); - foreach (var store in _storeService.GetAllStores()) + model.AvailableStores.Add(new SelectListItem { Text = "*", Value = "0" }); + foreach (var store in allStores) { - model.AvailableStores.Add(new SelectListItem() { Text = store.Name, Value = store.Id.ToString() }); + model.AvailableStores.Add(new SelectListItem { Text = store.Name, Value = store.Id.ToString() }); } - //model.AvailableCountries.Add(new SelectListItem() { Text = "*", Value = "0" }); + //model.AvailableCountries.Add(new SelectListItem { Text = "*", Value = "0" }); var countries = _countryService.GetAllCountries(true); foreach (var c in countries) { - model.AvailableCountries.Add(new SelectListItem() { Text = c.Name, Value = c.Id.ToString() }); + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString() }); } model.LimitMethodsToCreated = _shippingByTotalSettings.LimitMethodsToCreated; model.SmallQuantityThreshold = _shippingByTotalSettings.SmallQuantityThreshold; model.SmallQuantitySurcharge = _shippingByTotalSettings.SmallQuantitySurcharge; - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; model.GridPageSize = _adminAreaSettings.GridPageSize; return View(model); @@ -107,10 +100,11 @@ public ActionResult RateUpdate(ByTotalModel model, GridCommand command) { if (!ModelState.IsValid) { - return new JsonResult { Data = "error" }; + return new JsonResult { Data = T("Admin.Common.UnknownError").Text }; } var shippingByTotalRecord = _shippingByTotalService.GetShippingByTotalRecordById(model.Id); + shippingByTotalRecord.Zip = model.Zip == "*" ? null : model.Zip; shippingByTotalRecord.From = model.From; shippingByTotalRecord.To = model.To; @@ -119,6 +113,7 @@ public ActionResult RateUpdate(ByTotalModel model, GridCommand command) shippingByTotalRecord.ShippingChargePercentage = model.ShippingChargePercentage; shippingByTotalRecord.BaseCharge = model.BaseCharge; shippingByTotalRecord.MaxCharge = model.MaxCharge; + _shippingByTotalService.UpdateShippingByTotalRecord(shippingByTotalRecord); return RatesList(command); @@ -166,7 +161,7 @@ public ActionResult SaveGeneralSettings(ByTotalListModel model) _shippingByTotalSettings.SmallQuantityThreshold = model.SmallQuantityThreshold; _shippingByTotalSettings.SmallQuantitySurcharge = model.SmallQuantitySurcharge; - _settingService.SaveSetting(_shippingByTotalSettings); + _services.Settings.SaveSetting(_shippingByTotalSettings); return Json(new { Result = true }); } diff --git a/src/Plugins/SmartStore.Shipping/Controllers/FixedRateController.cs b/src/Plugins/SmartStore.Shipping/Controllers/FixedRateController.cs index 1522e7f545..353bafe59d 100644 --- a/src/Plugins/SmartStore.Shipping/Controllers/FixedRateController.cs +++ b/src/Plugins/SmartStore.Shipping/Controllers/FixedRateController.cs @@ -8,6 +8,7 @@ using SmartStore.Services.Shipping; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Shipping.Controllers @@ -28,7 +29,7 @@ public ActionResult Configure() { var shippingMethods = _shippingService.GetAllShippingMethods(); if (shippingMethods.Count == 0) - return Content("No shipping methods can be loaded"); + return Content(T("Admin.Configuration.Shipping.Methods.NoMethodsLoaded")); var tmp = new List(); foreach (var shippingMethod in shippingMethods) diff --git a/src/Plugins/SmartStore.Shipping/Description.txt b/src/Plugins/SmartStore.Shipping/Description.txt index 38688a6850..f241173e84 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: 1.0 -MinAppVersion: 2.1.0 +Version: 2.6.0 +MinAppVersion: 2.5.0 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 66322e9293..259ba4ecd4 100644 --- a/src/Plugins/SmartStore.Shipping/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.Shipping/Localization/resources.de-de.xml @@ -35,7 +35,7 @@ PLZ-(Bereich) - PLZ-(Bereich) des Kunden, entweder als spezifischer Wert oder als Muster (z.B. 4000-49999 für das PLZ-Gebiet 4). In einem Muster lassen sich auch Wildcards verwenden, wie Stern (*) oder Fragezeichen (?). Lassen Sie das Feld leer, wenn die Gebühr unabhängig von PLZ für alle Kunden im definierten (Bundes)land gelten soll. + PLZ-(Bereich) des Kunden, entweder als spezifische Werte (getrennt durch Komma) oder als Muster (z.B. 4000-49999 für das PLZ-Gebiet 4). In einem Muster lassen sich auch Wildcards verwenden, wie Stern (*) oder Fragezeichen (?). Sie können auch mehrere Wildcards angeben (durch Komma getrennt). Lassen Sie das Feld leer, wenn die Gebühr unabhängig von PLZ für alle Kunden im definierten (Bundes)land gelten soll. Versandart diff --git a/src/Plugins/SmartStore.Shipping/Localization/resources.en-us.xml b/src/Plugins/SmartStore.Shipping/Localization/resources.en-us.xml index c44b4c48fd..e229ab4d46 100644 --- a/src/Plugins/SmartStore.Shipping/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.Shipping/Localization/resources.en-us.xml @@ -36,7 +36,7 @@ Zip-(Range) - Zip/postal code (range), either as specific value or range pattern (e.g. 4000-4999). You can also define wildcard chars like * or ?. If zip is empty, then this shipping rate will apply to all customers from the given country or state / province, regardless of the zip code. + Zip/postal code (range), either as specific values (comma devided) or range pattern (e.g. 4000-4999). You can also define wildcard chars like * or ?. If zip is empty, then this shipping rate will apply to all customers from the given country or state / province, regardless of the zip code. You can also enter multiple ranges (comma devided) Shipping method @@ -60,7 +60,7 @@ Use percentage - Check to use 'charge percentage' value. + Check the box to use 'charge percentage' value. Charge % diff --git a/src/Plugins/SmartStore.Shipping/Models/ByTotalListModel.cs b/src/Plugins/SmartStore.Shipping/Models/ByTotalListModel.cs index dec18c67ac..848836b1af 100644 --- a/src/Plugins/SmartStore.Shipping/Models/ByTotalListModel.cs +++ b/src/Plugins/SmartStore.Shipping/Models/ByTotalListModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Shipping.Models { diff --git a/src/Plugins/SmartStore.Shipping/Models/ByTotalModel.cs b/src/Plugins/SmartStore.Shipping/Models/ByTotalModel.cs index 5287eb5df4..dc7a5108cc 100644 --- a/src/Plugins/SmartStore.Shipping/Models/ByTotalModel.cs +++ b/src/Plugins/SmartStore.Shipping/Models/ByTotalModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Shipping.Models { diff --git a/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs b/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs index cfc378e5ee..82ec989cf4 100644 --- a/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs +++ b/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs @@ -1,20 +1,20 @@ using System; -using System.Data.Entity.Migrations; using System.Web.Routing; using SmartStore.Core; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -using SmartStore.Shipping.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Configuration; using SmartStore.Services.Localization; -using SmartStore.Core.Logging; using SmartStore.Services.Shipping; using SmartStore.Services.Shipping.Tracking; +using SmartStore.Shipping.Services; namespace SmartStore.Shipping { - [SystemName("Shipping.ByTotal")] + [SystemName("Shipping.ByTotal")] [FriendlyName("Shipping by total")] [DisplayOrder(1)] public class ByTotalProvider : IShippingRateComputationMethod, IConfigurable @@ -28,7 +28,6 @@ public class ByTotalProvider : IShippingRateComputationMethod, IConfigurable private readonly ISettingService _settingService; private readonly ILocalizationService _localizationService; - /// /// Ctor /// @@ -56,14 +55,18 @@ public ByTotalProvider(IShippingService shippingService, this._logger = logger; this._settingService = settingService; this._localizationService = localizationService; - } - #region Properties + T = NullLocalizer.Instance; + } - /// - /// Gets a shipping rate computation method type - /// - public ShippingRateComputationMethodType ShippingRateComputationMethodType + #region Properties + + public Localizer T { get; set; } + + /// + /// Gets a shipping rate computation method type + /// + public ShippingRateComputationMethodType ShippingRateComputationMethodType { get { @@ -158,7 +161,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get if (getShippingOptionRequest.Items == null || getShippingOptionRequest.Items.Count == 0) { - response.AddError("No shipment items"); + response.AddError(T("Admin.System.Warnings.NoShipmentItems")); return response; } @@ -187,7 +190,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get decimal sqThreshold = _shippingByTotalSettings.SmallQuantityThreshold; decimal sqSurcharge = _shippingByTotalSettings.SmallQuantitySurcharge; - var shippingMethods = _shippingService.GetAllShippingMethods(countryId); + var shippingMethods = _shippingService.GetAllShippingMethods(getShippingOptionRequest); foreach (var shippingMethod in shippingMethods) { decimal? rate = GetRate(subTotal, shippingMethod.Id, storeId, countryId, stateProvinceId, zip); @@ -200,6 +203,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get } var shippingOption = new ShippingOption(); + shippingOption.ShippingMethodId = shippingMethod.Id; shippingOption.Name = shippingMethod.Name; shippingOption.Description = shippingMethod.Description; shippingOption.Rate = rate.Value; @@ -231,6 +235,11 @@ public IShipmentTracker ShipmentTracker } } + public bool IsActive + { + get { return true; } + } + /// /// Gets a route for provider configuration /// diff --git a/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs b/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs index 645e985c9c..cc303cee6e 100644 --- a/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs +++ b/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Web.Routing; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Localization; using SmartStore.Core.Plugins; using SmartStore.Services.Configuration; using SmartStore.Services.Localization; @@ -10,27 +11,29 @@ namespace SmartStore.Shipping { - /// - /// Fixed rate shipping computation provider - /// - [SystemName("Shipping.FixedRate")] + /// + /// Fixed rate shipping computation provider + /// + [SystemName("Shipping.FixedRate")] [FriendlyName("Fixed Rate Shipping")] [DisplayOrder(0)] public class FixedRateProvider : IShippingRateComputationMethod, IConfigurable { private readonly ISettingService _settingService; private readonly IShippingService _shippingService; - private readonly ILocalizationService _localizationService; public FixedRateProvider(ISettingService settingService, - IShippingService shippingService, ILocalizationService localizationService) + IShippingService shippingService) { this._settingService = settingService; this._shippingService = shippingService; - _localizationService = localizationService; - } - - private decimal GetRate(int shippingMethodId) + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + private decimal GetRate(int shippingMethodId) { string key = string.Format("ShippingRateComputationMethod.FixedRate.Rate.ShippingMethodId{0}", shippingMethodId); decimal rate = this._settingService.GetSettingByKey(key); @@ -51,15 +54,15 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get if (getShippingOptionRequest.Items == null || getShippingOptionRequest.Items.Count == 0) { - response.AddError("No shipment items"); + response.AddError(T("Admin.System.Warnings.NoShipmentItems")); return response; } - int? restrictByCountryId = (getShippingOptionRequest.ShippingAddress != null && getShippingOptionRequest.ShippingAddress.Country != null) ? (int?)getShippingOptionRequest.ShippingAddress.Country.Id : null; - var shippingMethods = this._shippingService.GetAllShippingMethods(restrictByCountryId); + var shippingMethods = this._shippingService.GetAllShippingMethods(getShippingOptionRequest); foreach (var shippingMethod in shippingMethods) { var shippingOption = new ShippingOption(); + shippingOption.ShippingMethodId = shippingMethod.Id; shippingOption.Name = shippingMethod.GetLocalized(x => x.Name); shippingOption.Description = shippingMethod.GetLocalized(x => x.Description); shippingOption.Rate = GetRate(shippingMethod.Id); @@ -79,8 +82,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get if (getShippingOptionRequest == null) throw new ArgumentNullException("getShippingOptionRequest"); - int? restrictByCountryId = (getShippingOptionRequest.ShippingAddress != null && getShippingOptionRequest.ShippingAddress.Country != null) ? (int?)getShippingOptionRequest.ShippingAddress.Country.Id : null; - var shippingMethods = this._shippingService.GetAllShippingMethods(restrictByCountryId); + var shippingMethods = this._shippingService.GetAllShippingMethods(getShippingOptionRequest); var rates = new List(); foreach (var shippingMethod in shippingMethods) @@ -133,5 +135,10 @@ public IShipmentTracker ShipmentTracker return null; } } + + public bool IsActive + { + get { return true; } + } } } diff --git a/src/Plugins/SmartStore.Shipping/RouteProvider.cs b/src/Plugins/SmartStore.Shipping/RouteProvider.cs index 6771a7781f..38dead4bf3 100644 --- a/src/Plugins/SmartStore.Shipping/RouteProvider.cs +++ b/src/Plugins/SmartStore.Shipping/RouteProvider.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.Shipping { diff --git a/src/Plugins/SmartStore.Shipping/Services/ShippingByTotalService.cs b/src/Plugins/SmartStore.Shipping/Services/ShippingByTotalService.cs index b20c154be6..a24143413c 100644 --- a/src/Plugins/SmartStore.Shipping/Services/ShippingByTotalService.cs +++ b/src/Plugins/SmartStore.Shipping/Services/ShippingByTotalService.cs @@ -212,16 +212,26 @@ private bool ZipMatches(string zip, string pattern) { return true; // catch all } - + + var patterns = pattern.Contains(",") + ? pattern.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()) + : new string[] { pattern }; + try { - var wildcard = new Wildcard(pattern); - return wildcard.IsMatch(zip); + foreach (var entry in patterns) + { + var wildcard = new Wildcard(entry); + if (wildcard.IsMatch(zip)) + return true; + } } catch { return zip.IsCaseInsensitiveEqual(pattern); } + + return false; } /// diff --git a/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj b/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj index cd3b819f1d..3a9b8863dd 100644 --- a/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj +++ b/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj @@ -1,5 +1,5 @@  - + @@ -20,7 +20,7 @@ Properties SmartStore.Shipping SmartStore.Shipping - v4.5 + v4.5.1 512 @@ -42,6 +42,7 @@ + true @@ -81,56 +82,59 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - - ..\..\packages\Autofac.Mvc5.3.3.1\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - + + + + + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - + ..\..\..\lib\Telerik\Telerik.Web.Mvc.dll diff --git a/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml b/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml index 9c8d9fb124..ae05d59b64 100644 --- a/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml +++ b/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml @@ -56,6 +56,7 @@ }) .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) .Editable(x => x.Mode(GridEditMode.InLine)) + .PreserveGridState() .EnableCustomBinding(true))
    @@ -17,6 +16,7 @@ { 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}"); @@ -53,6 +53,7 @@ .Delete("RateDelete", "ShippingByWeight", new RouteValueDictionary() { { "area", "SmartStore.ShippingByWeight" } }); }) .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .PreserveGridState() .EnableCustomBinding(true)) @@ -60,13 +61,11 @@

    @using (Html.BeginForm()) -{ - +{ -
    - @T("Plugins.Shipping.ByWeight.AddNewRecordTitle") -
    +
    + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - + - -
    +
    +
    @T("Plugins.Shipping.ByWeight.AddNewRecordTitle")
    +
    +
    @Html.SmartLabelFor(model => model.AddStoreId) @@ -94,134 +98,146 @@ @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.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.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.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) +
      - -
    - - -
    - @T("Plugins.Shipping.ByWeight.SettingsTitle") - - - - - - - - - - + + + +
    - @Html.SmartLabelFor(model => model.CalculatePerWeightUnit) - - @Html.EditorFor(model => model.CalculatePerWeightUnit) - @Html.ValidationMessageFor(model => model.CalculatePerWeightUnit) -
    - @Html.SmartLabelFor(model => model.LimitMethodsToCreated) - - @Html.EditorFor(model => model.LimitMethodsToCreated) - @Html.ValidationMessageFor(model => model.LimitMethodsToCreated) -
    + + + + + + + + + + + + + + - -
    +
    +
    @T("Plugins.Shipping.ByWeight.SettingsTitle")
    +
    +
    + @Html.SmartLabelFor(model => model.CalculatePerWeightUnit) + + @Html.EditorFor(model => model.CalculatePerWeightUnit) + @Html.ValidationMessageFor(model => model.CalculatePerWeightUnit) +
    + @Html.SmartLabelFor(model => model.LimitMethodsToCreated) + + @Html.EditorFor(model => model.LimitMethodsToCreated) + @Html.ValidationMessageFor(model => model.LimitMethodsToCreated) +
      - -
    -
    + +
    } \ 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 31dc8df754..e9d36a3c51 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Views/Web.config +++ b/src/Plugins/SmartStore.ShippingByWeight/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.ShippingByWeight/packages.config b/src/Plugins/SmartStore.ShippingByWeight/packages.config index 4acdbeae52..1a1ea4e95b 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 188ce3a0f8..c0f82db177 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/web.config +++ b/src/Plugins/SmartStore.ShippingByWeight/web.config @@ -1,116 +1,116 @@ - + - - + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs b/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs index 8117766089..ca43d31f14 100644 --- a/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs +++ b/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs @@ -9,6 +9,8 @@ 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 @@ -68,7 +70,7 @@ public ActionResult Configure() var tc = _taxCategoryService.GetTaxCategoryById(x.TaxCategoryId); m.TaxCategoryName = (tc != null) ? tc.Name : ""; var c = _countryService.GetCountryById(x.CountryId); - m.CountryName = (c != null) ? c.Name : "Unavailable"; + 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 : "*"; @@ -97,7 +99,7 @@ public ActionResult RatesList(GridCommand command) var tc = _taxCategoryService.GetTaxCategoryById(x.TaxCategoryId); m.TaxCategoryName = (tc != null) ? tc.Name : ""; var c = _countryService.GetCountryById(x.CountryId); - m.CountryName = (c != null) ? c.Name : "Unavailable"; + 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 : "*"; diff --git a/src/Plugins/SmartStore.Tax/Controllers/TaxFixedRateController.cs b/src/Plugins/SmartStore.Tax/Controllers/TaxFixedRateController.cs index 563e7ff424..1340565989 100644 --- a/src/Plugins/SmartStore.Tax/Controllers/TaxFixedRateController.cs +++ b/src/Plugins/SmartStore.Tax/Controllers/TaxFixedRateController.cs @@ -8,6 +8,7 @@ using SmartStore.Services.Tax; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Tax.Controllers diff --git a/src/Plugins/SmartStore.Tax/Description.txt b/src/Plugins/SmartStore.Tax/Description.txt index a8a2a5e0af..ec211f9efe 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: 1.0 -MinAppVersion: 2.1.0 +Version: 2.6.0 +MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.Tax.dll ResourceRootKey: Plugins.SmartStore.Tax diff --git a/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateListModel.cs b/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateListModel.cs index 75af2400f8..35cd18a325 100644 --- a/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateListModel.cs +++ b/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateListModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Tax.Models { diff --git a/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateModel.cs b/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateModel.cs index 058f50a138..70d4decd1e 100644 --- a/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateModel.cs +++ b/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Tax.Models { diff --git a/src/Plugins/SmartStore.Tax/RouteProvider.cs b/src/Plugins/SmartStore.Tax/RouteProvider.cs index 4b45008a97..120a251043 100644 --- a/src/Plugins/SmartStore.Tax/RouteProvider.cs +++ b/src/Plugins/SmartStore.Tax/RouteProvider.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.Tax { diff --git a/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj b/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj index 94c658fae5..3f4f325f97 100644 --- a/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj +++ b/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj @@ -1,5 +1,5 @@  - + @@ -20,7 +20,7 @@ Properties SmartStore.Tax SmartStore.Tax - v4.5 + v4.5.1 512 @@ -42,6 +42,7 @@ + true @@ -81,57 +82,60 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll - False + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.dll - False + + False + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll - False + + False + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True - + + + + + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - + False ..\..\..\lib\Telerik\Telerik.Web.Mvc.dll diff --git a/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml b/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml index 5b8d09d71d..f3b670b3b2 100644 --- a/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml +++ b/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml @@ -1,10 +1,11 @@ -@{ - Layout = ""; -} -@model SmartStore.Tax.Models.ByRegionTaxRateListModel -@using SmartStore.Web.Framework; +@using SmartStore.Web.Framework; @using Telerik.Web.Mvc.UI; @using System.Linq; +@model SmartStore.Tax.Models.ByRegionTaxRateListModel +@{ + Layout = ""; +} +
    @@ -46,8 +47,8 @@
    -

    -

    + +

    @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) -
    -   - - -
    +
    +
    @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 diff --git a/src/Plugins/SmartStore.Tax/Views/Web.config b/src/Plugins/SmartStore.Tax/Views/Web.config index 31dc8df754..e9d36a3c51 100644 --- a/src/Plugins/SmartStore.Tax/Views/Web.config +++ b/src/Plugins/SmartStore.Tax/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.Tax/packages.config b/src/Plugins/SmartStore.Tax/packages.config index 5be52ae422..7f794ac209 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 bb5615f21e..ba87d0f098 100644 --- a/src/Plugins/SmartStore.Tax/web.config +++ b/src/Plugins/SmartStore.Tax/web.config @@ -1,117 +1,117 @@ - + - - + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs new file mode 100644 index 0000000000..d2b92900b9 --- /dev/null +++ b/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs @@ -0,0 +1,303 @@ +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 index 49785de800..3b7e734583 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/AddressesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/AddressesController.cs @@ -73,14 +73,16 @@ public SingleResult
    GetAddress(int key) // navigation properties - public Country GetCountry(int key) + [WebApiQueryable] + public SingleResult GetCountry(int key) { - return GetExpandedProperty(key, x => x.Country); + return GetRelatedEntity(key, x => x.Country); } - public StateProvince GetStateProvince(int key) + [WebApiQueryable] + public SingleResult GetStateProvince(int key) { - return GetExpandedProperty(key, x => x.StateProvince); + 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 index aa72f64388..9d252befa9 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/CategoriesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/CategoriesController.cs @@ -67,9 +67,7 @@ public SingleResult GetCategory(int key) [WebApiQueryable] public IQueryable GetAppliedDiscounts(int key) { - var entity = GetExpandedEntity>(key, x => x.AppliedDiscounts); - - return entity.AppliedDiscounts.AsQueryable(); + 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 index cf78613bd4..15f58522a4 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/CountriesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/CountriesController.cs @@ -36,9 +36,7 @@ public SingleResult GetCountry(int key) [WebApiQueryable] public IQueryable GetStateProvinces(int key) { - var entity = GetExpandedEntity>(key, x => x.StateProvinces); - - return entity.StateProvinces.AsQueryable(); + return GetRelatedCollection(key, x => x.StateProvinces); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/CustomersController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/CustomersController.cs index 317c3e09da..c811dd60cf 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/CustomersController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/CustomersController.cs @@ -44,14 +44,16 @@ public SingleResult GetCustomer(int key) // navigation properties - public Address GetBillingAddress(int key) + [WebApiQueryable] + public SingleResult
    GetBillingAddress(int key) { - return GetExpandedProperty
    (key, x => x.BillingAddress); + return GetRelatedEntity(key, x => x.BillingAddress); } - public Address GetShippingAddress(int key) + [WebApiQueryable] + public SingleResult
    GetShippingAddress(int key) { - return GetExpandedProperty
    (key, x => x.ShippingAddress); + return GetRelatedEntity(key, x => x.ShippingAddress); } //public Language GetLanguage(int key) @@ -67,25 +69,19 @@ public Address GetShippingAddress(int key) [WebApiQueryable] public IQueryable GetOrders(int key) { - var entity = GetExpandedEntity>(key, x => x.Orders); - - return entity.Orders.AsQueryable(); + return GetRelatedCollection(key, x => x.Orders); } [WebApiQueryable] public IQueryable GetReturnRequests(int key) { - var entity = GetExpandedEntity>(key, x => x.ReturnRequests); - - return entity.ReturnRequests.AsQueryable(); + return GetRelatedCollection(key, x => x.ReturnRequests); } [WebApiQueryable] public IQueryable
    GetAddresses(int key) { - var entity = GetExpandedEntity>(key, x => x.Addresses); - - return entity.Addresses.AsQueryable(); + return GetRelatedCollection(key, x => x.Addresses); } // actions diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/DiscountsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/DiscountsController.cs index de6badcc25..eeb0d3d448 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/DiscountsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/DiscountsController.cs @@ -37,9 +37,7 @@ public SingleResult GetDiscount(int key) [WebApiQueryable] public IQueryable GetAppliedToCategories(int key) { - var entity = GetExpandedEntity>(key, x => x.AppliedToCategories); - - return entity.AppliedToCategories.AsQueryable(); + return GetRelatedCollection(key, x => x.AppliedToCategories); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/LocalizedPropertysController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/LocalizedPropertysController.cs index 8aa403ce14..9f9c52edca 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/LocalizedPropertysController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/LocalizedPropertysController.cs @@ -31,9 +31,10 @@ public SingleResult GetLocalizedProperty(int key) // navigation properties - public Language GetLanguage(int key) + [WebApiQueryable] + public SingleResult GetLanguage(int key) { - return GetExpandedProperty(key, x => x.Language); + return GetRelatedEntity(key, x => x.Language); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderItemsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderItemsController.cs index bdbe03a781..2e608f15bc 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderItemsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderItemsController.cs @@ -41,14 +41,16 @@ public SingleResult GetOrderItem(int key) // navigation properties - public Order GetOrder(int key) + [WebApiQueryable] + public SingleResult GetOrder(int key) { - return GetExpandedProperty(key, x => x.Order); + return GetRelatedEntity(key, x => x.Order); } - public Product GetProduct(int key) + [WebApiQueryable] + public SingleResult GetProduct(int key) { - return GetExpandedProperty(key, x => x.Product); + 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 index 99bc1502c3..354204fb0e 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderNotesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderNotesController.cs @@ -23,9 +23,10 @@ public SingleResult GetOrderNote(int key) // navigation properties - public Order GetOrder(int key) + [WebApiQueryable] + public SingleResult GetOrder(int key) { - return GetExpandedProperty(key, x => x.Order); + 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 index f86745f368..f0f1c0eb9b 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs @@ -55,67 +55,66 @@ public SingleResult GetOrder(int key) // navigation properties - public Customer GetCustomer(int key) + [WebApiQueryable] + public SingleResult GetCustomer(int key) { - return GetExpandedProperty(key, x => x.Customer); + return GetRelatedEntity(key, x => x.Customer); } - public Address GetBillingAddress(int key) + [WebApiQueryable] + public SingleResult
    GetBillingAddress(int key) { - return GetExpandedProperty
    (key, x => x.BillingAddress); + return GetRelatedEntity(key, x => x.BillingAddress); } - public Address GetShippingAddress(int key) + [WebApiQueryable] + public SingleResult
    GetShippingAddress(int key) { - return GetExpandedProperty
    (key, x => x.ShippingAddress); + return GetRelatedEntity(key, x => x.ShippingAddress); } [WebApiQueryable] public IQueryable GetOrderNotes(int key) { - var entity = GetExpandedEntity>(key, x => x.OrderNotes); //var entity = GetEntityByKeyNotNull(key); // if ProxyCreationEnabled = true - - return entity.OrderNotes.AsQueryable(); + return GetRelatedCollection(key, x => x.OrderNotes); } [WebApiQueryable] public IQueryable GetShipments(int key) { - var entity = GetExpandedEntity>(key, x => x.Shipments); - - return entity.Shipments.AsQueryable(); + return GetRelatedCollection(key, x => x.Shipments); } [WebApiQueryable] public IQueryable GetOrderItems(int key) { - var entity = GetExpandedEntity>(key, x => x.OrderItems); - - return entity.OrderItems.AsQueryable(); + return GetRelatedCollection(key, x => x.OrderItems); } // actions [HttpPost] - public Order PaymentPending(int key) + public SingleResult PaymentPending(int key) { - var entity = GetEntityByKeyNotNull(key); + var result = GetSingleResult(key); + var order = GetExpandedEntity(key, result, null); this.ProcessEntity(() => { - entity.PaymentStatus = PaymentStatus.Pending; - Service.UpdateOrder(entity); + order.PaymentStatus = PaymentStatus.Pending; + Service.UpdateOrder(order); return null; }); - return entity; + return result; } [HttpPost] - public Order PaymentPaid(int key, ODataActionParameters parameters) + public SingleResult PaymentPaid(int key, ODataActionParameters parameters) { - var entity = GetEntityByKeyNotNull(key); + var result = GetSingleResult(key); + var order = GetExpandedEntity(key, result, null); this.ProcessEntity(() => { @@ -123,20 +122,23 @@ public Order PaymentPaid(int key, ODataActionParameters parameters) if (paymentMethodName != null) { - entity.PaymentMethodSystemName = paymentMethodName; - Service.UpdateOrder(entity); + order.PaymentMethodSystemName = paymentMethodName; + Service.UpdateOrder(order); } - _orderProcessingService.Value.MarkOrderAsPaid(entity); + _orderProcessingService.Value.MarkOrderAsPaid(order); + return null; }); - return entity; + + return result; } [HttpPost] - public Order PaymentRefund(int key, ODataActionParameters parameters) + public SingleResult PaymentRefund(int key, ODataActionParameters parameters) { - var entity = GetEntityByKeyNotNull(key); + var result = GetSingleResult(key); + var order = GetExpandedEntity(key, result, null); this.ProcessEntity(() => { @@ -144,32 +146,61 @@ public Order PaymentRefund(int key, ODataActionParameters parameters) if (online) { - var errors = _orderProcessingService.Value.Refund(entity); + var errors = _orderProcessingService.Value.Refund(order); if (errors.Count > 0) return errors[0]; } else { - _orderProcessingService.Value.RefundOffline(entity); + _orderProcessingService.Value.RefundOffline(order); } return null; }); - return entity; + + 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 Order Cancel(int key) + public SingleResult AddShipment(int key, ODataActionParameters parameters) { - var entity = GetEntityByKeyNotNull(key); + var result = GetSingleResult(key); + var order = GetExpandedEntity(key, result, "OrderItems, OrderItems.Product, Shipments, Shipments.ShipmentItems"); this.ProcessEntity(() => { - _orderProcessingService.Value.CancelOrder(entity, true); + 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 entity; + + return result; } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/PaymentMethodsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/PaymentMethodsController.cs new file mode 100644 index 0000000000..ccc87cfad6 --- /dev/null +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/PaymentMethodsController.cs @@ -0,0 +1,35 @@ +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 index 26e4025c27..9e23c2959f 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/PicturesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/PicturesController.cs @@ -37,9 +37,7 @@ public SingleResult GetPicture(int key) [WebApiQueryable] public IQueryable GetProductPictures(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductPictures); - - return entity.ProductPictures.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductPictures); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductBundleItemsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductBundleItemsController.cs index 3d86242624..a24d207821 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductBundleItemsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductBundleItemsController.cs @@ -27,18 +27,20 @@ protected override void Delete(ProductBundleItem entity) public SingleResult GetProductBundleItem(int key) { return GetSingleResult(key); - } + } // navigation properties - public Product GetProduct(int key) + [WebApiQueryable] + public SingleResult GetProduct(int key) { - return GetExpandedProperty(key, x => x.Product); + return GetRelatedEntity(key, x => x.Product); } - public Product GetBundleProduct(int key) + [WebApiQueryable] + public SingleResult GetBundleProduct(int key) { - return GetExpandedProperty(key, x => x.BundleProduct); + 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 index a7e393e205..822b0d4d62 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeCombinationsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeCombinationsController.cs @@ -32,9 +32,10 @@ public SingleResult GetProductVariantAttribu // navigation properties - public DeliveryTime GetDeliveryTime(int key) + [WebApiQueryable] + public SingleResult GetDeliveryTime(int key) { - return GetExpandedProperty(key, x => x.DeliveryTime); + 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 index b2f6a9e3ff..ad1f54efe7 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeValuesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeValuesController.cs @@ -31,9 +31,10 @@ public SingleResult GetProductVariantAttributeValu // navigation properties - public ProductVariantAttribute GetProductVariantAttribute(int key) + [WebApiQueryable] + public SingleResult GetProductVariantAttribute(int key) { - return GetExpandedProperty(key, x => x.ProductVariantAttribute); + 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 index 9218f73e6d..487f636560 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributesController.cs @@ -33,17 +33,16 @@ public SingleResult GetProductVariantAttribute(int key) // navigation properties - public ProductAttribute GetProductAttribute(int key) + [WebApiQueryable] + public SingleResult GetProductAttribute(int key) { - return GetExpandedProperty(key, x => x.ProductAttribute); + return GetRelatedEntity(key, x => x.ProductAttribute); } [WebApiQueryable] public IQueryable GetProductVariantAttributeValues(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductVariantAttributeValues); - - return entity.ProductVariantAttributeValues.AsQueryable(); + 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 index 02caa63ae3..df8dfd389d 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs @@ -1,8 +1,11 @@ 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; @@ -24,17 +27,23 @@ public class ProductsController : WebApiEntityController _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 productAttributeService, + Lazy categoryService, + Lazy manufacturerService) { _workContext = workContext; _priceCalculationService = priceCalculationService; _urlRecordService = urlRecordService; _productAttributeService = productAttributeService; + _categoryService = categoryService; + _manufacturerService = manufacturerService; } protected override IQueryable GetEntitySet() @@ -79,99 +88,136 @@ public SingleResult GetProduct(int key) // navigation properties - public DeliveryTime GetDeliveryTime(int key) + public HttpResponseMessage NavigationProductCategories(int key, int relatedKey) { - return GetExpandedProperty(key, x => x.DeliveryTime); + 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 QuantityUnit GetQuantityUnit(int key) + public HttpResponseMessage NavigationProductManufacturers(int key, int relatedKey) { - return GetExpandedProperty(key, x => x.QuantityUnit); + 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); } - public Download GetSampleDownload(int key) + [WebApiQueryable] + public SingleResult GetDeliveryTime(int key) { - return GetExpandedProperty(key, x => x.SampleDownload); + return GetRelatedEntity(key, x => x.DeliveryTime); } [WebApiQueryable] - public IQueryable GetProductCategories(int key) + public SingleResult GetQuantityUnit(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductCategories); + return GetRelatedEntity(key, x => x.QuantityUnit); + } - return entity.ProductCategories.AsQueryable(); + [WebApiQueryable] + public SingleResult GetSampleDownload(int key) + { + return GetRelatedEntity(key, x => x.SampleDownload); } [WebApiQueryable] - public IQueryable GetProductManufacturers(int key) + public IQueryable GetProductCategories(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductManufacturers); + return GetRelatedCollection(key, x => x.ProductCategories); + } - return entity.ProductManufacturers.AsQueryable(); + [WebApiQueryable] + public IQueryable GetProductManufacturers(int key) + { + return GetRelatedCollection(key, x => x.ProductManufacturers); } [WebApiQueryable] public IQueryable GetProductPictures(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductPictures); - - return entity.ProductPictures.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductPictures); } [WebApiQueryable] public IQueryable GetProductSpecificationAttributes(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductSpecificationAttributes); - - return entity.ProductSpecificationAttributes.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductSpecificationAttributes); } [WebApiQueryable] public IQueryable GetProductTags(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductTags); - - return entity.ProductTags.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductTags); } [WebApiQueryable] public IQueryable GetTierPrices(int key) { - var entity = GetExpandedEntity>(key, x => x.TierPrices); - - return entity.TierPrices.AsQueryable(); + return GetRelatedCollection(key, x => x.TierPrices); } [WebApiQueryable] public IQueryable GetAppliedDiscounts(int key) { - var entity = GetExpandedEntity>(key, x => x.AppliedDiscounts); - - return entity.AppliedDiscounts.AsQueryable(); + return GetRelatedCollection(key, x => x.AppliedDiscounts); } [WebApiQueryable] public IQueryable GetProductVariantAttributes(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductVariantAttributes); - - return entity.ProductVariantAttributes.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductVariantAttributes); } [WebApiQueryable] public IQueryable GetProductVariantAttributeCombinations(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductVariantAttributeCombinations); - - return entity.ProductVariantAttributeCombinations.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductVariantAttributeCombinations); } [WebApiQueryable] public IQueryable GetProductBundleItems(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductBundleItems); - - return entity.ProductBundleItems.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductBundleItems); } // actions @@ -188,8 +234,9 @@ public IQueryable GetProductBundleItems(int key) { if (entity.ProductType == ProductType.GroupedProduct) { - var searchContext = new ProductSearchContext() + var searchContext = new ProductSearchContext { + OrderBy = ProductSortingEnum.Position, Query = this.GetExpandedEntitySet(requiredProperties), ParentGroupedProductId = entity.Id, PageSize = int.MaxValue, @@ -199,17 +246,17 @@ public IQueryable GetProductBundleItems(int key) Product lowestPriceProduct; var associatedProducts = Service.PrepareProductSearchQuery(searchContext); - result = _priceCalculationService.Value.GetLowestPrice(entity, associatedProducts, out lowestPriceProduct); + result = _priceCalculationService.Value.GetLowestPrice(entity, null, associatedProducts, out lowestPriceProduct); } else { bool displayFromMessage; - result = _priceCalculationService.Value.GetLowestPrice(entity, out displayFromMessage); + result = _priceCalculationService.Value.GetLowestPrice(entity, null, out displayFromMessage); } } else { - result = _priceCalculationService.Value.GetFinalPrice(entity, null, _workContext.Value.CurrentCustomer, decimal.Zero, true, 1); + result = _priceCalculationService.Value.GetPreselectedPrice(entity, null); } return null; }); @@ -345,22 +392,5 @@ public IQueryable ManageAttributes(int key, ODataAction return entity.ProductVariantAttributes.AsQueryable(); } - - - //[HttpGet, WebApiQueryable] - //public IQueryable GetRelatedProducts(int key) - //{ - // if (!ModelState.IsValid) - // throw this.ExceptionInvalidModelState(); - - // var repository = EngineContext.Current.Resolve>(); - - // var query = - // from x in repository.Table - // where x.ProductId1 == key - // select x; - - // return query; - //} } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ReturnRequestsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ReturnRequestsController.cs index 949b9b067b..fbcdd389b0 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ReturnRequestsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ReturnRequestsController.cs @@ -24,9 +24,10 @@ public SingleResult GetReturnRequest(int key) // navigation properties - public Customer GetCustomer(int key) + [WebApiQueryable] + public SingleResult GetCustomer(int key) { - return GetExpandedProperty(key, x => x.Customer); + return GetRelatedEntity(key, x => x.Customer); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentItemsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentItemsController.cs new file mode 100644 index 0000000000..47fdf9982a --- /dev/null +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentItemsController.cs @@ -0,0 +1,32 @@ +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/ShippingMethodsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ShippingMethodsController.cs index 0f4f6aef90..ab13899ff9 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ShippingMethodsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ShippingMethodsController.cs @@ -37,9 +37,7 @@ public SingleResult GetShippingMethod(int key) [WebApiQueryable] public IQueryable GetRestrictedCountries(int key) { - var entity = GetExpandedEntity>(key, x => x.RestrictedCountries); - - return entity.RestrictedCountries.AsQueryable(); + 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 index f4e66b5ddd..e377bb9fff 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributeOptionsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributeOptionsController.cs @@ -33,17 +33,16 @@ public SingleResult GetSpecificationAttributeOptio // navigation properties - public SpecificationAttribute GetSpecificationAttribute(int key) + [WebApiQueryable] + public SingleResult GetSpecificationAttribute(int key) { - return GetExpandedProperty(key, x => x.SpecificationAttribute); + return GetRelatedEntity(key, x => x.SpecificationAttribute); } [WebApiQueryable] public IQueryable GetProductSpecificationAttributes(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductSpecificationAttributes); - - return entity.ProductSpecificationAttributes.AsQueryable(); + 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 index 7e9c084a9d..3990c280d8 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributesController.cs @@ -36,9 +36,7 @@ public SingleResult GetSpecificationAttribute(int key) [WebApiQueryable] public IQueryable GetSpecificationAttributeOptions(int key) { - var entity = GetExpandedEntity>(key, x => x.SpecificationAttributeOptions); - - return entity.SpecificationAttributeOptions.AsQueryable(); + 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 index 1c26bdacb6..f01dccfec4 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/StateProvincesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/StateProvincesController.cs @@ -31,9 +31,10 @@ public SingleResult GetStateProvince(int key) // navigation properties - public Country GetCountry(int key) + [WebApiQueryable] + public SingleResult GetCountry(int key) { - return GetExpandedProperty(key, x => x.Country); + return GetRelatedEntity(key, x => x.Country); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/SyncMappingsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/SyncMappingsController.cs new file mode 100644 index 0000000000..bccd10dcaa --- /dev/null +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/SyncMappingsController.cs @@ -0,0 +1,33 @@ +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/WebApiController.cs b/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs index e5b8fcaad1..4fb254bbff 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs @@ -4,7 +4,10 @@ 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; @@ -85,7 +88,7 @@ public ActionResult SaveGeneralSettings(WebApiConfigModel model) model.Copy(_webApiSettings, false); _services.Settings.SaveSetting(_webApiSettings); - WebApiCaching.Remove(WebApiControllingCacheData.Key); + WebApiCachingControllingData.Remove(); return Configure(); } @@ -94,7 +97,7 @@ public ActionResult SaveGeneralSettings(WebApiConfigModel model) public ActionResult GridUserData(GridCommand command) { if (!HasPermission()) - return new JsonResult { Data = new GridModel() { Data = new List() }}; + return new JsonResult { Data = new GridModel { Data = new List() } }; var model = _webApiPluginService.GetGridModel(command.Page - 1, command.PageSize); diff --git a/src/Plugins/SmartStore.WebApi/Description.txt b/src/Plugins/SmartStore.WebApi/Description.txt index e66c0bc6dd..646ac2fd0d 100644 --- a/src/Plugins/SmartStore.WebApi/Description.txt +++ b/src/Plugins/SmartStore.WebApi/Description.txt @@ -1,10 +1,10 @@ FriendlyName: SmartStore.NET Web Api SystemName: SmartStore.WebApi -Version: 1.32 +Version: 2.6.0 Group: Api -MinAppVersion: 2.1.0 +MinAppVersion: 2.5.0 Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.WebApi.dll ResourceRootKey: Plugins.Api.WebApi -Url: http://community.smartstore.com/index.php?/files/file/27-smartstorenet-web-api/ \ No newline at end of file +Url: http://community.smartstore.com/marketplace/file/27-smartstorenet-web-api/ \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Docs/Developer-Guide.md b/src/Plugins/SmartStore.WebApi/Docs/Developer-Guide.md deleted file mode 100644 index 900bc6f0d1..0000000000 --- a/src/Plugins/SmartStore.WebApi/Docs/Developer-Guide.md +++ /dev/null @@ -1,492 +0,0 @@ -# Introduction -The SmartStore.Net Web API allows direct access to the data of an online store. It is build on the most recent technologies Microsoft offers for web based data consuming, the ASP.NET Web API and the OData provider. This documentation gives you an overview of how to build an API client, also known as API consumer. It uses C# code but you can build your consumer in any common programming language. As OData is a standardized protocol there are a lot of [frameworks and toolkits][4] available for various platforms. - -The ASP.NET Web API is a framework for building web APIs on top of the .NET framework. It uses HTTP as an application protocol (rather than a transport protocol) to return data based on the client requests. The Web API is able to return the data appropriately based on the media type specified with the request. By default it provides JSON and XML based responses. - -The [Open Data Protocol (OData)][5] is a standardized web protocol that provides an uniform way to expose, structure, query and manipulate data using REST practices. OData also provides an uniform way to represent metadata about the data, allowing clients to know more about the type system, relationships and structure of the data. - -# Prerequisites -The SmartStore.Net Web API requires configuration by the storekeeper to go into action. First of all he must install the Web API plugin in the backend of SmartStore.Net. The plugin technology gives him the opportunity to activate or deactivate the entire Web API at any time without any influence on the online store. - -Next step is to configure the API on the plugin's configuration page. The main thing here is to provide individual members access to the API and the data of the online store. Therefore the storekeeper can create a public and a secret key for each registered member. Only a registered member with both keys has access to the API. To exclude a member from the API the storekeeper can either delete the keys of the member (permanent exclusion) or disable them (temporary exclusion). Roles and rights of a member are taken into consideration when accessing data via the API. - -The consumer must transmit the public key through a custom HTTP header field. It identifies the member who is accessing the API. The secret key on the other hand should never ever be send over the wire! It is secret to the storekeeper and the member who is accessing the Web API. It is only used for encryption as described in the following chapters. - -# The SmartStore.Net Web API in detail -You can consume API services through HTTP calls in a RESTful manner by using HTTP methods GET, POST (insert), PUT (update), PATCH (partially update) and DELETE. OData options (like $filter, $top, $select etc.) and API specific options (like SmNetFulfillCountry) can be transfered through query strings. The web API offers two services. The OData service (path `/odata/v1/`) provides all entity related data and the API service (path `/api/v1/`) provides all remaining resources. - -Paging is required if you want to query multiple records. You can do that with OData $skip and $top. The maximum value for $top is returned in SmartStore-Net-Api-MaxTop header field. - -A request body needs to be UTF8 encoded. - -The OData metadata document ------- -The metadata document describes the entity data model (EDM) of the OData service, using an XML language called the Conceptual Schema Definition Language (CSDL). The metadata document shows the structure of the data in the OData service and can be used to generate client code. It is the recommended overview for the consumer to indicate where to find a particular resource. To get the metadata document, send a - - GET http://localhost:1260/odata/v1/$metadata - -Request HTTP header fields ------- -**User-Agent** (optional): Short description of the API consumer. Example: `My shopping data consumer v.1.0` - -**Accept** (required): The wanted response format. Example for JSON: `application/json, text/javascript, */*`. Example for XML: `application/atom+xml,application/atomsvc+xml,application/xml` - -**Accept-Charset** (required): Always UTF-8. - -**Content-Type** and **Content-Length** (conditional): Necessary for the methods POST, PUT, and PATCH, if new data is send via the HTTP body. Example: `application/json; charset=utf-8` - -**Content-MD5** (optional): For methods POST, PUT, and PATCH. Authentication provides the error `ContentMd5NotMatching` if the hash does not match the one calculated by the server. This header field has no safety relevance, because the content carried out using the HMAC signature. Example: `lgifXydL3FhffpTIilkwOw==` - -**SmartStore-Net-Api-Date** (required): Date and time of the request as coordinated universal time (UTC). Use ISO-8601 format including milliseconds. Example: `2013-11-09T11:42:48.4715986Z` - -**SmartStore-Net-Api-PublicKey** (required): The public key of a member. Example: `0c6b33651708eb09c8a8d6036b79d739` - -**Authorization** (required): The authorization schema and the HMAC signature, separated by a space. The schema is SmNetHmac plus its version. Example: `SmNetHmac1 +yvONYvJmQl19omu1uE3HVlQ7afd7Qqkk8DrNrfUbe8=` - -Example of a complete request header: - - User-Agent: My shopping data consumer v.1.0 - Accept: application/json, text/javascript, */* - Accept-Charset: UTF-8 - SmartStore-Net-Api-PublicKey: 0c6b33651708eb09c8a8d6036b79d739 - SmartStore-Net-Api-Date: 2013-11-09T11:42:48.4715986Z - Content-Type: application/json; charset=utf-8 - Content-MD5: lgifXydL3FhffpTIilkwOw== - Authorization: SmNetHmac1 +yvONYvJmQl19omu1uE3HVlQ7afd7Qqkk8DrNrfUbe8= - - -Response HTTP header fields ------- -**SmartStore-Net-Api-Version**: The highest API version supported by the server (unsigned integer) and the version of the installed API plugin (floating-point value). The API version is only increased when there is a fundamental break in API development without the ability of downward compatibility. The plugin version is typically increased when the API has been extended, for example when new ressouces were added. -Example: `1 1.0` - -**SmartStore-Net-Api-Date**: The current server date and time in ISO-8601 UTC. Example: `2013-11-11T14:35:33.7772907Z` - -**SmartStore-Net-Api-MaxTop**: The maximum value for OData $top option. Required for client driven paging. - -**SmartStore-Net-Api-HmacResultDesc**: Short description of the result of the HMAC authentication. Only returned if the authentication failed. Example: `InvalidTimestamp` - -**SmartStore-Net-Api-HmacResultId**: Unsigned ID that represents the result of the HMAC authentication. Only returned if the authentication failed. Example: `5` - -**WWW-Authenticate**: The name of the authentication method that failed. Note that CORS requests can lead to multiple of this header fields. The SmartStore.Net Web API returns `SmNetHmac1` if its authentication failed. - -List with HmacResultId and HmacResultDesc: - -- 0 Success -- 1 FailedForUnknownReason -- 2 ApiUnavailable -- 3 InvalidAuthorizationHeader -- 4 InvalidSignature -- 5 InvalidTimestamp -- 6 TimestampOutOfPeriod -- 7 TimestampOlderThanLastRequest -- 8 MissingMessageRepresentationParameter -- 9 ContentMd5NotMatching -- 10 UserUnknown -- 11 UserDisabled -- 12 UserInvalid -- 13 UserHasNoPermission - -Frequent HTTP response status codes ------- -**200 OK**: Standard response for successful HTTP requests. - -**201 Created**: The request has been fulfilled and resulted in a new resource being created. - -**204 No Content**: The server successfully processed the request, but is not returning any content. Usually used as a response to a successful delete request. - -**400 BadRequest**: There is something wrong with the request, for instance an invalid or missing parameter. - -**401 Unauthorized**: Authentication failed or the user is not authorized (in case of UserHasNoPermission). There is no content returned because no access can be granted to the client. - -**403 Forbidden**: The resource cannot be accessed through the API, for example for security reasons. - -**404 Not found**: The requested ressource cannot be found. - -**406 Not Acceptable**: The requested resource is only capable of generating content not acceptable according to the accept headers sent in the request. Typically generated by OData provider. - -**422 Unprocessable entity**: The request is ok but its processing failed due to sematic issues or unprocessable instructions. For example putting the payment status of an order to an unknown value. - -**500 Internal Server Error**: A generic error message, given when no more specific message is suitable. - -**501 Not implemented**: Accessing the ressource through the API is not implemented (yet). - -Query options ------- -[OData query options][2] allows to manipulate the querying of data like sorting, filtering, paging etc. They are send in the query string of the request URL. A more detailed overview can be found [here][3]. - -Custom query options should lighten the work with the SmartStore.Net Web API, especially when you work with entity relationships. - -**SmNetFulfill{property_name}**: Entities are often in multiple relationships with other entities. In most cases an ID has to be set to create or to change such a relation. But mostly this ID is unknown to you. To reduce the amount of API round trips this option can set an entity relation indirectly. Example: You want to add a german address but you don't know the ID of the german country entity which is required for inserting an address. Rather than calling the API again to get the ID you can add the query option SmNetFulfillCountry=DE and the API resolves the relationship automatically. The API can fulfill the following properties: - -- Country: The two or three letter ISO country code. Example: SmNetFulfillCountry=USA -- StateProvince: The abbreviation of a state province. Example for California: SmNetFulfillStateProvince=CA -- Language: The culture of a language. Example for German: SmNetFulfillLanguage=de-DE -- Currency: The ISO code of a currency. Example for Euro: SmNetFulfillCurrency=EUR - -# HMAC authentication -The SmartStore.Net Web API uses the HMAC authentication method to protect data from unauthorized access. - -HMAC (hash-based message authentication code) is a sessionless authentication method, where the integrity of a resource request is guaranteed through a this request representing code (called message representation) and a cryptological hash function. The procedure is considered to be very safe. It is used for example by Amazon to protect their S3 Web services. - -SmartStore.Net uses SHA-256 as hash function, which is why this authentication procedure is often called HMAC-SHA256. - -The consumer must transmit a timestamp with each request which may not differ too far from the server time, otherwise the authentication will fail. The storekeeper can configure this time window in the plugin configuration. The default value is 15 minutes. To prevent replay attacks the timestamp must be younger than the one of a previous request. - - -Message representation ------- -The message representation is a concatenation of various informations separated by newline characters. All values are required but can be empty (in case of Content-MD5). It must be build as follows: - - HTTP method\n - Content-MD5\n - Response content type (accept header)\n - Canonicalized URI\n - ISO-8601 UTC timestamp\n - Public key - -**HTTP method**: In lower case. Example: `post` - -**Content-MD5**: The base64 encoded MD5 hash of the HTTP body, so `base64(md5Hash(content))`. For GET requests this fied is empty. The value should be equal to the optional Content-MD5 request header field. For example the MD5 hash for the content - - {"OrderId":152,"Note":"Hello world!","DisplayToCustomer":false,"CreatedOnUtc":"2013-11-09T11:15:00"} - -is `lgifXydL3FhffpTIilkwOw==`. - -**Response content type**: The value of the accept header field. In lower case. Example: `application/json, text/javascript, */*` - -**Canonicalized URI**: The complete URI of the requested resource (including query string). In lower case. Example: `http://localhost:1260/odata/v1/ordernotes` - -**ISO-8601 UTC timestamp**: Current UTC date and time including milliseconds. Must be equal to the request header field SmartStore-Net-Api-Date. Example: `2013-11-09T11:37:21.1918793Z`. The API accepts milliseconds with 7 and 3 digits. Can be created in C# as follows: `string timestamp = DateTime.UtcNow.ToString("o");` - -**Public key**: The public key of the user. In lower case. Example: `0c6b33651708eb09c8a8d6036b79d739` - -A complete message representation might look like this: - - post - lgifXydL3FhffpTIilkwOw== - application/json, text/javascript, */* - http://localhost:1260/odata/v1/ordernotes - 2013-11-09T11:42:48.4715986Z - 0c6b33651708eb09c8a8d6036b79d739 - -HMAC signature ------- -The signature is the computed hash of the message representation by using SHA-256 and the user's secret key. Always UTF8 encode the secret key and the message representation while calculating the hash. Example of creating the HMAC signature: - - public string CreateSignature(string secretKey, string messageRepresentation) - { - if (string.IsNullOrWhiteSpace(secretKey) || string.IsNullOrWhiteSpace(messageRepresentation)) - return ""; - - string signature; - var secretBytes = Encoding.UTF8.GetBytes(secretKey); - var valueBytes = Encoding.UTF8.GetBytes(messageRepresentation); - - using (var hmac = new HMACSHA256(secretBytes)) - { - var hash = hmac.ComputeHash(valueBytes); - signature = Convert.ToBase64String(hash); - } - return signature; - } - -The signature is transmitted as a base64 encoded string together with the schema using authorization header field. -For above message representation and the secret key `3025c89ebaab20b71e0e42744239bf50` the authorization header field is - - Authorization: SmNetHmac1 +yvONYvJmQl19omu1uE3HVlQ7afd7Qqkk8DrNrfUbe8= - -# Code examples -See also [this tutorial][1] for more information about using the ASP.NET web API client libraries. - -Making a GET request ------- -Let us read orders created after a particular date. For security reasons query results needs to be paged, so we have to specify the $top option (and optionally $skip). - - string publicKey = "0c6b33651708eb09c8a8d6036b79d739"; - string secretKey = "3025c89ebaab20b71e0e42744239bf50"; - string method = "get"; - string accept = "application/json, text/javascript, */*"; - string timestamp = DateTime.UtcNow.ToString("o"); // 2013-11-11T10:15:54.1731069Z - string url = "http://localhost:1260/odata/v1/Orders?$top=10&$filter=CreatedOnUtc gt datetime'2013-02-20T00:00:00'"; - -First we create the message representation. - - string messageRepresentation = string.Join("\n", - get.ToLower(), - "", - accept.ToLower(), - url.ToLower(), - timestamp, - publicKey.ToLower() - ); - -It looks like: - - get - - application/json, text/javascript, */* - http://localhost:1260/odata/v1/orders?$top=10&$filter=createdonutc gt datetime'2013-02-20t00:00:00' - 2013-11-11T10:15:54.1731069Z - 0c6b33651708eb09c8a8d6036b79d739 - -Now we can calculate the HMAC signature by using our secret key. - - string signature = CreateSignature(secretKey, messageRepresentation); // hWce6V2KA0kkB0GBbIK0GSw5QAcS3+vj+m+WN/8k9EE= - -We have all informations to setup the request, so we create a web request object and pass the required headers. - - var request = (HttpWebRequest)WebRequest.Create(url); - request.Method = method; - request.UserAgent = "My shopping data consumer v.1.0"; - request.Accept = accept; - request.Headers.Add("Accept-Charset", "UTF-8"); - request.Headers.Add("SmartStore-Net-Api-PublicKey", publicKey); - request.Headers.Add("SmartStore-Net-Api-Date", timestamp); - request.Headers.Add("Authorization", "SmNetHmac1 " + signature); - -The complete header looks like: - - User-Agent: My shopping data consumer v.1.0 - Accept: application/json, text/javascript, */* - Accept-Charset: UTF-8 - SmartStore-Net-Api-PublicKey: 0c6b33651708eb09c8a8d6036b79d739 - SmartStore-Net-Api-Date: 2013-11-11T10:15:54.1731069Z - Authorization: SmNetHmac1 hWce6V2KA0kkB0GBbIK0GSw5QAcS3+vj+m+WN/8k9EE= - -Making a POST request ------- -Posting means inserting data via API. This example shows how to add a new order note "Hello world!" to the order with ID 152. Here is the function to create the MD5 hash of the request body: - - public string CreateContentMd5Hash(byte[] content) - { - string result = ""; - if (content != null && content.Length > 0) - { - using (var md5 = MD5.Create()) - { - byte[] hash = md5.ComputeHash(content); - result = Convert.ToBase64String(hash); - } - } - return result; - } - -All other variables have not changed. - - string content = "{\"OrderId\":152,\"Note\":\"Hello world!\",\"DisplayToCustomer\":false,\"CreatedOnUtc\":\"2013-11-09T11:15:00\"}"; - - byte[] data = Encoding.UTF8.GetBytes(content); - string contentMd5Hash = CreateContentMd5Hash(data); - - string method = "post"; - string timestamp = DateTime.UtcNow.ToString("o"); // 2013-11-11T19:44:04.9378268Z - string url = "http://localhost:1260/odata/v1/OrderNotes"; - -We add the same header fields as in the previous example and additionally: - - request.ContentLength = data.Length; - request.ContentType = "application/json; charset=utf-8"; - request.Headers.Add("Content-MD5", contentMd5Hash); // optional - -And we write the content into the request stream. - - using (var stream = request.GetRequestStream()) - { - stream.Write(data, 0, data.Length); - } - -The message representation is: - - post - lgifXydL3FhffpTIilkwOw== - application/json, text/javascript, */* - http://localhost:1260/odata/v1/ordernotes - 2013-11-11T19:44:04.9378268Z - 0c6b33651708eb09c8a8d6036b79d739 - -The header looks like: - - User-Agent: My shopping data consumer v.1.0 - Accept: application/json, text/javascript, */* - Accept-Charset: UTF-8 - SmartStore-Net-Api-PublicKey: 0c6b33651708eb09c8a8d6036b79d739 - SmartStore-Net-Api-Date: 2013-11-11T19:44:04.9378268Z - Content-Type: application/json; charset=utf-8 - Content-Length: 100 - Content-MD5: lgifXydL3FhffpTIilkwOw== - Authorization: SmNetHmac1 ejKxxtHNJYHCtBglZPg+cbSs3YTrA50pkfTHtVb1PMo= - -As a general rule POST, PUT and PATCH are returning the added or changed record. For example: - - { - "odata.metadata":"http://localhost:1260/odata/v1/$metadata#OrderNotes/@Element","OrderId":152,"Note":"Hello world!","DisplayToCustomer":false,"CreatedOnUtc":"2013-11-09T11:15:00","Id":692 - } - -Processing the response ------- -Example of reading the response into a string: - - HttpWebResponse webResponse = null; - string response; - - try - { - webResponse = request.GetResponse() as HttpWebResponse; - using (var reader = new StreamReader(webResponse.GetResponseStream(), Encoding.UTF8)) - { - response = reader.ReadToEnd(); - } - } - catch (WebException wexc) { /* ... */ } - catch (Exception exc) { /* ... */ } - finally - { - if (webResponse != null) - { - webResponse.Close(); - webResponse.Dispose(); - } - } - -JSON data can be easily parsed into dynamic or strongly typed objects using [Json.NET][6]. This example deserialize a JSON string into a list of customers. - - public class Customer - { - public string Id { get; set; } - public string CustomerGuid { get; set; } - public string Email { get; set; } - // more properties... - } - - JObject json = JObject.Parse(response); - string metadata = (string)json["odata.metadata"]; - - if (!string.IsNullOrWhiteSpace(metadata) && metadata.EndsWith("#Customers")) - { - List customers = json["value"].Select(x => x.ToObject()).ToList(); - } - -Dynamic JSON parsing might look like this: - - dynamic dynamicJson = JObject.Parse(response); - - foreach (dynamic customer in dynamicJson.value) - { - string str = string.Format("{0} {1} {2}", customer.Id, customer.CustomerGuid, customer.Email); - Debug.WriteLine(str); - } - - -# Working with the product entity - -#### Getting a product with name *SmartStore eBay SmartSeller* - - GET http://localhost:1260/odata/v1/Products?$top=1&$filter=Name eq 'SmartStore eBay SmartSeller' - -#### Get child products of group product with ID 210 - - GET http://localhost:1260/odata/v1/Products?$top=120&$filter=ParentGroupedProductId eq 210 - -#### Get final price of product with ID 211 - - POST http://localhost:1260/odata/v1/Products(211)/FinalPrice - -Note the post method. There is a second action `LowestPrice` which serves the lowest possible price for a product. - -#### Managing product attributes - -You can use the following endpoints: `ProductAttributes` (types of attributes), `ProductVariantAttributes` (attribute types mapped to a product), `ProductVariantAttributeValues` (attribute values assigned to product) and optionally `ProductVariantAttributeCombinations` (additional information for particular attribute combinations). Because managing attributes that way can lead to some extra work, there is an action method `ManageAttributes` that wraps up the most important steps. - - POST http://localhost:1260/odata/v1/Products(211)/ManageAttributes - {"Synchronize":true,"Attributes":[ - {"Name":"Color","IsRequired":false,"Values":[ - {"Name":"Red"},{"Name":"Green","IsPreSelected":true},{"Name":"Blue"} - ]}, - {"Name":"Size","Values":[ - {"Name":"Large"},{"Name":"X-Large","IsPreSelected":true} - ]} - ]} - -That request configurates a product with the ID 211 with two attributes `Color` and `Size` and its values `Red, Green, Blue` and `Large, X-Large`. The action parameter `Attributes` is of type `ManageAttributeType`. See the OData metadata document for a complete list of available properties. If `Synchronize` is set to `false` only missing attributes and attribute values are inserted. If set to `true` existing records are also updated and values that are not included in the request body are removed from the database. If you pass an empty value array the attribute and all its values are removed from the product. - -The following method creates all possible attribute combinations for a product with the ID 211 - - POST http://localhost:1260/odata/v1/Products(211)/CreateAttributeCombinations - -As a first step this action always deletes all existing attribute combinations (for a product). - - -# More examples - -#### Get installed payment methods - - GET http://localhost:1260/api/v1/Payments/Methods?$filter=Installed eq true - -Note that payment methods in SmartStore.Net are plugins and not entities, so we must use the `/api/v1/` service here. - -#### Get return requests for customer with ID 1 - - GET http://localhost:1260/odata/v1/Customers(1)/ReturnRequests?$top=25&$inlinecount=allpages - -Note the inlinecount option which tells OData to return an odata.count value with total count of matching entities in the response. - -#### Mark order with ID 145 as payed - - POST http://localhost:1260/odata/v1/Orders(145)/PaymentPaid - {"PaymentMethodName":"Payments.Sofortueberweisung"} - -This request is called OData action because it is initiating further data processing on the server. -Note the second line which are optional OData action parameters. They are send in the request body, not in the query string. This example additionally set the system name of the payment method to `Payments.Sofortueberweisung` with which order 145 was balanced. - -Other actions that would change the payment status are `PaymentPending` and `PaymentRefund`. `PaymentRefund` supports the action parameter `Online`. *True* would call the related payment gateway to refund the payment. Use the action `Cancel` to cancel an order. - -#### Get shipping address for order 145 - - GET http://localhost:1260/odata/v1/Orders(145)/ShippingAddress - -This request is called OData navigation. Use `BillingAddress` if you want the billing address of an order to be returned. - -#### Partially update address with ID 1 - - PATCH http://localhost:1260/odata/v1/Addresses(1)?SmNetFulfillCountry=US&SmNetFulfillStateProvince=NY - {"City":"New York","Address1":"21 West 52nd Street","ZipPostalCode":"10021","FirstName":"John","LastName":"Doe"} - -The example uses SmNetFulfillCountry and SmNetFulfillStateProvince options to update the country (USA) and province (New York). That avoids extra querying of country and province records and passing its IDs in the request body. -Note that you cannot use a path `/Orders(145)/ShippingAddress` to update an address because `ShippingAddress` is a navigation property and updates are not supported here. - -#### Get email address of customer with ID 1 - - GET http://localhost:1260/odata/v1/Customers(1)/Email - -#### Get ID of store with name *my nice store* - - GET http://localhost:1260/odata/v1/Stores?$top=1&$filter=Name eq 'my nice store'&$select=Id - -Note the select option which tells OData just to return the Id property. - -#### Getting localized property values - - GET http://localhost:1260/odata/v1/LocalizedPropertys?$top=120&$filter=LocaleKeyGroup eq 'Product' and EntityId eq 224 and Language/LanguageCulture eq 'en-US' - -That's where OData filter enter the equation. That request filters all english property values for a product with the ID 224. LocaleKeyGroup is typically the entity name (Product, Category, Manufacturer, ProductBundleItem, DeliveryTime etc.). -The result looks like - - { - "odata.metadata":"http://localhost:1260/odata/v1/$metadata#LocalizedPropertys","value":[ - { - "EntityId":224,"LanguageId":1,"LocaleKeyGroup":"Product","LocaleKey":"Name","LocaleValue":"My name for the product","Id":111 - },{ - "EntityId":224,"LanguageId":1,"LocaleKeyGroup":"Product","LocaleKey":"FullDescription","LocaleValue":"

    Hello world! My product description goes here...

    ","Id":113 - }] - } - -LocaleKey is the context property name (here name and full description of the product entity). LocaleValue offers the localized value. - -
    -[1]: http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client -[2]: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options -[3]: http://www.odata.org/documentation/odata-v2-documentation/uri-conventions/#4_Query_String_Options -[4]: http://msopentech.com/odataorg/libraries/ -[5]: http://msopentech.com/odataorg/introduction/ -[6]: https://json.codeplex.com/ \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Extensions/MiscExtensions.cs b/src/Plugins/SmartStore.WebApi/Extensions/MiscExtensions.cs index 4680cb8782..3e754343db 100644 --- a/src/Plugins/SmartStore.WebApi/Extensions/MiscExtensions.cs +++ b/src/Plugins/SmartStore.WebApi/Extensions/MiscExtensions.cs @@ -1,9 +1,9 @@ using System.Text; using System.Web.Mvc; -namespace SmartStore.WebApi.Extensions +namespace SmartStore.WebApi { - public static class MiscExtensions + public static class PublicMiscExtensions { public static string ApiGridButtons(this HtmlHelper helper) { @@ -28,4 +28,17 @@ public static string ApiGridButtons(this HtmlHelper helper) 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 index 0159961db4..650e50d1ab 100644 --- a/src/Plugins/SmartStore.WebApi/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.WebApi/Localization/resources.de-de.xml @@ -1,5 +1,5 @@  - + Web-API @@ -30,10 +30,22 @@ 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. - Unauthorisierte Zugriffe speichern + Unautorisierte Zugriffe speichern - Jeder unauthorisierte Zugriff wird in der Ereignisliste gespeichert. + 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 diff --git a/src/Plugins/SmartStore.WebApi/Localization/resources.en-us.xml b/src/Plugins/SmartStore.WebApi/Localization/resources.en-us.xml index 4b7f67b5bf..7aafa97635 100644 --- a/src/Plugins/SmartStore.WebApi/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.WebApi/Localization/resources.en-us.xml @@ -1,5 +1,5 @@  - + Web-API @@ -35,6 +35,18 @@ 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 diff --git a/src/Plugins/SmartStore.WebApi/Models/Api/UploadFileBase.cs b/src/Plugins/SmartStore.WebApi/Models/Api/UploadFileBase.cs new file mode 100644 index 0000000000..d0884fa6a3 --- /dev/null +++ b/src/Plugins/SmartStore.WebApi/Models/Api/UploadFileBase.cs @@ -0,0 +1,74 @@ +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 new file mode 100644 index 0000000000..cca701246b --- /dev/null +++ b/src/Plugins/SmartStore.WebApi/Models/Api/UploadImage.cs @@ -0,0 +1,42 @@ +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 new file mode 100644 index 0000000000..ac43524577 --- /dev/null +++ b/src/Plugins/SmartStore.WebApi/Models/Api/UploadImportFile.cs @@ -0,0 +1,23 @@ +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 index 0688b8f792..23882c149e 100644 --- a/src/Plugins/SmartStore.WebApi/Models/WebApiConfigModel.cs +++ b/src/Plugins/SmartStore.WebApi/Models/WebApiConfigModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.WebApi; namespace SmartStore.WebApi.Models @@ -15,7 +15,13 @@ public class WebApiConfigModel : ModelBase [SmartResourceDisplayName("Plugins.Api.WebApi.ValidMinutePeriod")] public int ValidMinutePeriod { get; set; } - [SmartResourceDisplayName("Plugins.Api.WebApi.LogUnauthorized")] + [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; } @@ -25,11 +31,15 @@ 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 index 8888f3f803..2694f9166b 100644 --- a/src/Plugins/SmartStore.WebApi/Models/WebApiUserModel.cs +++ b/src/Plugins/SmartStore.WebApi/Models/WebApiUserModel.cs @@ -1,6 +1,6 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; using System; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.WebApi.Models { diff --git a/src/Plugins/SmartStore.WebApi/RouteProvider.cs b/src/Plugins/SmartStore.WebApi/RouteProvider.cs index 032a806c71..1ce5fb95cb 100644 --- a/src/Plugins/SmartStore.WebApi/RouteProvider.cs +++ b/src/Plugins/SmartStore.WebApi/RouteProvider.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; using SmartStore.Web.Framework.WebApi; namespace SmartStore.WebApi diff --git a/src/Plugins/SmartStore.WebApi/Services/WebApiPluginService.cs b/src/Plugins/SmartStore.WebApi/Services/WebApiPluginService.cs index a1b51c1bf7..8fb2385a8a 100644 --- a/src/Plugins/SmartStore.WebApi/Services/WebApiPluginService.cs +++ b/src/Plugins/SmartStore.WebApi/Services/WebApiPluginService.cs @@ -1,13 +1,13 @@ -using SmartStore.Core; +using System.Linq; +using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; -using SmartStore.WebApi.Models; using SmartStore.Services.Common; using SmartStore.Services.Customers; -using SmartStore.Web.Framework.WebApi; +using SmartStore.Web.Framework.WebApi.Caching; using SmartStore.Web.Framework.WebApi.Security; -using System.Linq; +using SmartStore.WebApi.Models; using Telerik.Web.Mvc; namespace SmartStore.WebApi.Services @@ -40,7 +40,7 @@ from c in _customers.Table join a in ( from a in _genericAttributes.Table - where a.KeyGroup == "Customer" && a.Key == WebApiUserCacheData.Key + where a.KeyGroup == "Customer" && a.Key == WebApiCachingUserData.Key select a ) on c.Id equals a.EntityId into ga @@ -57,7 +57,7 @@ orderby a.Value descending var lst = new PagedList(query, pageIndex, pageSize); - var cacheData = WebApiCaching.UserData(); + var cacheData = WebApiCachingUserData.Data(); foreach (var itm in lst) { @@ -80,7 +80,7 @@ public GridModel GetGridModel(int pageIndex, int pageSize) { var apiUsers = GetUsers(pageIndex, pageSize); - var model = new GridModel() + var model = new GridModel { Data = apiUsers, Total = apiUsers.TotalCount @@ -94,14 +94,14 @@ public bool CreateKeys(int customerId) if (customerId != 0) { var hmac = new HmacAuthentication(); - var userData = WebApiCaching.UserData(); + 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() + var apiUser = new WebApiUserCacheData { CustomerId = customerId, PublicKey = key1, @@ -111,17 +111,17 @@ public bool CreateKeys(int customerId) RemoveKeys(customerId); - var attribute = new GenericAttribute() + var attribute = new GenericAttribute { EntityId = customerId, KeyGroup = "Customer", - Key = WebApiUserCacheData.Key, + Key = WebApiCachingUserData.Key, Value = apiUser.ToString() }; _genericAttributeService.InsertAttribute(attribute); - WebApiCaching.Remove(WebApiUserCacheData.Key); + WebApiCachingUserData.Remove(); return true; } } @@ -134,7 +134,7 @@ public void RemoveKeys(int customerId) { var data = ( from a in _genericAttributes.Table - where a.EntityId == customerId && a.KeyGroup == "Customer" && a.Key == WebApiUserCacheData.Key + where a.EntityId == customerId && a.KeyGroup == "Customer" && a.Key == WebApiCachingUserData.Key select a).ToList(); if (data.Count > 0) @@ -142,7 +142,7 @@ from a in _genericAttributes.Table foreach (var itm in data) _genericAttributeService.DeleteAttribute(itm); - WebApiCaching.Remove(WebApiUserCacheData.Key); + WebApiCachingUserData.Remove(); } } } @@ -150,7 +150,7 @@ public void EnableOrDisableUser(int customerId, bool enable) { if (customerId != 0) { - var cacheData = WebApiCaching.UserData(); + var cacheData = WebApiCachingUserData.Data(); var apiUser = cacheData.FirstOrDefault(x => x.CustomerId == customerId); if (apiUser != null) diff --git a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj index 8373533bef..73e315200d 100644 --- a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj +++ b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj @@ -1,5 +1,5 @@  - + @@ -27,7 +27,7 @@ Properties SmartStore.WebApi SmartStore.WebApi - v4.5 + v4.5.1 512 @@ -48,6 +48,8 @@ + + true @@ -76,20 +78,23 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - - ..\..\packages\Autofac.Mvc5.3.3.1\lib\net45\Autofac.Integration.Mvc.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.3.2.1\lib\net40\AutoMapper.dll + + ..\..\packages\AutoMapper.4.1.1\lib\net45\AutoMapper.dll - - ..\..\packages\AutoMapper.3.2.1\lib\net40\AutoMapper.Net4.dll + + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + + + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll False @@ -102,17 +107,21 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.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 + @@ -121,12 +130,16 @@ + ..\..\packages\Microsoft.AspNet.Cors.5.1.2\lib\net45\System.Web.Cors.dll + + + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll False @@ -138,32 +151,31 @@ ..\..\packages\Microsoft.AspNet.WebApi.OData.5.1.2\lib\net45\System.Web.Http.OData.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - + ..\..\..\lib\Telerik\Telerik.Web.Mvc.dll @@ -177,6 +189,7 @@ + @@ -198,6 +211,9 @@ + + + @@ -215,6 +231,9 @@ + + + @@ -267,7 +286,6 @@ PreserveNewest - Designer @@ -294,9 +312,9 @@ False True - 0 + 49203 / - http://localhost:53128/ + http://localhost:49567/ False True http://www.smartstore.net @@ -305,7 +323,6 @@ - - - -
    -
    - - - - - - - - - - - - - - - - - - - diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index 1dd6b756d8..5483f5971f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -42,6 +42,11 @@ a:hover { background-color: @dropdownLinkBackgroundHover; } +.alert { + padding-top: 14px; + padding-bottom: 14px; +} + /* Page -------------------------------------------------------------- */ @@ -254,33 +259,101 @@ header { .opacity(100); } +.reddot { + position: absolute; + width: 10px; + height: 10px; + border-radius: 50%; + background-color: @red; + color: #fff; +} + /* SM additions -------------------------------------------------------------- */ + #ajax-busy { - position: absolute; - width: 150px; - height: 14px; - padding: 0; - background: transparent url('images/ajax-progress-2.gif') 50% 50% no-repeat; - top: 52px; - left: 30px; - visibility: hidden; - .opacity(0); - .transition(opacity 0.15s linear); + position: fixed; + opacity: 0; + left: 0; + right: 0; + top: 0; + width: 100%; + height: 3px; + z-index: 20; + background-color: #ffab40; + .transition(opacity .05s ease-in); } #ajax-busy.busy { - visibility: visible; - .opacity(100); + opacity: 1; +} +#ajax-busy.busy > .bar { + content: ""; + display: inline; + position: absolute; + width: 0; + height: 100%; + left: 50%; + text-align: center; + animation: loading 1.5s cubic-bezier(0.445, 0.05, 0.55, 0.95) infinite; + -webkit-animation: loading 1.5s cubic-bezier(0.445, 0.05, 0.55, 0.95) infinite; +} +#ajax-busy > .bar:nth-child(1) { + background-color: #fff; +} +#ajax-busy > .bar:nth-child(2) { + background-color: #3F51B5; + animation-delay: 0.5s; + -webkit-animation-delay: 0.5s; +} +#ajax-busy > .bar:nth-child(3) { + background-color: #ffab40; + animation-delay: 1s; + -webkit-animation-delay: 1s; +} +@keyframes loading { + from {left: 50%; width: 0; z-index:100;} + 50% {left: 0; width: 100%; z-index: 10;} + to {left: 0; width: 100%;} +} +@-webkit-keyframes loading { + from {left: 50%; width: 0; z-index:100;} + 50% {left: 0; width: 100%; z-index: 10;} + to {left: 0; width: 100%;} } -.ajax-loader-small { - display: inline-block; - position: relative; - width: 16px; - height: 16px; - padding: 0; - margin: 0; - background: transparent url('images/ajax_loader_small.gif') 50% 50% no-repeat; +.loading-bar { + height: 4px; + width: 100%; + position: relative; + overflow: hidden; + background-color: #ddd; +} +.loading-bar:before { + display: block; + position: absolute; + content: ""; + left: -200px; + width: 200px; + height: 4px; + background-color: #2980b9; + animation: loadingbar 2s linear infinite; + -webkit-animation: loadingbar 2s linear infinite; +} +@keyframes loadingbar { + from {left: -200px; width: 30%;} + 50% {width: 30%;} + 70% {width: 70%;} + 80% { left: 50%;} + 95% {left: 120%;} + to {left: 100%;} +} +@-webkit-keyframes loadingbar { + from {left: -200px; width: 30%;} + 50% {width: 30%;} + 70% {width: 70%;} + 80% { left: 50%;} + 95% {left: 120%;} + to {left: 100%;} } #content { @@ -315,14 +388,35 @@ td.adminData > input[type="checkbox"] { margin-top: -1px; } -.admin-config-group .title { - font-family: @headingsFontFamily; - font-weight: @headingsFontWeight; - color: inherit; - font-size: 18px; - margin: 12px 0; - padding: 12px 0; - border-bottom: 1px solid #ddd; +.admin-config-group { + .title, + .small-title, + .head { + font-family: @headingsFontFamily; + font-weight: @headingsFontWeight; + color: inherit; + border-bottom: 1px solid #ddd; + } + .sub-title { + font-family: @baseFontFamily; + font-size: @baseFontSize; + font-weight: normal; + } + .title, + .head { + font-size: @fontSizeLarge; + padding: 12px 0; + } + .head { + margin-bottom: 12px; + } + .title { + margin: 12px 0; + } + .small-title { + margin: 2px 0; + padding: 2px 0; + } } @@ -333,6 +427,60 @@ td.adminData > input[type="checkbox"] { min-width: 314px; /* ensures more clean look & feel */ } +.select2-image-item { + img { + margin-bottom: 3px; + } + span:not(.select2-icon) { + padding-left: 5px; + } + .icon-container { + float: left; + display: inline-block; + width: 16px; + min-width: 16px; + max-width: 16px; + } +} + +/* select2 with large, templated items +-------------------------------------------------------------- */ +.large-select2 { + .select2-choice { + height: 42px; + min-height: 42px; + padding-top: 4px; + + > div > b { + padding-top: 10px; + } + } +} + +.large-select2-item { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + height: 38px; + + img { + margin-right: 12px; + vertical-align: middle; + text-align: center; + max-height: 100%; + } + > div { + margin-right: 0px !important; + display: inline-block; + vertical-align: top; + line-height: normal; + } + > div > span { + display: block; + padding-bottom: 1px; + } +} + /* SM Telerik tweaks & overrides -------------------------------------------------------------- */ @@ -387,7 +535,12 @@ td.adminData > input[type="checkbox"] { } .t-grid-filter { - margin-top: -1.6em; + margin: 0; + padding: 0; + position: absolute; + right: 4px; + top: 50%; + margin-top: -8px; } .t-link:hover { @@ -398,12 +551,13 @@ td.adminData > input[type="checkbox"] { font-size: 12px; text-transform: uppercase; font-weight: bold !important; - padding-top: 0.6em !important; - padding-bottom: 0.6em !important; + padding: 0 !important; + position: relative; } .t-grid-header .t-header .t-link { - width: 100%; + margin: 0; + padding: 0.6em; } .t-grid-pager, @@ -446,6 +600,7 @@ body .t-content.t-state-active { } + /* HTML editor (tiny mce) -------------------------------------------------------------- */ @@ -468,10 +623,6 @@ label.forcheckbox { display: inline; } -a:hover { - font-weight: normal; -} - /* SECTION HEADERS */ .section-title { border-bottom: solid 3px #dfdfdf; @@ -519,6 +670,7 @@ a:hover { img { max-height: 32px; max-width: 120px; + margin-right: 5px; vertical-align: top; } @@ -645,7 +797,6 @@ td.adminTitle > .ctl-label { label { display: inline-block; padding-right: 20px; - margin-top: 1px; margin-bottom: 0; } @@ -670,6 +821,16 @@ td.adminTitle > .ctl-label { } } } +tr.adminGroup { + > td { + padding-top: 18px; + } + + label { + padding-bottom: 3px; + text-decoration: underline; + } +} td.adminTitle:hover a.hint { .opacity(100); @@ -690,9 +851,16 @@ td.adminData .text-box.single-line { width: 300px; } td.adminData input[type=text].input-large, -td.adminData textarea.input-large { +td.adminData textarea.input-large, +td.adminData .control-large { width: 500px; } +td.adminData input[type=text].input-xlarge, +td.adminData textarea.input-xlarge, +td.adminData .control-xlarge { + width: 800px; +} + .t-grid .text-box.single-line { /* ... */ @@ -704,31 +872,54 @@ td.adminData .t-combobox { width: 306px; } -tr.adminSeparator hr { - background-color: #D3D3D3; - border: medium none; - color: #D3D3D3; +tr.adminSeparator hr, +td.adminSeparator hr { + border: medium none; + background-color: #ddd; + color: #ddd; height: 1px; - margin-top: 10px; - margin-bottom: 10px; + margin-top: 12px; + margin-bottom: 12px; padding: 0; } -.plugin-intro-table { +.plugin-configuration { + display: table; width: 100%; clear: both; - td { + > div { + display: table-cell; vertical-align: top; } - - td:first-child { + > div:first-child { width: 80%; } - - td:last-child { + > div:last-child { float: right; - padding-left: 14px; + padding-left: 50px; + } +} + +.admin-table { + thead th { + text-transform: uppercase; + } + th, td { + padding: 12px; + } + .disabled { + .muted(); + } + .progress-info { + min-width: 260px; + } + select, input { + margin-bottom: 0; + } + .control-large { + width: 500px; + min-width: 500px; } } @@ -748,6 +939,7 @@ label { } .mr8 { margin-right: 8px; } +.ml4 { margin-left: 4px; } /* MASTER HEADER */ .header-menu, @@ -1108,11 +1300,18 @@ table.order-edit td { .module-info { margin-top: 3px; - .attr-name { } - .attr-value { - color: #aaa; - padding-right: 5px; - } +} +.module-info .attr:after { + display: inline-block; + content: '\00b7'; + padding: 0 5px; +} +.module-info .attr:last-child:after { + display: none; +} +.module-info .attr-value { + color: #aaa; + //padding-right: 5px; } .module-description { @@ -1275,8 +1474,8 @@ table.payment-method-features td { display: block; overflow: hidden; cursor: pointer; - border: 2px solid #969696; - border-radius: 5px; + border: 1px solid #969696; + border-radius: 4px; height: 14px; padding: 0; margin: 0; @@ -1285,7 +1484,6 @@ table.payment-method-features td { .onoffswitch-inner { width: 200%; margin-left: -100%; - .transition(all 0.3s ease-in 0s); } .onoffswitch-on, .onoffswitch-off { @@ -1294,37 +1492,36 @@ table.payment-method-features td { height: 15px; line-height: 15px; padding: 0; - color: white; - font-size: 11px; - font-family: Trebuchet, Arial, sans-serif; + color: #fff; + font-size: 10px; font-weight: normal; + text-align: center; + vertical-align: middle; .box-sizing(border-box); } .onoffswitch-on { - padding-left: 6px; - background-color: #6BB2ED; - color: #FFFFFF; + background-color: @orange; + color: #fff; } .onoffswitch-off { - padding-right: 3px; - background-color: #FFFFFF; - color: #969696; - text-align: right; + background-color: #fff; + color: #bbb; } .onoffswitch-switch { - width: 27px; + width: 30px; + box-sizing: border-box; margin: 0; position: absolute; top: 0; bottom: 0; - right: 29px; - background: #FFFFFF; - border: 2px solid #969696; - .border-radius(5px); - .transition(all 0.3s ease-in 0s); + right: 30px; + background: #fff; + border: 1px solid #969696; + .border-radius(4px 0 0 4px); + .transition(all 0.05s ease-in); #gradient > .vertical(#dddddd, #ffffff); } @@ -1334,6 +1531,7 @@ table.payment-method-features td { .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { right: 0; + .border-radius(0 4px 4px 0); } #pnlAllFlags a.flag { min-width: 100px; @@ -1343,16 +1541,127 @@ table.payment-method-features td { text-decoration: none; } -#product-edit h3, -.caption-container h3 { - font-family: @headingsFontFamily; - font-weight: @headingsFontWeight; - font-size: 18px; - line-height: 36px; - color: #333; + +/* Download editor +-------------------------------------------------------------- */ + +.download-editor { + .panel-switcher-icon { + width: 13px; + } + + .filename { + line-height: 32px; + vertical-align: middle; + padding-right: 10px; + font-weight: bold; + max-width: 300px; + display: inline-block; + .ellipsis(); + } } -#product-edit h3.bordered, -.caption-container h3.bordered { - border: 0; - border-bottom: 1px solid #e5e5e5; + + +/* Scheduled tasks +-------------------------------------------------------------- */ + +.minimal-task-widget { + min-height: 26px; +} +.minimal-task-widget .task-progress { + width: 400px; } +.minimal-task-widget .btn-cancel-task { + margin-left: 10px +} +.minimal-task-widget .btn-actions { + display: inline-block; + margin-left: 10px; +} + + + + +/* Data exchange +-------------------------------------------------------------- */ +.profile-list { + margin-bottom: 10px; + border-bottom: 1px solid #ddd; +} + +.info-profile td { + padding-top: 20px; +} + +/* Import column mapping +-------------------------------------------------------------- */ +@media (max-width: 1280px) { + #ImportColumnMappings .right-label, + .column-mapping .mapping-button-label, + .mapping-list-item .right-label { + display: none; + } +} + +.column-mapping .right-label, +.mapping-list-item .right-label { + float: right; + font-size: 0.9em; +} +.mapping-list-item { + .ellipsis(); +} + +.column-mapping { + .column-one, .column-two { + width: 42%; + } + + .select-column, .select-property, .input-default { + width: 98%; + } + + th, td, .mapping-delete, .mapping-add, .mapping-apply, .mapping-cancel { + white-space: nowrap; + padding-left: 8px; + padding-right: 8px; + } + + .select2-container .select2-choice span { + margin-right: 12px; + white-space: nowrap; + } + + .mapping-edit { + white-space: nowrap; + + .select-column, .select-property { + width: 84%; + } + .select2-container { + float: left; + display: inline-block; + min-width: 220px; + margin-right: 4px; + } + } + + .mapping-item { + .item-inner { + white-space: nowrap; + cursor: pointer; + padding: 7px 0; + + .property-icon { + display: inline-block; + margin-right: 0; + } + .left-label { + display: inline-block; + } + .right-label { + margin-right: 20px; + } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/LICENSE.txt b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/LICENSE.txt similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/LICENSE.txt rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/LICENSE.txt diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json new file mode 100644 index 0000000000..c08220bdfe --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json @@ -0,0 +1,33 @@ +{ + "FILES_ROOT": "~/Media/Uploaded", + "SESSION_PATH_KEY": "", + "THUMBS_VIEW_WIDTH": "140", + "THUMBS_VIEW_HEIGHT": "120", + "PREVIEW_THUMB_WIDTH": "300", + "PREVIEW_THUMB_HEIGHT": "200", + "MAX_IMAGE_WIDTH": "0", + "MAX_IMAGE_HEIGHT": "0", + "INTEGRATION": "ckeditor", + "DIRLIST": "../../../Admin/RoxyFileManager/ProcessRequest?a=DIRLIST", + "CREATEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=CREATEDIR", + "DELETEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=DELETEDIR", + "MOVEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=MOVEDIR", + "COPYDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=COPYDIR", + "RENAMEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=RENAMEDIR", + "FILESLIST": "../../../Admin/RoxyFileManager/ProcessRequest?a=FILESLIST", + "UPLOAD": "../../../Admin/RoxyFileManager/ProcessRequest?a=UPLOAD", + "DOWNLOAD": "../../../Admin/RoxyFileManager/ProcessRequest?a=DOWNLOAD", + "DOWNLOADDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=DOWNLOADDIR", + "DELETEFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=DELETEFILE", + "MOVEFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=MOVEFILE", + "COPYFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=COPYFILE", + "RENAMEFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=RENAMEFILE", + "GENERATETHUMB": "../../../Admin/RoxyFileManager/ProcessRequest?a=GENERATETHUMB", + "DEFAULTVIEW": "list", + "FORBIDDEN_UPLOADS": "zip js jsp jsb mhtml mht xhtml xht php phtml php3 php4 php5 phps shtml jhtml pl sh py cgi exe scr dll msi vbs bat com pif cmd vxd cpl htpasswd htaccess", + "ALLOWED_UPLOADS": "", + "FILEPERMISSIONS": "0644", + "DIRPERMISSIONS": "0755", + "LANG": "auto", + "DATEFORMAT": "dd.MM.yyyy HH:mm" +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/animated-overlay.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/animated-overlay.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/animated-overlay.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/animated-overlay.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_222222_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_222222_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_222222_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_222222_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_454545_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_454545_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_454545_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_454545_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_888888_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_888888_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_888888_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_888888_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/main.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/main.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/main.min.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.min.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/main.min.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.min.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-paste.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-paste.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-paste.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-paste.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-rename.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-rename.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-rename.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-rename.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_down.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_down.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_down.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_down.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_up.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_up.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_up.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_up.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/blank.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/blank.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/blank.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/blank.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/copy.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/copy.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/copy.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/copy.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/cut.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/cut.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/cut.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/cut.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/dir-minus.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-minus.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/dir-minus.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-minus.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/dir-plus.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-plus.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/dir-plus.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-plus.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-add.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-add.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-add.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-add.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-delete.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-delete.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-delete.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-delete.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-download.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-download.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-download.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-download.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-duplicate.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-duplicate.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-duplicate.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-duplicate.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-upload.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-upload.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-upload.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-upload.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_3gp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_3gp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_3gp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_3gp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_7z.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_7z.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_7z.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_7z.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ace.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ace.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ace.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ace.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ai.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ai.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ai.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ai.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aiff.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aiff.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aiff.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aiff.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_amr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_amr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_amr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_amr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bin.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bin.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bin.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bin.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bup.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bup.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bup.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bup.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cab.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cab.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cab.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cab.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cbr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cbr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cbr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cbr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cda.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cda.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cda.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cda.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdl.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdl.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdl.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdl.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_chm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_chm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_chm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_chm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_divx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_divx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_divx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_divx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dll.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dll.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dll.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dll.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dmg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dmg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dmg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dmg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_doc.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_doc.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_doc.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_doc.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dvf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dvf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dvf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dvf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dwg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dwg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dwg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dwg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eml.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eml.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eml.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eml.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_exe.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_exe.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_exe.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_exe.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_fla.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_fla.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_fla.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_fla.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_flv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_flv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_flv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_flv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_hqx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_hqx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_hqx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_hqx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_htm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_htm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_htm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_htm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_html.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_html.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_html.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_html.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ifo.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ifo.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ifo.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ifo.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_indd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_indd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_indd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_indd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_iso.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_iso.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_iso.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_iso.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_lnk.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_lnk.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_lnk.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_lnk.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_log.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_log.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_log.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_log.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4a.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4a.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4a.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4a.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4b.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4b.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4b.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4b.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4p.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4p.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4p.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4p.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4v.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4v.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4v.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4v.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mdb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mdb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mdb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mdb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mid.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mid.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mid.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mid.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mov.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mov.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mov.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mov.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp2.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp2.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp2.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp2.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp4.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp4.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp4.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp4.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_msi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_msi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_msi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_msi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mswmm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mswmm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mswmm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mswmm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ogg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ogg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ogg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ogg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pdf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pdf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pdf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pdf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_png.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_png.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_png.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_png.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_psd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_psd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_psd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_psd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pst.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pst.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pst.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pst.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ptb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ptb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ptb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ptb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pub.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pub.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pub.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pub.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbw.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbw.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbw.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbw.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qxd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qxd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qxd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qxd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ram.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ram.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ram.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ram.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rmvb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rmvb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rmvb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rmvb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rtf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rtf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rtf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rtf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sea.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sea.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sea.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sea.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ses.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ses.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ses.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ses.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sit.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sit.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sit.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sit.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sitx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sitx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sitx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sitx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_swf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_swf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_swf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_swf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tgz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tgz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tgz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tgz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_thm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_thm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_thm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_thm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_torrent.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_torrent.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_torrent.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_torrent.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ttf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ttf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ttf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ttf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_txt.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_txt.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_txt.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_txt.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vob.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vob.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vob.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vob.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wav.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wav.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wav.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wav.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wma.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wma.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wma.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wma.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wmv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wmv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wmv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wmv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xls.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xls.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xls.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xls.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xpi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xpi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xpi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xpi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_zip.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_zip.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_zip.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_zip.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/unknown.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/unknown.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/unknown.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/unknown.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_3gp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_3gp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_3gp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_3gp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_7z.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_7z.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_7z.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_7z.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ace.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ace.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ace.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ace.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ai.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ai.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ai.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ai.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aiff.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aiff.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aiff.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aiff.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_amr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_amr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_amr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_amr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bin.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bin.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bin.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bin.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bup.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bup.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bup.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bup.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cab.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cab.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cab.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cab.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cbr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cbr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cbr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cbr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cda.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cda.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cda.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cda.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdl.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdl.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdl.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdl.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_chm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_chm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_chm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_chm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_divx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_divx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_divx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_divx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dll.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dll.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dll.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dll.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dmg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dmg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dmg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dmg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_doc.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_doc.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_doc.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_doc.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dvf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dvf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dvf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dvf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dwg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dwg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dwg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dwg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eml.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eml.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eml.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eml.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_exe.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_exe.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_exe.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_exe.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_fla.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_fla.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_fla.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_fla.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_flv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_flv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_flv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_flv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_hqx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_hqx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_hqx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_hqx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_htm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_htm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_htm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_htm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_html.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_html.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_html.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_html.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ifo.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ifo.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ifo.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ifo.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_indd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_indd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_indd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_indd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_iso.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_iso.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_iso.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_iso.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_lnk.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_lnk.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_lnk.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_lnk.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_log.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_log.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_log.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_log.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4a.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4a.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4a.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4a.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4b.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4b.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4b.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4b.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4p.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4p.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4p.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4p.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4v.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4v.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4v.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4v.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mdb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mdb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mdb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mdb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mid.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mid.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mid.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mid.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mov.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mov.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mov.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mov.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp2.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp2.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp2.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp2.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp4.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp4.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp4.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp4.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_msi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_msi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_msi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_msi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mswmm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mswmm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mswmm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mswmm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ogg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ogg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ogg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ogg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pdf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pdf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pdf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pdf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_png.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_png.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_png.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_png.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_psd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_psd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_psd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_psd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pst.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pst.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pst.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pst.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ptb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ptb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ptb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ptb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pub.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pub.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pub.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pub.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbw.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbw.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbw.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbw.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qxd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qxd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qxd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qxd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ram.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ram.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ram.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ram.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rmvb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rmvb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rmvb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rmvb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rtf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rtf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rtf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rtf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sea.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sea.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sea.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sea.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ses.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ses.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ses.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ses.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sit.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sit.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sit.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sit.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sitx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sitx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sitx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sitx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_swf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_swf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_swf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_swf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tgz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tgz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tgz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tgz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_thm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_thm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_thm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_thm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_torrent.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_torrent.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_torrent.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_torrent.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ttf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ttf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ttf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ttf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_txt.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_txt.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_txt.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_txt.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vob.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vob.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vob.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vob.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wav.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wav.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wav.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wav.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wma.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wma.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wma.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wma.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wmv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wmv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wmv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wmv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xls.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xls.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xls.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xls.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xpi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xpi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xpi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xpi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_zip.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_zip.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_zip.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_zip.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/unknown.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/unknown.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/unknown.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/unknown.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/find.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/find.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/find.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/find.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-add.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-add.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-add.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-add.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-delete.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-delete.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-delete.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-delete.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-download.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-download.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-download.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-download.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-green.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-green.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-green.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-green.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-opened.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-opened.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-opened.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-opened.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/loading-dir.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading-dir.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/loading-dir.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading-dir.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/loading.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/loading.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/paste.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/paste.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/paste.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/paste.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/preview.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/preview.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/preview.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/preview.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/rename.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/rename.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/rename.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/rename.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/search.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/search.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/search.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/search.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/select.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/select.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/select.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/select.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/upload-big.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/upload-big.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/upload-big.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/upload-big.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/view-list.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-list.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/view-list.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-list.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/view-tile.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-tile.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/view-tile.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-tile.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/index.html b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/index.html similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/index.html rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/index.html diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/custom.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/custom.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/directory.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/directory.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/file.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/file.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/fileman-modified.zip b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/fileman-modified.zip similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/fileman-modified.zip rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/fileman-modified.zip diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/filetypes.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/filetypes.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-1.10.2.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-1.10.2.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-dateFormat.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-dateFormat.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-dateFormat.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-dateFormat.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/main.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/main.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/main.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/main.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/mini-main.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/mini-main.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/mini-main.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/mini-main.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/tiny_mce_popup.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/tiny_mce_popup.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/tiny_mce_popup.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/tiny_mce_popup.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/utils.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/utils.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/utils.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/utils.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/bg.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/bg.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/bg.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/bg.json diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/de.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/de.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/de.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/de.json diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/en.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/en.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/en.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/en.json diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/fr.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/fr.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/fr.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/fr.json diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ajax-progress-2.gif b/src/Presentation/SmartStore.Web/Administration/Content/images/ajax-progress-2.gif deleted file mode 100644 index 2ed9a034bd..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ajax-progress-2.gif and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-catalog-white.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-catalog-white.png deleted file mode 100644 index d12bfff9be..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-catalog-white.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-catalog.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-catalog.png deleted file mode 100644 index 5e3a11babf..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-catalog.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-chevron.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-chevron.png deleted file mode 100644 index 4168ab3209..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-chevron.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-cms-white.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-cms-white.png deleted file mode 100644 index 5bfb96f67b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-cms-white.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-cms.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-cms.png deleted file mode 100644 index fe052b3879..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-cms.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-customers-white.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-customers-white.png deleted file mode 100644 index f2b8e2608c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-customers-white.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-customers.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-customers.png deleted file mode 100644 index faad9b156e..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-customers.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-dashboard-white.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-dashboard-white.png deleted file mode 100644 index eee29a0dd6..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-dashboard-white.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-dashboard.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-dashboard.png deleted file mode 100644 index 3388b811aa..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-dashboard.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-default-white.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-default-white.png deleted file mode 100644 index ea4f248b44..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-default-white.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-default.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-default.png deleted file mode 100644 index 365847d12f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-default.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-help-white.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-help-white.png deleted file mode 100644 index d7fa0626c1..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-help-white.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-help.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-help.png deleted file mode 100644 index 2acb084bc1..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-help.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-marketing-white.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-marketing-white.png deleted file mode 100644 index 9d3ef59faf..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-marketing-white.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-marketing.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-marketing.png deleted file mode 100644 index 82649f18dc..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-marketing.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-sales-white.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-sales-white.png deleted file mode 100644 index 25013b3977..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-sales-white.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-sales.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-sales.png deleted file mode 100644 index fb6ffda399..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-sales.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-settings-white.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-settings-white.png deleted file mode 100644 index 76e61cb9ed..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-settings-white.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-settings.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-settings.png deleted file mode 100644 index de9bbd1376..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-settings.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-system-white.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-system-white.png deleted file mode 100644 index 1fc35c2bc5..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-system-white.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-system.png b/src/Presentation/SmartStore.Web/Administration/Content/images/ap-system.png deleted file mode 100644 index 204b19571d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/ap-system.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/images/preloader-32.gif b/src/Presentation/SmartStore.Web/Administration/Content/images/preloader-32.gif deleted file mode 100644 index f4144d0f19..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/images/preloader-32.gif and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/theme.less b/src/Presentation/SmartStore.Web/Administration/Content/theme.less index ac18fb4397..a816128c79 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/theme.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/theme.less @@ -10,9 +10,6 @@ @bsPath: '../../Content/bootstrap'; -// Fonts -@import "~/Content/fonts/fontastic.less"; - // CSS Reset @import "~/Content/bootstrap/reset.less"; @@ -67,6 +64,7 @@ // (MC) extra 3rd party or own components +@import "~/Content/bootstrap/custom/spinner.less"; @import "~/Content/bootstrap/custom/throbber.less"; @import "~/Content/bootstrap/custom/select2.less"; @import "~/Content/bootstrap/custom/datetimepicker.less"; diff --git a/src/Presentation/SmartStore.Web/Administration/Content/variables.less b/src/Presentation/SmartStore.Web/Administration/Content/variables.less index 3534700836..741c1de2bc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/variables.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/variables.less @@ -64,14 +64,14 @@ @headingsFontFamily: 'Segoe UI Semibold', 'Segoe UI', Arial, Helvetica, sans-serif; //inherit; // empty to use BS default, @baseFontFamily @headingsFontWeight: 600; //bold; // instead of browser default, bold -@headingsColor: #777; //#009fff; //inherit; // empty to use BS default, @textColor +@headingsColor: @textColor; //#009fff; //inherit; // empty to use BS default, @textColor // Component sizing // ------------------------- // Based on 14px font-size and 20px line-height -@fontSizeLarge: @baseFontSize * 1.25; // ~18px +@fontSizeLarge: @baseFontSize * 1.4; // ~18px @fontSizeSmall: @baseFontSize * 0.85; // ~12px @fontSizeMini: @baseFontSize * 0.75; // ~11px diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ActivityLogController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ActivityLogController.cs index 1274bc54fe..e6c49b910e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ActivityLogController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ActivityLogController.cs @@ -1,38 +1,47 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Logging; -using SmartStore.Services.Helpers; -using SmartStore.Services.Localization; +using SmartStore.Core.Domain.Common; using SmartStore.Core.Logging; +using SmartStore.Services.Customers; +using SmartStore.Services.Helpers; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public partial class ActivityLogController : AdminControllerBase { #region Fields private readonly ICustomerActivityService _customerActivityService; - private readonly IDateTimeHelper _dateTimeHelper; - private readonly ILocalizationService _localizationService; + private readonly ICustomerService _customerService; + private readonly IDateTimeHelper _dateTimeHelper; private readonly IPermissionService _permissionService; + private readonly AdminAreaSettings _adminAreaSettings; - #endregion Fields + #endregion Fields - #region Constructors + #region Constructors - public ActivityLogController(ICustomerActivityService customerActivityService, - IDateTimeHelper dateTimeHelper, ILocalizationService localizationService, - IPermissionService permissionService) + public ActivityLogController( + ICustomerActivityService customerActivityService, + ICustomerService customerService, + IDateTimeHelper dateTimeHelper, + IPermissionService permissionService, + AdminAreaSettings adminAreaSettings) { this._customerActivityService = customerActivityService; + this._customerService = customerService; this._dateTimeHelper = dateTimeHelper; - this._localizationService = localizationService; this._permissionService = permissionService; + this._adminAreaSettings = adminAreaSettings; } #endregion  @@ -44,7 +53,7 @@ public ActionResult ListTypes() if (!_permissionService.Authorize(StandardPermissionProvider.ManageActivityLog)) return AccessDeniedView(); - var activityLogTypeModel = _customerActivityService.GetAllActivityTypes().Select(x => x.ToModel()); + var activityLogTypeModel = _customerActivityService.GetAllActivityTypes().Select(x => x.ToModel()).OrderBy(x => x.Name); var gridModel = new GridModel { Data = activityLogTypeModel, @@ -56,11 +65,11 @@ public ActionResult ListTypes() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ListTypes(GridCommand command) { - var activityLogTypeModel = _customerActivityService.GetAllActivityTypes().Select(x => x.ToModel()); + var activityLogTypeModel = _customerActivityService.GetAllActivityTypes().Select(x => x.ToModel()).ToList(); var gridModel = new GridModel { Data = activityLogTypeModel, - Total = activityLogTypeModel.Count() + Total = activityLogTypeModel.Count }; return new JsonResult { @@ -81,12 +90,14 @@ public ActionResult SaveTypes(FormCollection formCollection) if (Int32.TryParse(key,out id)) { var activityType = _customerActivityService.GetActivityTypeById(id); - activityType.Enabled = formCollection["checkBox_"+key].Equals("false") ? false : true; + activityType.Enabled = !formCollection["checkBox_" + key].Equals("false"); _customerActivityService.UpdateActivityType(activityType); } } - NotifySuccess(_localizationService.GetResource("Admin.Configuration.ActivityLog.ActivityLogType.Updated")); + + NotifySuccess(T("Admin.Configuration.ActivityLog.ActivityLogType.Updated")); + return RedirectToAction("ListTypes"); } @@ -99,78 +110,95 @@ public ActionResult ListLogs() if (!_permissionService.Authorize(StandardPermissionProvider.ManageActivityLog)) return AccessDeniedView(); - var activityLogSearchModel = new ActivityLogSearchModel(); - activityLogSearchModel.ActivityLogType.Add(new SelectListItem() - { - Value = "0", - Text = "All" - }); - - - foreach (var at in _customerActivityService.GetAllActivityTypes() - .OrderBy(x=>x.Name) - .Select(x => - { - return new SelectListItem() - { - Value = x.Id.ToString(), - Text = x.Name - }; - })) - activityLogSearchModel.ActivityLogType.Add(at); - return View(activityLogSearchModel); + var model = new ActivityLogSearchModel + { + GridPageSize = _adminAreaSettings.GridPageSize + }; + + model.ActivityLogType = _customerActivityService.GetAllActivityTypes() + .OrderBy(x => x.Name) + .Select(x => new SelectListItem + { + Value = x.Id.ToString(), + Text = x.Name + }) + .ToList(); + + return View(model); } [HttpPost, GridAction(EnableCustomBinding = true)] - public JsonResult ListLogs(GridCommand command, ActivityLogSearchModel model) + public JsonResult ListActivityLogs(GridCommand command, ActivityLogSearchModel model) { DateTime? startDateValue = (model.CreatedOnFrom == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnFrom.Value, _dateTimeHelper.CurrentTimeZone); DateTime? endDateValue = (model.CreatedOnTo == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + var activityLog = _customerActivityService.GetAllActivities(startDateValue, endDateValue, null, model.ActivityLogTypeId, + command.Page - 1, command.PageSize, model.CustomerEmail, model.CustomerSystemAccount); + + var systemAccountCustomers = _customerService.GetSystemAccountCustomers(); - var activityLog = _customerActivityService.GetAllActivities(startDateValue, endDateValue,null, model.ActivityLogTypeId, command.Page - 1, command.PageSize); - var gridModel = new GridModel + var gridModel = new GridModel { Data = activityLog.Select(x => { var m = x.ToModel(); + var systemCustomer = systemAccountCustomers.FirstOrDefault(y => y.Id == x.CustomerId); + m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - return m; - + m.IsSystemAccount = (systemCustomer != null); + + if (systemCustomer != null) + { + if (systemCustomer.IsSearchEngineAccount()) + m.SystemAccountName = T("Admin.System.SystemCustomerNames.SearchEngine"); + else if (systemCustomer.IsBackgroundTaskAccount()) + m.SystemAccountName = T("Admin.System.SystemCustomerNames.BackgroundTask"); + else if (systemCustomer.IsPdfConverter()) + m.SystemAccountName = T("Admin.System.SystemCustomerNames.PdfConverter"); + else + m.SystemAccountName = "".NaIfEmpty(); + } + + return m; }), Total = activityLog.TotalCount }; return new JsonResult { Data = gridModel}; } - [GridAction(EnableCustomBinding = true)] - public ActionResult AcivityLogDelete(int id, GridCommand command) + [HttpPost, ActionName("ListLogs")] + [FormValueRequired("clearall")] + public ActionResult ClearAll() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageActivityLog)) return AccessDeniedView(); - var activityLog = _customerActivityService.GetActivityById(id); - if (activityLog == null) - throw new ArgumentException("No activity log found with the specified id"); - - _customerActivityService.DeleteActivity(activityLog); + _customerActivityService.ClearAllActivities(); - //TODO pass and return current ActivityLogSearchModel - return ListLogs(command, new ActivityLogSearchModel()); + return RedirectToAction("ListLogs"); } - public ActionResult ClearAll() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageActivityLog)) - return AccessDeniedView(); + [HttpPost] + public ActionResult DeleteSelected(ICollection selectedIds) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageActivityLog)) + return AccessDeniedView(); - _customerActivityService.ClearAllActivities(); - return RedirectToAction("ListLogs"); - } + if (selectedIds != null) + { + var activityLogs = _customerActivityService.GetActivityByIds(selectedIds.ToArray()); - #endregion + foreach (var activityLog in activityLogs) + _customerActivityService.DeleteActivity(activityLog); + } + + return Json(new { Result = true }); + } - } + #endregion + } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/AffiliateController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/AffiliateController.cs index a921214f8f..cf1e2b3c87 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/AffiliateController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/AffiliateController.cs @@ -18,11 +18,13 @@ using SmartStore.Services.Security; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public partial class AffiliateController : AdminControllerBase { #region Fields @@ -154,23 +156,31 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); + var model = new GridModel(); - var affiliates = _affiliateService.GetAllAffiliates(true); - var gridModel = new GridModel - { - Data = affiliates.PagedForCommand(command).Select(x => - { - var m = new AffiliateModel(); - PrepareAffiliateModel(m, x, false); - return m; - }), - Total = affiliates.Count, - }; - return new JsonResult + if (_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + { + var affiliates = _affiliateService.GetAllAffiliates(true); + + model.Data = affiliates.PagedForCommand(command).Select(x => + { + var m = new AffiliateModel(); + PrepareAffiliateModel(m, x, false); + return m; + }); + + model.Total = affiliates.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + + return new JsonResult { - Data = gridModel + Data = model }; } @@ -186,7 +196,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [FormValueRequired("save", "save-continue")] public ActionResult Create(AffiliateModel model, bool continueEditing) { @@ -234,7 +244,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(AffiliateModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) @@ -285,31 +295,34 @@ public ActionResult Delete(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult AffiliatedOrderList(int affiliateId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); + var model = new GridModel(); - var affiliate = _affiliateService.GetAffiliateById(affiliateId); - if (affiliate == null) - throw new ArgumentException("No affiliate found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + { + var affiliate = _affiliateService.GetAffiliateById(affiliateId); + var orders = _orderService.GetAllOrders(affiliate.Id, command.Page - 1, command.PageSize); + + model.Data = orders.Select(order => + { + var orderModel = new AffiliateModel.AffiliatedOrderModel(); + orderModel.Id = order.Id; + orderModel.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext); + orderModel.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext); + orderModel.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext); + orderModel.OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false); + orderModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc); + return orderModel; + }); + model.Total = orders.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); - var orders = _orderService.GetAllOrders(affiliate.Id, command.Page - 1, command.PageSize); - var model = new GridModel - { - Data = orders.Select(order => - { - var orderModel = new AffiliateModel.AffiliatedOrderModel(); - orderModel.Id = order.Id; - orderModel.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext); - orderModel.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext); - orderModel.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext); - orderModel.OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false); - orderModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc); - return orderModel; - }), - Total = orders.TotalCount - }; + NotifyAccessDenied(); + } - return new JsonResult + return new JsonResult { Data = model }; @@ -318,19 +331,16 @@ public ActionResult AffiliatedOrderList(int affiliateId, GridCommand command) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult AffiliatedCustomerList(int affiliateId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); + var model = new GridModel(); - var affiliate = _affiliateService.GetAffiliateById(affiliateId); - if (affiliate == null) - throw new ArgumentException("No affiliate found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + { + var affiliate = _affiliateService.GetAffiliateById(affiliateId); + var customers = _customerService.GetAllCustomers(affiliate.Id, command.Page - 1, command.PageSize); - var customers = _customerService.GetAllCustomers(affiliate.Id, command.Page - 1, command.PageSize); - var model = new GridModel - { - Data = customers.Select(customer => - { - var customerModel = new AffiliateModel.AffiliatedCustomerModel() + model.Data = customers.Select(customer => + { + var customerModel = new AffiliateModel.AffiliatedCustomerModel { Id = customer.Id, Email = customer.Email, @@ -338,16 +348,23 @@ public ActionResult AffiliatedCustomerList(int affiliateId, GridCommand command) FullName = customer.GetFullName() }; - return customerModel; - }), - Total = customers.TotalCount - }; + return customerModel; + }); + model.Total = customers.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { Data = model }; } + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs index bcfa6611a6..f0883628b1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs @@ -13,6 +13,8 @@ using SmartStore.Services.Stores; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -103,29 +105,37 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) + { + var blogPosts = _blogService.GetAllBlogPosts(0, 0, null, null, command.Page - 1, command.PageSize, true); + + model.Data = blogPosts.Select(x => + { + var m = x.ToModel(); + if (x.StartDateUtc.HasValue) + m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); + if (x.EndDateUtc.HasValue) + m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); + m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + m.LanguageName = x.Language.Name; + m.Comments = x.ApprovedCommentCount + x.NotApprovedCommentCount; + return m; + }); + + model.Total = blogPosts.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var blogPosts = _blogService.GetAllBlogPosts(0, 0, null, null, command.Page - 1, command.PageSize, true); - var gridModel = new GridModel - { - Data = blogPosts.Select(x => - { - var m = x.ToModel(); - if (x.StartDateUtc.HasValue) - m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); - if (x.EndDateUtc.HasValue) - m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); - m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - m.LanguageName = x.Language.Name; - m.Comments = x.ApprovedCommentCount + x.NotApprovedCommentCount; - return m; - }), - Total = blogPosts.TotalCount - }; return new JsonResult { - Data = gridModel + Data = model }; } @@ -146,7 +156,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(BlogPostModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) @@ -198,7 +208,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(BlogPostModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) @@ -267,71 +277,76 @@ public ActionResult Comments(int? filterByBlogPostId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Comments(int? filterByBlogPostId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) - return AccessDeniedView(); + var model = new GridModel(); - IList comments; - if (filterByBlogPostId.HasValue) - { - //filter comments by blog - var blogPost = _blogService.GetBlogPostById(filterByBlogPostId.Value); - comments = blogPost.BlogComments.OrderBy(bc => bc.CreatedOnUtc).ToList(); - } - else - { - //load all blog comments - comments = _customerContentService.GetAllCustomerContent(0, null); - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) + { + IList comments; + if (filterByBlogPostId.HasValue) + { + //filter comments by blog + var blogPost = _blogService.GetBlogPostById(filterByBlogPostId.Value); + comments = blogPost.BlogComments.OrderBy(bc => bc.CreatedOnUtc).ToList(); + } + else + { + //load all blog comments + comments = _customerContentService.GetAllCustomerContent(0, null); + } - var gridModel = new GridModel - { - Data = comments.PagedForCommand(command).Select(blogComment => - { - var commentModel = new BlogCommentModel(); + model.Data = comments.PagedForCommand(command).Select(blogComment => + { + var commentModel = new BlogCommentModel(); var customer = _customerService.GetCustomerById(blogComment.CustomerId); - commentModel.Id = blogComment.Id; - commentModel.BlogPostId = blogComment.BlogPostId; - commentModel.BlogPostTitle = blogComment.BlogPost.Title; - commentModel.CustomerId = blogComment.CustomerId; - commentModel.IpAddress = blogComment.IpAddress; - commentModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(blogComment.CreatedOnUtc, DateTimeKind.Utc); - commentModel.Comment = Core.Html.HtmlUtils.FormatText(blogComment.CommentText, false, true, false, false, false, false); + commentModel.Id = blogComment.Id; + commentModel.BlogPostId = blogComment.BlogPostId; + commentModel.BlogPostTitle = blogComment.BlogPost.Title; + commentModel.CustomerId = blogComment.CustomerId; + commentModel.IpAddress = blogComment.IpAddress; + commentModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(blogComment.CreatedOnUtc, DateTimeKind.Utc); + commentModel.Comment = Core.Html.HtmlUtils.FormatText(blogComment.CommentText, false, true, false, false, false, false); if (customer == null) commentModel.CustomerName = "".NaIfEmpty(); else commentModel.CustomerName = customer.GetFullName(); - return commentModel; - }), - Total = comments.Count, - }; + return commentModel; + }); + + model.Total = comments.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { - Data = gridModel + Data = model }; } [GridAction(EnableCustomBinding = true)] public ActionResult CommentDelete(int? filterByBlogPostId, int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) + { + var comment = _customerContentService.GetCustomerContentById(id) as BlogComment; - var comment = _customerContentService.GetCustomerContentById(id) as BlogComment; - if (comment == null) - throw new ArgumentException("No comment found with the specified id"); + var blogPost = comment.BlogPost; + _customerContentService.DeleteCustomerContent(comment); - var blogPost = comment.BlogPost; - _customerContentService.DeleteCustomerContent(comment); - //update totals - _blogService.UpdateCommentTotals(blogPost); + //update totals + _blogService.UpdateCommentTotals(blogPost); + } return Comments(filterByBlogPostId, command); } - #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CampaignController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CampaignController.cs index 8822be0e32..77a2ca4909 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CampaignController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CampaignController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Web.Mvc; using SmartStore.Admin.Models.Messages; using SmartStore.Core.Domain.Messages; @@ -11,6 +10,8 @@ using SmartStore.Services.Security; using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -79,40 +80,39 @@ public ActionResult List() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCampaigns)) return AccessDeniedView(); - var campaigns = _campaignService.GetAllCampaigns(); - var gridModel = new GridModel - { - Data = campaigns.Select(x => - { - var model = x.ToModel(); - model.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - return model; - }), - Total = campaigns.Count - }; - return View(gridModel); + ViewData["StoreCount"] = _storeService.GetAllStores().Count(); + + return View(); } [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCampaigns)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCampaigns)) + { + var campaigns = _campaignService.GetAllCampaigns(); + + model.Data = campaigns.Select(x => + { + var m = x.ToModel(); + m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + return m; + }); + + model.Total = campaigns.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var campaigns = _campaignService.GetAllCampaigns(); - var gridModel = new GridModel - { - Data = campaigns.Select(x => - { - var model = x.ToModel(); - model.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - return model; - }), - Total = campaigns.Count - }; return new JsonResult { - Data = gridModel + Data = model }; } @@ -126,7 +126,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(CampaignModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCampaigns)) @@ -166,7 +166,7 @@ public ActionResult Edit(int id) } [HttpPost] - [ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [ParameterBasedOnFormName("save-continue", "continueEditing")] [FormValueRequired("save", "save-continue")] public ActionResult Edit(CampaignModel model, bool continueEditing) { @@ -209,14 +209,14 @@ public ActionResult SendTestEmail(CampaignModel model) try { - var emailAccount = _emailAccountService.GetEmailAccountById(_emailAccountSettings.DefaultEmailAccountId); + var emailAccount = _emailAccountService.GetDefaultEmailAccount(); if (emailAccount == null) - throw new SmartException("Email account could not be loaded"); + throw new SmartException(T("Common.Error.NoEmailAccount")); var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(model.TestEmail); if (subscription != null) { - //there's a subscription. let's use it + // there's a subscription. let's use it var subscriptions = new List(); subscriptions.Add(subscription); _campaignService.SendCampaign(campaign, emailAccount, subscriptions); @@ -227,7 +227,8 @@ public ActionResult SendTestEmail(CampaignModel model) _campaignService.SendCampaign(campaign, emailAccount, model.TestEmail); } - NotifySuccess(_localizationService.GetResource("Admin.Promotions.Campaigns.TestEmailSentToCustomers"), false); + NotifySuccess(T("Admin.Promotions.Campaigns.TestEmailSentToCustomers"), false); + return View(model); } catch (Exception exc) @@ -254,9 +255,9 @@ public ActionResult SendMassEmail(CampaignModel model) try { - var emailAccount = _emailAccountService.GetEmailAccountById(_emailAccountSettings.DefaultEmailAccountId); + var emailAccount = _emailAccountService.GetDefaultEmailAccount(); if (emailAccount == null) - throw new SmartException("Email account could not be loaded"); + throw new SmartException(T("Common.Error.NoEmailAccount")); var subscriptions = _newsLetterSubscriptionService.GetAllNewsLetterSubscriptions(null, 0 , int.MaxValue, false); var totalEmailsSent = _campaignService.SendCampaign(campaign, emailAccount, subscriptions); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs index e9fc74a00c..df70f6c9c6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs @@ -1,36 +1,36 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Catalog; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Events; -using SmartStore.Core.Infrastructure; using SmartStore.Core.Logging; using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Discounts; -using SmartStore.Services.ExportImport; +using SmartStore.Services.Filter; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Stores; -using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; using Telerik.Web.Mvc.UI; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public partial class CategoryController : AdminControllerBase { #region Fields @@ -50,31 +50,32 @@ public partial class CategoryController : AdminControllerBase private readonly IAclService _aclService; private readonly IStoreService _storeService; private readonly IStoreMappingService _storeMappingService; - private readonly IExportManager _exportManager; private readonly IWorkContext _workContext; private readonly ICustomerActivityService _customerActivityService; private readonly IDateTimeHelper _dateTimeHelper; private readonly AdminAreaSettings _adminAreaSettings; private readonly CatalogSettings _catalogSettings; private readonly IEventPublisher _eventPublisher; + private readonly IFilterService _filterService; - #endregion + #endregion - #region Constructors + #region Constructors - public CategoryController(ICategoryService categoryService, ICategoryTemplateService categoryTemplateService, + public CategoryController(ICategoryService categoryService, ICategoryTemplateService categoryTemplateService, IManufacturerService manufacturerService, IProductService productService, ICustomerService customerService, IUrlRecordService urlRecordService, IPictureService pictureService, ILanguageService languageService, ILocalizationService localizationService, ILocalizedEntityService localizedEntityService, IDiscountService discountService, IPermissionService permissionService, IAclService aclService, IStoreService storeService, IStoreMappingService storeMappingService, - IExportManager exportManager, IWorkContext workContext, + IWorkContext workContext, ICustomerActivityService customerActivityService, IDateTimeHelper dateTimeHelper, AdminAreaSettings adminAreaSettings, CatalogSettings catalogSettings, - IEventPublisher eventPublisher) + IEventPublisher eventPublisher, + IFilterService filterService) { this._categoryService = categoryService; this._categoryTemplateService = categoryTemplateService; @@ -91,13 +92,13 @@ public CategoryController(ICategoryService categoryService, ICategoryTemplateSer this._aclService = aclService; this._storeService = storeService; this._storeMappingService = storeMappingService; - this._exportManager = exportManager; this._workContext = workContext; this._customerActivityService = customerActivityService; this._dateTimeHelper = dateTimeHelper; this._adminAreaSettings = adminAreaSettings; this._catalogSettings = catalogSettings; this._eventPublisher = eventPublisher; + this._filterService = filterService; } #endregion @@ -109,30 +110,19 @@ protected void UpdateLocales(Category category, CategoryModel model) { foreach (var localized in model.Locales) { - _localizedEntityService.SaveLocalizedValue(category, - x => x.Name, - localized.Name, - localized.LanguageId); - - _localizedEntityService.SaveLocalizedValue(category, - x => x.Description, - localized.Description, - localized.LanguageId); - - _localizedEntityService.SaveLocalizedValue(category, - x => x.MetaKeywords, - localized.MetaKeywords, - localized.LanguageId); - - _localizedEntityService.SaveLocalizedValue(category, - x => x.MetaDescription, - localized.MetaDescription, - localized.LanguageId); - - _localizedEntityService.SaveLocalizedValue(category, - x => x.MetaTitle, - localized.MetaTitle, - localized.LanguageId); + _localizedEntityService.SaveLocalizedValue(category, x => x.Name, localized.Name, localized.LanguageId); + + _localizedEntityService.SaveLocalizedValue(category, x => x.FullName, localized.FullName, localized.LanguageId); + + _localizedEntityService.SaveLocalizedValue(category, x => x.Description, localized.Description, localized.LanguageId); + + _localizedEntityService.SaveLocalizedValue(category, x => x.BottomDescription, localized.BottomDescription, localized.LanguageId); + + _localizedEntityService.SaveLocalizedValue(category, x => x.MetaKeywords, localized.MetaKeywords, localized.LanguageId); + + _localizedEntityService.SaveLocalizedValue(category, x => x.MetaDescription, localized.MetaDescription, localized.LanguageId); + + _localizedEntityService.SaveLocalizedValue(category, x => x.MetaTitle, localized.MetaTitle, localized.LanguageId); //search engine name var seName = category.ValidateSeName(localized.SeName, localized.Name, false, localized.LanguageId); @@ -171,6 +161,8 @@ protected void PrepareCategoryModel(CategoryModel model, Category category, bool if (model == null) throw new ArgumentNullException("model"); + model.GridPageSize = _adminAreaSettings.GridPageSize; + var discounts = _discountService.GetAllDiscounts(DiscountType.AssignedToCategories, null, true); model.AvailableDiscounts = discounts.ToList(); @@ -276,42 +268,47 @@ public ActionResult List() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - var model = new CategoryListModel(); - var categories = _categoryService.GetAllCategories(null, 0, _adminAreaSettings.GridPageSize, true); - var mappedCategories = categories.ToDictionary(x => x.Id); - model.Categories = new GridModel - { - Data = categories.Select(x => - { - var categoryModel = x.ToModel(); - categoryModel.Breadcrumb = x.GetCategoryBreadCrumb(_categoryService, mappedCategories); - return categoryModel; - }), - Total = categories.TotalCount - }; + var allStores = _storeService.GetAllStores(); + var model = new CategoryListModel + { + GridPageSize = _adminAreaSettings.GridPageSize + }; + + foreach (var store in allStores) + { + model.AvailableStores.Add(new SelectListItem { Text = store.Name, Value = store.Id.ToString() }); + } + return View(model); } [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, CategoryListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var categories = _categoryService.GetAllCategories(model.SearchCategoryName, command.Page - 1, command.PageSize, true, model.SearchAlias, true, false); - var mappedCategories = categories.ToDictionary(x => x.Id); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var categories = _categoryService.GetAllCategories(model.SearchCategoryName, command.Page - 1, command.PageSize, true, model.SearchAlias, true, false, model.SearchStoreId); + var mappedCategories = categories.ToDictionary(x => x.Id); - var gridModel = new GridModel - { - Data = categories.Select(x => - { - var categoryModel = x.ToModel(); - categoryModel.Breadcrumb = x.GetCategoryBreadCrumb(_categoryService, mappedCategories); - return categoryModel; - }), - Total = categories.TotalCount - }; - return new JsonResult + gridModel.Data = categories.Select(x => + { + var categoryModel = x.ToModel(); + categoryModel.Breadcrumb = x.GetCategoryBreadCrumb(_categoryService, mappedCategories); + return categoryModel; + }); + + gridModel.Total = categories.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + + return new JsonResult { Data = gridModel }; @@ -376,32 +373,54 @@ public ActionResult Tree() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - var rootCategories = _categoryService.GetAllCategoriesByParentCategoryId(0, true); - return View(rootCategories); + var allStores = _storeService.GetAllStores(); + var model = new CategoryTreeModel(); + + foreach (var store in allStores) + { + model.AvailableStores.Add(new SelectListItem { Text = store.Name, Value = store.Id.ToString() }); + } + + return View(model); } //ajax [AcceptVerbs(HttpVerbs.Post)] - public ActionResult TreeLoadChildren(TreeViewItem node) + public ActionResult TreeLoadChildren(TreeViewItem node, CategoryTreeModel model) { var parentId = !string.IsNullOrEmpty(node.Value) ? Convert.ToInt32(node.Value) : 0; var urlHelper = new UrlHelper(this.ControllerContext.RequestContext); - var children = _categoryService.GetAllCategoriesByParentCategoryId(parentId, true).Select(x => + var parentCategories = _categoryService.GetAllCategoriesByParentCategoryId(parentId, true); + + if (parentId == 0 && model.SearchStoreId != 0) + { + for (int i = parentCategories.Count - 1; i >= 0; --i) + { + var category = parentCategories[i]; + if (!category.LimitedToStores || (category.LimitedToStores && !_storeMappingService.GetStoresIdsWithAccess(category).Contains(model.SearchStoreId))) + { + parentCategories.Remove(category); + } + } + } + + var children = parentCategories.Select(x => { var childCount = _categoryService.GetAllCategoriesByParentCategoryId(x.Id, true).Count; - string text = (childCount > 0 ? "{0} ({1})".FormatWith(x.Name, childCount) : x.Name); + string text = (childCount > 0 ? "{0} ({1})".FormatInvariant(x.Name, childCount) : x.Name); - var item = new TreeViewItem - { - Text = x.Alias.HasValue() ? "{0} {1}".FormatCurrent(text, x.Alias) : text, - Encoded = x.Alias.IsEmpty(), - Value = x.Id.ToString(), - LoadOnDemand = (childCount > 0), - Enabled = true, - ImageUrl = Url.Content(x.Published ? "~/Administration/Content/images/ico-content.png" : "~/Administration/Content/images/ico-content-o60.png"), + var item = new TreeViewItem + { + Text = x.Alias.HasValue() ? "{0} {1}".FormatCurrent(text, x.Alias) : text, + Encoded = x.Alias.IsEmpty(), + Value = x.Id.ToString(), + LoadOnDemand = (childCount > 0), + Enabled = true, + ImageUrl = Url.Content(x.Published ? "~/Administration/Content/images/ico-content.png" : "~/Administration/Content/images/ico-content-o60.png"), Url = urlHelper.Action("Edit", "Category", new { id = x.Id }) - }; + }; + return item; }); @@ -477,17 +496,16 @@ public ActionResult Create() return AccessDeniedView(); var model = new CategoryModel(); - //parent categories - //locales - AddLocales(_languageService, model.Locales); - //templates + + AddLocales(_languageService, model.Locales); + PrepareTemplatesModel(model); PrepareCategoryModel(model, null, true); - //ACL - PrepareAclModel(model, null, false); - //Stores + + PrepareAclModel(model, null, false); + PrepareStoresMappingModel(model, null, false); - //default values + model.PageSize = 12; model.Published = true; @@ -496,7 +514,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [ValidateInput(false)] public ActionResult Create(CategoryModel model, bool continueEditing, FormCollection form) { @@ -508,6 +526,9 @@ public ActionResult Create(CategoryModel model, bool continueEditing, FormCollec var category = model.ToEntity(); category.CreatedOnUtc = DateTime.UtcNow; category.UpdatedOnUtc = DateTime.UtcNow; + + MediaHelper.UpdatePictureTransientStateFor(category, c => c.PictureId); + _categoryService.InsertCategory(category); //search engine name @@ -578,37 +599,41 @@ public ActionResult Edit(int id) return RedirectToAction("List"); var model = category.ToModel(); - //parent categories - if (model.ParentCategoryId.HasValue) + + //parent categories + if (model.ParentCategoryId.HasValue) { var parentCategory = _categoryService.GetCategoryById(model.ParentCategoryId.Value); + if (parentCategory != null && !parentCategory.Deleted) model.ParentCategoryBreadcrumb = parentCategory.GetCategoryBreadCrumb(_categoryService); else model.ParentCategoryId = 0; } - //locales + AddLocales(_languageService, model.Locales, (locale, languageId) => { locale.Name = category.GetLocalized(x => x.Name, languageId, false, false); + locale.FullName = category.GetLocalized(x => x.FullName, languageId, false, false); locale.Description = category.GetLocalized(x => x.Description, languageId, false, false); + locale.BottomDescription = category.GetLocalized(x => x.BottomDescription, languageId, false, false); locale.MetaKeywords = category.GetLocalized(x => x.MetaKeywords, languageId, false, false); locale.MetaDescription = category.GetLocalized(x => x.MetaDescription, languageId, false, false); locale.MetaTitle = category.GetLocalized(x => x.MetaTitle, languageId, false, false); locale.SeName = category.GetSeName(languageId, false, false); }); - //templates + PrepareTemplatesModel(model); PrepareCategoryModel(model, category, false); - //ACL + PrepareAclModel(model, category, false); - //Stores + PrepareStoresMappingModel(model, category, false); return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [ValidateInput(false)] public ActionResult Edit(CategoryModel model, bool continueEditing, FormCollection form) { @@ -621,15 +646,20 @@ public ActionResult Edit(CategoryModel model, bool continueEditing, FormCollecti if (ModelState.IsValid) { - int prevPictureId = category.PictureId.GetValueOrDefault(); category = model.ToEntity(category); + + MediaHelper.UpdatePictureTransientStateFor(category, c => c.PictureId); + category.UpdatedOnUtc = DateTime.UtcNow; _categoryService.UpdateCategory(category); + //search engine name model.SeName = category.ValidateSeName(model.SeName, category.Name, true); _urlRecordService.SaveSlug(category, model.SeName, 0); + //locales UpdateLocales(category, model); + //discounts var allDiscounts = _discountService.GetAllDiscounts(DiscountType.AssignedToCategories, null, true); foreach (var discount in allDiscounts) @@ -652,14 +682,6 @@ public ActionResult Edit(CategoryModel model, bool continueEditing, FormCollecti //update "HasDiscountsApplied" property _categoryService.UpdateHasDiscountsApplied(category); - //delete an old picture (if deleted or updated) - if (prevPictureId > 0 && prevPictureId != category.PictureId) - { - var prevPicture = _pictureService.GetPictureById(prevPictureId); - if (prevPicture != null) - _pictureService.DeletePicture(prevPicture); - } - //update picture seo file name UpdatePictureSeoNames(category); @@ -700,12 +722,28 @@ public ActionResult Edit(CategoryModel model, bool continueEditing, FormCollecti return View(model); } + [ValidateInput(false)] + public ActionResult InheritAclIntoChildren(int categoryId) + { + _categoryService.InheritAclIntoChildren(categoryId, false, true, false); + + return RedirectToAction("Edit", "Category", new { id = categoryId }); + } + + [ValidateInput(false)] + public ActionResult InheritStoresIntoChildren(int categoryId) + { + _categoryService.InheritStoresIntoChildren(categoryId, false, true, false); + + return RedirectToAction("Edit", "Category", new { id = categoryId }); + } + [HttpPost] public ActionResult Delete(int id, string deleteType) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - + var category = _categoryService.GetCategoryById(id); if (category == null) return RedirectToAction("List"); @@ -721,64 +759,54 @@ public ActionResult Delete(int id, string deleteType) #endregion - #region Export / Import + #region Products - public ActionResult ExportXml() + [HttpPost, GridAction(EnableCustomBinding = true)] + public ActionResult ProductList(GridCommand command, int categoryId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - try - { - var fileName = string.Format("categories_{0}.xml", DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")); - var xml = _exportManager.ExportCategoriesToXml(); - return new XmlDownloadResult(xml, "categories.xml"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productCategories = _categoryService.GetProductCategoriesByCategoryId(categoryId, command.Page - 1, command.PageSize, true); - #endregion + var products = _productService.GetProductsByIds(productCategories.Select(x => x.ProductId).ToArray()); - #region Products + model.Data = productCategories.Select(x => + { + var productModel = new CategoryModel.CategoryProductModel + { + Id = x.Id, + CategoryId = x.CategoryId, + ProductId = x.ProductId, + IsFeaturedProduct = x.IsFeaturedProduct, + DisplayOrder1 = x.DisplayOrder + }; - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult ProductList(GridCommand command, int categoryId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productCategories = _categoryService.GetProductCategoriesByCategoryId(categoryId, - command.Page - 1, command.PageSize, true); + var product = products.FirstOrDefault(y => y.Id == x.ProductId); - var model = new GridModel - { - Data = productCategories - .Select(x => - { - var product = _productService.GetProductById(x.ProductId); + if (product != null) + { + productModel.ProductName = product.Name; + productModel.Sku = product.Sku; + productModel.ProductTypeName = product.GetProductTypeLabel(_localizationService); + productModel.ProductTypeLabelHint = product.ProductTypeLabelHint; + productModel.Published = product.Published; + } - return new CategoryModel.CategoryProductModel() - { - Id = x.Id, - CategoryId = x.CategoryId, - ProductId = x.ProductId, - ProductName = product.Name, - Sku = product.Sku, - ProductTypeName = product.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = product.ProductTypeLabelHint, - Published = product.Published, - IsFeaturedProduct = x.IsFeaturedProduct, - DisplayOrder1 = x.DisplayOrder - }; - }), - Total = productCategories.TotalCount - }; + return productModel; + }); - return new JsonResult + model.Total = productCategories.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + + return new JsonResult { Data = model }; @@ -787,16 +815,15 @@ public ActionResult ProductList(GridCommand command, int categoryId) [GridAction(EnableCustomBinding = true)] public ActionResult ProductUpdate(GridCommand command, CategoryModel.CategoryProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var productCategory = _categoryService.GetProductCategoryById(model.Id); - var productCategory = _categoryService.GetProductCategoryById(model.Id); - if (productCategory == null) - throw new ArgumentException("No product category mapping found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + productCategory.IsFeaturedProduct = model.IsFeaturedProduct; + productCategory.DisplayOrder = model.DisplayOrder1; - productCategory.IsFeaturedProduct = model.IsFeaturedProduct; - productCategory.DisplayOrder = model.DisplayOrder1; - _categoryService.UpdateProductCategory(productCategory); + _categoryService.UpdateProductCategory(productCategory); + } return ProductList(command, productCategory.CategoryId); } @@ -804,138 +831,56 @@ public ActionResult ProductUpdate(GridCommand command, CategoryModel.CategoryPro [GridAction(EnableCustomBinding = true)] public ActionResult ProductDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productCategory = _categoryService.GetProductCategoryById(id); - if (productCategory == null) - throw new ArgumentException("No product category mapping found with the specified id"); + var productCategory = _categoryService.GetProductCategoryById(id); + var categoryId = productCategory.CategoryId; - var categoryId = productCategory.CategoryId; - _categoryService.DeleteProductCategory(productCategory); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _categoryService.DeleteProductCategory(productCategory); + } return ProductList(command, categoryId); } - public ActionResult ProductAddPopup(int categoryId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - - var ctx = new ProductSearchContext(); - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageSize = _adminAreaSettings.GridPageSize; - ctx.ShowHidden = true; - - var products = _productService.SearchProducts(ctx); + [HttpPost] + public ActionResult ProductAdd(int categoryId, string selectedProductIds) + { + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productIds = selectedProductIds.SplitSafe(",").Select(x => x.ToInt()).ToArray(); + var products = _productService.GetProductsByIds(productIds); + ProductCategory productCategory = null; + var maxDisplayOrder = -1; - var model = new CategoryModel.AddCategoryProductModel(); - model.Products = new GridModel - { - Data = products.Select(x => + foreach (var product in products) { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + var existingProductCategories = _categoryService.GetProductCategoriesByCategoryId(categoryId, 0, int.MaxValue, true); - return productModel; - }), - Total = products.TotalCount - }; - // categories - var allCategories = _categoryService.GetAllCategories(showHidden: true); - var mappedCategories = allCategories.ToDictionary(x => x.Id); - foreach (var c in allCategories) - { - model.AvailableCategories.Add(new SelectListItem() { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); - } - - //manufacturers - foreach (var m in _manufacturerService.GetAllManufacturers(true)) - model.AvailableManufacturers.Add(new SelectListItem() { Text = m.Name, Value = m.Id.ToString() }); - - //product types - model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - return View(model); - } - - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult ProductAddPopupList(GridCommand command, CategoryModel.AddCategoryProductModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var gridModel = new GridModel(); - var ctx = new ProductSearchContext(); - - if (model.SearchCategoryId > 0) - ctx.CategoryIds.Add(model.SearchCategoryId); - - ctx.ManufacturerId = model.SearchManufacturerId; - ctx.Keywords = model.SearchProductName; - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageIndex = command.Page - 1; - ctx.PageSize = command.PageSize; - ctx.ShowHidden = true; - ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; - - var products = _productService.SearchProducts(ctx); - gridModel.Data = products.Select(x => + if (existingProductCategories.FindProductCategory(product.Id, categoryId) == null) + { + if (maxDisplayOrder == -1 && (productCategory = existingProductCategories.OrderByDescending(x => x.DisplayOrder).FirstOrDefault()) != null) + { + maxDisplayOrder = productCategory.DisplayOrder; + } + + _categoryService.InsertProductCategory(new ProductCategory + { + CategoryId = categoryId, + ProductId = product.Id, + IsFeaturedProduct = false, + DisplayOrder = ++maxDisplayOrder + }); + } + } + } + else { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - - return productModel; - }); - - gridModel.Total = products.TotalCount; - return new JsonResult - { - Data = gridModel - }; - } - - [HttpPost] - [FormValueRequired("save")] - public ActionResult ProductAddPopup(string btnId, string formId, CategoryModel.AddCategoryProductModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - if (model.SelectedProductIds != null) - { - foreach (int id in model.SelectedProductIds) - { - var product = _productService.GetProductById(id); - if (product != null) - { - var existingProductCategories = _categoryService.GetProductCategoriesByCategoryId(model.CategoryId, 0, int.MaxValue, true); - if (existingProductCategories.FindProductCategory(id, model.CategoryId) == null) - { - _categoryService.InsertProductCategory( - new ProductCategory() - { - CategoryId = model.CategoryId, - ProductId = id, - IsFeaturedProduct = false, - DisplayOrder = 1 - }); - } - } - } - } + NotifyAccessDenied(); + } - ViewBag.RefreshPage = true; - ViewBag.btnId = btnId; - ViewBag.formId = formId; - model.Products = new GridModel(); - return View(model); - } + return new EmptyResult(); + } - #endregion - } + #endregion + } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs index b0d2d559d6..51db07dad9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs @@ -3,61 +3,68 @@ using System.Web.Mvc; using SmartStore.Admin.Models.Orders; using SmartStore.Core; +using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Logging; +using SmartStore.Services; using SmartStore.Services.Directory; using SmartStore.Services.Localization; -using SmartStore.Core.Logging; using SmartStore.Services.Orders; using SmartStore.Services.Security; +using SmartStore.Services.Stores; using SmartStore.Services.Tax; +using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public class CheckoutAttributeController : AdminControllerBase { - #region Fields + #region Fields - private readonly ICheckoutAttributeService _checkoutAttributeService; + private readonly ICommonServices _services; + private readonly ICheckoutAttributeService _checkoutAttributeService; private readonly ILanguageService _languageService; private readonly ILocalizedEntityService _localizedEntityService; - private readonly ILocalizationService _localizationService; private readonly ITaxCategoryService _taxCategoryService; - private readonly IWorkContext _workContext; - private readonly ICurrencyService _currencyService; - private readonly CurrencySettings _currencySettings; private readonly IMeasureService _measureService; private readonly MeasureSettings _measureSettings; private readonly ICustomerActivityService _customerActivityService; - private readonly IPermissionService _permissionService; + private readonly IStoreMappingService _storeMappingService; - #endregion + private readonly AdminAreaSettings _adminAreaSettings; + + #endregion - #region Constructors + #region Constructors - public CheckoutAttributeController(ICheckoutAttributeService checkoutAttributeService, - ILanguageService languageService, ILocalizedEntityService localizedEntityService, - ILocalizationService localizationService, ITaxCategoryService taxCategoryService, - IWorkContext workContext, ICurrencyService currencyService, - ICustomerActivityService customerActivityService, CurrencySettings currencySettings, - IMeasureService measureService, MeasureSettings measureSettings, - IPermissionService permissionService) + public CheckoutAttributeController( + ICommonServices services, + ICheckoutAttributeService checkoutAttributeService, + ILanguageService languageService, + ILocalizedEntityService localizedEntityService, + ITaxCategoryService taxCategoryService, + ICustomerActivityService customerActivityService, + IMeasureService measureService, + MeasureSettings measureSettings, + IStoreMappingService storeMappingService, + AdminAreaSettings adminAreaSettings) { - this._checkoutAttributeService = checkoutAttributeService; - this._languageService = languageService; - this._localizedEntityService = localizedEntityService; - this._localizationService = localizationService; - this._taxCategoryService = taxCategoryService; - this._workContext = workContext; - this._currencyService = currencyService; - this._customerActivityService = customerActivityService; - this._currencySettings = currencySettings; - this._measureService = measureService; - this._measureSettings = measureSettings; - this._permissionService = permissionService; + _services = services; + _checkoutAttributeService = checkoutAttributeService; + _languageService = languageService; + _localizedEntityService = localizedEntityService; + _taxCategoryService = taxCategoryService; + _customerActivityService = customerActivityService; + _measureService = measureService; + _measureSettings = measureSettings; + _storeMappingService = storeMappingService; + _adminAreaSettings = adminAreaSettings; } #endregion @@ -99,17 +106,32 @@ private void PrepareCheckoutAttributeModel(CheckoutAttributeModel model, Checkou if (model == null) throw new ArgumentNullException("model"); - //tax categories var taxCategories = _taxCategoryService.GetAllTaxCategories(); - foreach (var tc in taxCategories) - model.AvailableTaxCategories.Add(new SelectListItem() { Text = tc.Name, Value = tc.Id.ToString(), Selected = checkoutAttribute != null && !excludeProperties && tc.Id == checkoutAttribute.TaxCategoryId }); - } + + foreach (var tc in taxCategories) + { + model.AvailableTaxCategories.Add(new SelectListItem + { + Text = tc.Name, + Value = tc.Id.ToString(), + Selected = (checkoutAttribute != null && !excludeProperties && tc.Id == checkoutAttribute.TaxCategoryId) + }); + } + + model.AvailableStores = _services.StoreService.GetAllStores() + .Select(s => s.ToModel()) + .ToList(); + + if (!excludeProperties) + { + model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(checkoutAttribute); + } + } #endregion #region Checkout attributes - //list public ActionResult Index() { return RedirectToAction("List"); @@ -117,75 +139,85 @@ public ActionResult Index() public ActionResult List() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(); - var gridModel = new GridModel - { - Data = checkoutAttributes.Select(x => - { - var caModel = x.ToModel(); - caModel.AttributeControlTypeName = x.AttributeControlType.GetLocalizedEnum(_localizationService, _workContext); - return caModel; - }), - Total = checkoutAttributes.Count() - }; - return View(gridModel); + var model = new CheckoutAttributeListModel + { + GridPageSize = _adminAreaSettings.GridPageSize + }; + + model.AvailableStores = _services.StoreService.GetAllStores().ToSelectListItems(); + + return View(model); } [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(); - var gridModel = new GridModel + var model = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var query = _checkoutAttributeService.GetCheckoutAttributes(0, true); + var pagedList = new PagedList(query, command.Page - 1, command.PageSize); + + model.Data = pagedList.Select(x => + { + var caModel = x.ToModel(); + caModel.AttributeControlTypeName = x.AttributeControlType.GetLocalizedEnum(_services.Localization, _services.WorkContext); + return caModel; + }); + + model.Total = pagedList.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + + return new JsonResult { - Data = checkoutAttributes.Select(x => - { - var caModel = x.ToModel(); - caModel.AttributeControlTypeName = x.AttributeControlType.GetLocalizedEnum(_localizationService, _workContext); - return caModel; - }), - Total = checkoutAttributes.Count() - }; - return new JsonResult - { - Data = gridModel + Data = model }; } //create public ActionResult Create() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); var model = new CheckoutAttributeModel(); + model.IsActive = true; + //locales AddLocales(_languageService, model.Locales); PrepareCheckoutAttributeModel(model, null, true); return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(CheckoutAttributeModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); if (ModelState.IsValid) { var checkoutAttribute = model.ToEntity(); _checkoutAttributeService.InsertCheckoutAttribute(checkoutAttribute); + UpdateAttributeLocales(checkoutAttribute, model); - //activity log - _customerActivityService.InsertActivity("AddNewCheckoutAttribute", _localizationService.GetResource("ActivityLog.AddNewCheckoutAttribute"), checkoutAttribute.Name); + _storeMappingService.SaveStoreMappings(checkoutAttribute, model.SelectedStoreIds); + + //activity log + _customerActivityService.InsertActivity("AddNewCheckoutAttribute", _services.Localization.GetResource("ActivityLog.AddNewCheckoutAttribute"), checkoutAttribute.Name); - NotifySuccess(_localizationService.GetResource("Admin.Catalog.Attributes.CheckoutAttributes.Added")); + NotifySuccess(_services.Localization.GetResource("Admin.Catalog.Attributes.CheckoutAttributes.Added")); return continueEditing ? RedirectToAction("Edit", new { id = checkoutAttribute.Id }) : RedirectToAction("List"); } @@ -197,12 +229,11 @@ public ActionResult Create(CheckoutAttributeModel model, bool continueEditing) //edit public ActionResult Edit(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); var checkoutAttribute = _checkoutAttributeService.GetCheckoutAttributeById(id); if (checkoutAttribute == null) - //No checkout attribute found with the specified id return RedirectToAction("List"); var model = checkoutAttribute.ToModel(); @@ -217,15 +248,14 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(CheckoutAttributeModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); var checkoutAttribute = _checkoutAttributeService.GetCheckoutAttributeById(model.Id); if (checkoutAttribute == null) - //No checkout attribute found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -235,10 +265,12 @@ public ActionResult Edit(CheckoutAttributeModel model, bool continueEditing) UpdateAttributeLocales(checkoutAttribute, model); - //activity log - _customerActivityService.InsertActivity("EditCheckoutAttribute", _localizationService.GetResource("ActivityLog.EditCheckoutAttribute"), checkoutAttribute.Name); + _storeMappingService.SaveStoreMappings(checkoutAttribute, model.SelectedStoreIds); - NotifySuccess(_localizationService.GetResource("Admin.Catalog.Attributes.CheckoutAttributes.Updated")); + //activity log + _customerActivityService.InsertActivity("EditCheckoutAttribute", _services.Localization.GetResource("ActivityLog.EditCheckoutAttribute"), checkoutAttribute.Name); + + NotifySuccess(_services.Localization.GetResource("Admin.Catalog.Attributes.CheckoutAttributes.Updated")); return continueEditing ? RedirectToAction("Edit", checkoutAttribute.Id) : RedirectToAction("List"); } @@ -251,16 +283,16 @@ public ActionResult Edit(CheckoutAttributeModel model, bool continueEditing) [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); var checkoutAttribute = _checkoutAttributeService.GetCheckoutAttributeById(id); _checkoutAttributeService.DeleteCheckoutAttribute(checkoutAttribute); //activity log - _customerActivityService.InsertActivity("DeleteCheckoutAttribute", _localizationService.GetResource("ActivityLog.DeleteCheckoutAttribute"), checkoutAttribute.Name); + _customerActivityService.InsertActivity("DeleteCheckoutAttribute", _services.Localization.GetResource("ActivityLog.DeleteCheckoutAttribute"), checkoutAttribute.Name); - NotifySuccess(_localizationService.GetResource("Admin.Catalog.Attributes.CheckoutAttributes.Deleted")); + NotifySuccess(_services.Localization.GetResource("Admin.Catalog.Attributes.CheckoutAttributes.Deleted")); return RedirectToAction("List"); } @@ -268,43 +300,41 @@ public ActionResult DeleteConfirmed(int id) #region Checkout attribute values - //list [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ValueList(int checkoutAttributeId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var values = _checkoutAttributeService.GetCheckoutAttributeValues(checkoutAttributeId); - var gridModel = new GridModel - { - Data = values.Select(x => - { - var model = x.ToModel(); - //locales - //AddLocales(_languageService, model.Locales, (locale, languageId) => - //{ - // locale.Name = x.GetLocalized(y => y.Name, languageId, false, false); - //}); - return model; - }), - Total = values.Count() - }; - return new JsonResult + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var values = _checkoutAttributeService.GetCheckoutAttributeValues(checkoutAttributeId); + + model.Data = values.Select(x => x.ToModel()); + + model.Total = values.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + + return new JsonResult { - Data = gridModel + Data = model }; } //create public ActionResult ValueCreatePopup(int checkoutAttributeId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); var model = new CheckoutAttributeValueModel(); model.CheckoutAttributeId = checkoutAttributeId; - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; model.BaseWeightIn = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name; //locales @@ -315,15 +345,14 @@ public ActionResult ValueCreatePopup(int checkoutAttributeId) [HttpPost] public ActionResult ValueCreatePopup(string btnId, string formId, CheckoutAttributeValueModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); var checkoutAttribute = _checkoutAttributeService.GetCheckoutAttributeById(model.CheckoutAttributeId); if (checkoutAttribute == null) - //No checkout attribute found with the specified id return RedirectToAction("List"); - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; model.BaseWeightIn = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name; if (ModelState.IsValid) @@ -346,16 +375,15 @@ public ActionResult ValueCreatePopup(string btnId, string formId, CheckoutAttrib //edit public ActionResult ValueEditPopup(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); var cav = _checkoutAttributeService.GetCheckoutAttributeValueById(id); if (cav == null) - //No checkout attribute value found with the specified id return RedirectToAction("List"); var model = cav.ToModel(); - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; model.BaseWeightIn = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name; //locales @@ -370,15 +398,14 @@ public ActionResult ValueEditPopup(int id) [HttpPost] public ActionResult ValueEditPopup(string btnId, string formId, CheckoutAttributeValueModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); var cav = _checkoutAttributeService.GetCheckoutAttributeValueById(model.Id); if (cav == null) - //No checkout attribute value found with the specified id return RedirectToAction("List"); - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; model.BaseWeightIn = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name; if (ModelState.IsValid) @@ -398,22 +425,19 @@ public ActionResult ValueEditPopup(string btnId, string formId, CheckoutAttribut return View(model); } - //delete [GridAction(EnableCustomBinding = true)] public ActionResult ValueDelete(int valueId, int checkoutAttributeId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var cav = _checkoutAttributeService.GetCheckoutAttributeValueById(valueId); - var cav = _checkoutAttributeService.GetCheckoutAttributeValueById(valueId); - if (cav == null) - throw new ArgumentException("No checkout attribute value found with the specified id"); - _checkoutAttributeService.DeleteCheckoutAttributeValue(cav); + _checkoutAttributeService.DeleteCheckoutAttributeValue(cav); + } return ValueList(checkoutAttributeId, command); } - #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index fbf04a413e..4bebf35ee1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -2,37 +2,47 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; using System.Runtime.InteropServices; using System.Security.Principal; +using System.Threading.Tasks; using System.Web.Mvc; +using Newtonsoft.Json; using SmartStore.Admin.Models.Common; +using SmartStore.Collections; using SmartStore.Core; +using SmartStore.Core.Async; using SmartStore.Core.Caching; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Logging; +using SmartStore.Core.Packaging; using SmartStore.Core.Plugins; +using SmartStore.Services; +using SmartStore.Services.Common; using SmartStore.Services.Customers; +using SmartStore.Services.DataExchange.Import; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Payments; using SmartStore.Services.Security; -using SmartStore.Services.Seo; using SmartStore.Services.Shipping; +using SmartStore.Utilities; +using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Security; -using SmartStore.Collections; -using SmartStore.Utilities; using SmartStore.Web.Framework.UI; -using SmartStore.Core.Domain.Security; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Data; using Telerik.Web.Mvc; -using SmartStore.Services.Common; -using SmartStore.Core.Domain.Common; -using System.Reflection; -using Autofac; namespace SmartStore.Admin.Controllers { @@ -46,25 +56,22 @@ public class CommonController : AdminControllerBase private readonly Lazy _currencyService; private readonly Lazy _measureService; private readonly ICustomerService _customerService; - private readonly IUrlRecordService _urlRecordService; - private readonly IWebHelper _webHelper; + private readonly Lazy _commonSettings; private readonly Lazy _currencySettings; private readonly Lazy _measureSettings; private readonly Lazy _dateTimeHelper; private readonly ILanguageService _languageService; - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; - private readonly IPermissionService _permissionService; private readonly ILocalizationService _localizationService; private readonly Lazy _imageCache; private readonly Lazy _securitySettings; private readonly Lazy _menuPublisher; private readonly Lazy _pluginFinder; - private readonly IGenericAttributeService _genericAttributeService; - private readonly IDbContext _dbContext; + private readonly Lazy _importProfileService; + private readonly IGenericAttributeService _genericAttributeService; + private readonly ICommonServices _services; private readonly Func _cache; - private readonly static object _lock = new object(); + private readonly static object s_lock = new object(); #endregion @@ -76,22 +83,19 @@ public CommonController( Lazy currencyService, Lazy measureService, ICustomerService customerService, - IUrlRecordService urlRecordService, - IWebHelper webHelper, + Lazy commonSettings, Lazy currencySettings, Lazy measureSettings, Lazy dateTimeHelper, ILanguageService languageService, - IWorkContext workContext, - IStoreContext storeContext, - IPermissionService permissionService, ILocalizationService localizationService, Lazy imageCache, Lazy securitySettings, Lazy menuPublisher, Lazy pluginFinder, - IGenericAttributeService genericAttributeService, - IDbContext dbContext, + Lazy importProfileService, + IGenericAttributeService genericAttributeService, + ICommonServices services, Func cache) { this._paymentService = paymentService; @@ -99,22 +103,19 @@ public CommonController( this._currencyService = currencyService; this._measureService = measureService; this._customerService = customerService; - this._urlRecordService = urlRecordService; - this._webHelper = webHelper; + this._commonSettings = commonSettings; this._currencySettings = currencySettings; this._measureSettings = measureSettings; this._dateTimeHelper = dateTimeHelper; this._languageService = languageService; - this._workContext = workContext; - this._storeContext = storeContext; - this._permissionService = permissionService; this._localizationService = localizationService; this._imageCache = imageCache; this._securitySettings = securitySettings; this._menuPublisher = menuPublisher; this._pluginFinder = pluginFinder; + this._importProfileService = importProfileService; this._genericAttributeService = genericAttributeService; - this._dbContext = dbContext; + this._services = services; this._cache = cache; } @@ -122,19 +123,34 @@ public CommonController( #region Methods - #region AdminMenu + #region Navbar & Menu + + [ChildActionOnly] + public ActionResult Navbar() + { + var currentCustomer = _services.WorkContext.CurrentCustomer; + + ViewBag.UserName = _services.Settings.LoadSetting().UsernamesEnabled ? currentCustomer.Username : currentCustomer.Email; + ViewBag.Stores = _services.StoreService.GetAllStores(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + { + ViewBag.CheckUpdateResult = AsyncRunner.RunSync(() => CheckUpdateAsync(false)); + } + + return PartialView(); + } [ChildActionOnly] public ActionResult Menu() { - var cacheManager = _cache("static"); + var cacheManager = _services.Cache; - var customerRolesIds = _workContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); - string cacheKey = string.Format("smartstore.pres.adminmenu.navigation-{0}-{1}", _workContext.WorkingLanguage.Id, string.Join(",", customerRolesIds)); + var customerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); + string cacheKey = string.Format("smartstore.pres.adminmenu.navigation-{0}-{1}", _services.WorkContext.WorkingLanguage.Id, string.Join(",", customerRolesIds)); var rootNode = cacheManager.Get(cacheKey, () => { - lock (_lock) { + lock (s_lock) { return PrepareAdminMenu(); } }); @@ -151,31 +167,6 @@ private TreeNode PrepareAdminMenu() _menuPublisher.Value.RegisterMenus(rootNode, "admin"); - //TreeNode pluginNode = null; - - //// "collect" menus from plugins - //var providers = new List(); - //var providerTypes = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true); - - //foreach (var type in providerTypes) - //{ - // try - // { - // var provider = Activator.CreateInstance(type) as IMenuProvider; - // if (provider.MenuName.IsCaseInsensitiveEqual("admin")) - // { - // providers.Add(provider); - // } - // } - // catch { } - //} - - //if (providers.Any()) - //{ - // pluginNode = rootNode.Children.FirstOrDefault(x => x.Value.Id == "plugins"); - // providers.Each(x => x.BuildMenu(pluginNode)); - //} - // hide based on permissions rootNode.TraverseTree(x => { if (!x.IsRoot) @@ -264,7 +255,7 @@ private bool MenuItemAccessPermitted(MenuItem item) if (_securitySettings.Value.HideAdminMenuItemsBasedOnPermissions && item.PermissionNames.HasValue()) { - var permitted = item.PermissionNames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Any(x => _permissionService.Authorize(x.Trim())); + var permitted = item.PermissionNames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Any(x => _services.Permissions.Authorize(x.Trim())); if (!permitted) { result = false; @@ -274,6 +265,146 @@ private bool MenuItemAccessPermitted(MenuItem item) return result; } + #endregion + + #region CheckUpdate + + public async Task CheckUpdate(bool enforce = false) + { + var model = await CheckUpdateAsync(enforce); + return View(model); + } + + public ActionResult CheckUpdateSuppress(string myVersion, string newVersion) + { + CheckUpdateSuppressInternal(myVersion, newVersion); + return RedirectToAction("Index", "Home"); + } + + public void CheckUpdateSuppressInternal(string myVersion, string newVersion) + { + var suppressKey = "SuppressUpdateMessage.{0}.{1}".FormatInvariant(myVersion, newVersion); + _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, suppressKey, true); + _services.Cache.RemoveByPattern("Common.CheckUpdateResult"); + } + + [NonAction] + private async Task CheckUpdateAsync(bool enforce = false, bool forSuppress = false) + { + var curVersion = SmartStoreVersion.CurrentFullVersion; + var lang = _services.WorkContext.WorkingLanguage.UniqueSeoCode; + var cacheKeyPattern = "Common.CheckUpdateResult"; + var cacheKey = "{0}.{1}".FormatInvariant(cacheKeyPattern, lang); + + if (enforce) + { + _services.Cache.RemoveByPattern(cacheKeyPattern); + } + + var result = await _services.Cache.Get(cacheKey, async () => + { + var noUpdateResult = new CheckUpdateResult { UpdateAvailable = false, LanguageCode = lang, CurrentVersion = curVersion }; + + try + { + string url = "http://dlm.smartstore.com/api/v1/apprelease/CheckUpdate?app=SMNET&version={0}&language={1}".FormatInvariant(curVersion, lang); + + using (var client = new HttpClient()) + { + client.Timeout = TimeSpan.FromMilliseconds(3000); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + client.DefaultRequestHeaders.UserAgent.ParseAdd("SmartStore.NET {0}".FormatInvariant(curVersion)); + client.DefaultRequestHeaders.Add("Authorization-Key", _services.StoreContext.CurrentStore.Url.TrimEnd('/')); + + HttpResponseMessage response = await client.GetAsync(url); + + if (response.StatusCode != HttpStatusCode.OK) + { + return noUpdateResult; + } + + var jsonStr = await response.Content.ReadAsStringAsync(); + var model = JsonConvert.DeserializeObject(jsonStr); + + model.UpdateAvailable = true; + model.CurrentVersion = curVersion; + model.LanguageCode = lang; + + if (CommonHelper.IsDevEnvironment || !_commonSettings.Value.AutoUpdateEnabled || !_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + { + model.AutoUpdatePossible = false; + } + + // don't show message if user decided to suppress it + var suppressKey = "SuppressUpdateMessage.{0}.{1}".FormatInvariant(curVersion, model.Version); + if (enforce) + { + // but ignore user's decision if 'enforce' + _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, suppressKey, null); + } + var showMessage = enforce || _services.WorkContext.CurrentCustomer.GetAttribute(suppressKey, _genericAttributeService).GetValueOrDefault() == false; + if (!showMessage) + { + return noUpdateResult; + } + + return model; + } + } + catch (Exception ex) + { + Logger.Error("An error occurred while checking for update", ex); + return noUpdateResult; + } + }, 1440 /* 24h * 60min. */); + + return result; + } + + public ActionResult InstallUpdate(string packageUrl) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + return AccessDeniedView(); + + try + { + Uri uri = new Uri(packageUrl); + string fileName = System.IO.Path.GetFileName(uri.LocalPath); + + using (var wc = new WebClient()) + { + var dir = CommonHelper.MapPath(AppUpdater.UpdatePackagePath, false); + Directory.CreateDirectory(dir); + wc.DownloadFile(packageUrl, Path.Combine(dir, fileName)); + } + + if (!InstallablePackageExists()) + { + NotifyError(_localizationService.GetResource("Admin.CheckUpdate.AutoUpdateFailure")); + } + else + { + _services.WebHelper.RestartAppDomain(); + } + + } + catch (Exception ex) + { + NotifyError(ex); + } + + return RedirectToAction("CheckUpdate", new { enforce = true }); + } + + private bool InstallablePackageExists() + { + using (var updater = new AppUpdater()) + { + return updater.InstallablePackageExists(); + } + } + #endregion [HttpPost] @@ -287,6 +418,17 @@ public JsonResult SetSelectedTab(string navId, string tabId, string path) return Json(new { Success = true }); } + [HttpPost] + public JsonResult SetGridState(string gridId, GridState state, string path) + { + if (gridId.HasValue() && state != null && path.HasValue()) + { + var info = new GridStateInfo { State = state, Path = path }; + TempData[gridId] = info; + } + return Json(new { Success = true }); + } + public ActionResult SystemInfo() { var model = new SystemInfoModel(); @@ -311,25 +453,26 @@ public ActionResult SystemInfo() model.ServerTimeZone = TimeZone.CurrentTimeZone.StandardName; model.ServerLocalTime = DateTime.Now; model.UtcTime = DateTime.UtcNow; - model.HttpHost = _webHelper.ServerVariables("HTTP_HOST"); - //Environment.GetEnvironmentVariable("USERNAME"); + model.HttpHost = _services.WebHelper.ServerVariables("HTTP_HOST"); try { - var mbSize = _dbContext.SqlQuery("Select Sum(size)/128.0 From sysfiles").FirstOrDefault(); - model.DatabaseSize = Convert.ToDouble(mbSize); + var mbSize = _services.DbContext.SqlQuery("Select Sum(size)/128.0 From sysfiles").FirstOrDefault(); + model.DatabaseSize = Convert.ToInt64(mbSize * 1024 *1024); + + model.UsedMemorySize = System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64; } - catch (Exception) { } + catch { } try { if (DataSettings.Current.IsValid()) { model.DataProviderFriendlyName = DataSettings.Current.ProviderFriendlyName; - model.ShrinkDatabaseEnabled = DataSettings.Current.IsSqlServer; + model.ShrinkDatabaseEnabled = _services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance) && DataSettings.Current.IsSqlServer; } } - catch (Exception) { } + catch { } try { @@ -337,7 +480,7 @@ public ActionResult SystemInfo() var fi = new FileInfo(assembly.Location); model.AppDate = fi.LastWriteTime.ToLocalTime(); } - catch (Exception) { } + catch { } foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { @@ -354,35 +497,84 @@ public ActionResult SystemInfo() public ActionResult Warnings() { var model = new List(); + var store = _services.StoreContext.CurrentStore; //store URL - var currentStoreUrl = _storeContext.CurrentStore.Url.EnsureEndsWith("/"); - if (currentStoreUrl.HasValue() && (currentStoreUrl.IsCaseInsensitiveEqual(_webHelper.GetStoreLocation(false)) || currentStoreUrl.IsCaseInsensitiveEqual(_webHelper.GetStoreLocation(true)))) - model.Add(new SystemWarningModel() - { - Level = SystemWarningLevel.Pass, - Text = _localizationService.GetResource("Admin.System.Warnings.URL.Match") - }); - else - model.Add(new SystemWarningModel() - { - Level = SystemWarningLevel.Warning, - Text = string.Format(_localizationService.GetResource("Admin.System.Warnings.URL.NoMatch"), currentStoreUrl, _webHelper.GetStoreLocation(false)) - }); + var storeUrl = store.Url.EnsureEndsWith("/"); + if (storeUrl.HasValue() && (storeUrl.IsCaseInsensitiveEqual(_services.WebHelper.GetStoreLocation(false)) || storeUrl.IsCaseInsensitiveEqual(_services.WebHelper.GetStoreLocation(true)))) + { + model.Add(new SystemWarningModel + { + Level = SystemWarningLevel.Pass, + Text = _localizationService.GetResource("Admin.System.Warnings.URL.Match") + }); + } + else + { + model.Add(new SystemWarningModel + { + Level = SystemWarningLevel.Warning, + Text = string.Format(_localizationService.GetResource("Admin.System.Warnings.URL.NoMatch"), storeUrl, _services.WebHelper.GetStoreLocation(false)) + }); + } + // sitemap reachability + string sitemapUrl = null; + try + { + sitemapUrl = WebHelper.GetAbsoluteUrl(Url.RouteUrl("SitemapSEO"), this.Request); + var request = WebHelper.CreateHttpRequestForSafeLocalCall(new Uri(sitemapUrl)); + request.Method = "HEAD"; + request.Timeout = 15000; + + using (var response = (HttpWebResponse)request.GetResponse()) + { + var status = response.StatusCode; + var warningModel = new SystemWarningModel(); + warningModel.Level = (status == HttpStatusCode.OK ? SystemWarningLevel.Pass : SystemWarningLevel.Warning); + + switch (status) + { + case HttpStatusCode.OK: + warningModel.Text = T("Admin.System.Warnings.SitemapReachable.OK"); + break; + default: + if (status == HttpStatusCode.MethodNotAllowed) + warningModel.Text = T("Admin.System.Warnings.SitemapReachable.MethodNotAllowed"); + else + warningModel.Text = T("Admin.System.Warnings.SitemapReachable.Wrong"); + + warningModel.Text = string.Concat(warningModel.Text, " ", T("Admin.Common.HttpStatus", (int)status, status.ToString())); + break; + } + + model.Add(warningModel); + } + } + catch (WebException exception) + { + model.Add(new SystemWarningModel + { + Level = SystemWarningLevel.Warning, + Text = T("Admin.System.Warnings.SitemapReachable.Wrong") + }); + + Logger.Warning(sitemapUrl.IsEmpty() ? "SitemapSEO" : sitemapUrl, exception); + } //primary exchange rate currency - var perCurrency = _currencyService.Value.GetCurrencyById(_currencySettings.Value.PrimaryExchangeRateCurrencyId); + var perCurrency = store.PrimaryExchangeRateCurrency; if (perCurrency != null) { - model.Add(new SystemWarningModel() + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Pass, Text = _localizationService.GetResource("Admin.System.Warnings.ExchangeCurrency.Set"), }); + if (perCurrency.Rate != 1) { - model.Add(new SystemWarningModel() + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Fail, Text = _localizationService.GetResource("Admin.System.Warnings.ExchangeCurrency.Rate1") @@ -391,7 +583,7 @@ public ActionResult Warnings() } else { - model.Add(new SystemWarningModel() + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Fail, Text = _localizationService.GetResource("Admin.System.Warnings.ExchangeCurrency.NotSet") @@ -399,10 +591,10 @@ public ActionResult Warnings() } //primary store currency - var pscCurrency = _currencyService.Value.GetCurrencyById(_currencySettings.Value.PrimaryStoreCurrencyId); + var pscCurrency = store.PrimaryStoreCurrency; if (pscCurrency != null) { - model.Add(new SystemWarningModel() + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Pass, Text = _localizationService.GetResource("Admin.System.Warnings.PrimaryCurrency.Set"), @@ -410,7 +602,7 @@ public ActionResult Warnings() } else { - model.Add(new SystemWarningModel() + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Fail, Text = _localizationService.GetResource("Admin.System.Warnings.PrimaryCurrency.NotSet") @@ -422,7 +614,7 @@ public ActionResult Warnings() var bWeight = _measureService.Value.GetMeasureWeightById(_measureSettings.Value.BaseWeightId); if (bWeight != null) { - model.Add(new SystemWarningModel() + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Pass, Text = _localizationService.GetResource("Admin.System.Warnings.DefaultWeight.Set"), @@ -430,7 +622,7 @@ public ActionResult Warnings() if (bWeight.Ratio != 1) { - model.Add(new SystemWarningModel() + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Fail, Text = _localizationService.GetResource("Admin.System.Warnings.DefaultWeight.Ratio1") @@ -439,7 +631,7 @@ public ActionResult Warnings() } else { - model.Add(new SystemWarningModel() + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Fail, Text = _localizationService.GetResource("Admin.System.Warnings.DefaultWeight.NotSet") @@ -451,7 +643,7 @@ public ActionResult Warnings() var bDimension = _measureService.Value.GetMeasureDimensionById(_measureSettings.Value.BaseDimensionId); if (bDimension != null) { - model.Add(new SystemWarningModel() + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Pass, Text = _localizationService.GetResource("Admin.System.Warnings.DefaultDimension.Set"), @@ -459,7 +651,7 @@ public ActionResult Warnings() if (bDimension.Ratio != 1) { - model.Add(new SystemWarningModel() + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Fail, Text = _localizationService.GetResource("Admin.System.Warnings.DefaultDimension.Ratio1") @@ -468,101 +660,133 @@ public ActionResult Warnings() } else { - model.Add(new SystemWarningModel() + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Fail, Text = _localizationService.GetResource("Admin.System.Warnings.DefaultDimension.NotSet") }); } - // shipping rate coputation methods - if (_shippingService.Value.LoadActiveShippingRateComputationMethods() - .Where(x => x.Value.ShippingRateComputationMethodType == ShippingRateComputationMethodType.Offline) - .Count() > 1) - model.Add(new SystemWarningModel() - { - Level = SystemWarningLevel.Warning, - Text = _localizationService.GetResource("Admin.System.Warnings.Shipping.OnlyOneOffline") - }); + // shipping rate coputation methods + int activeShippingMethodCount = 0; + + try + { + activeShippingMethodCount = _shippingService.Value.LoadActiveShippingRateComputationMethods() + .Where(x => x.Value.ShippingRateComputationMethodType == ShippingRateComputationMethodType.Offline) + .Count(); + } + catch { } + + if (activeShippingMethodCount > 1) + { + model.Add(new SystemWarningModel + { + Level = SystemWarningLevel.Warning, + Text = _localizationService.GetResource("Admin.System.Warnings.Shipping.OnlyOneOffline") + }); + } //payment methods - if (_paymentService.Value.LoadActivePaymentMethods() - .Count() > 0) - model.Add(new SystemWarningModel() - { - Level = SystemWarningLevel.Pass, - Text = _localizationService.GetResource("Admin.System.Warnings.PaymentMethods.OK") - }); - else - model.Add(new SystemWarningModel() - { - Level = SystemWarningLevel.Fail, - Text = _localizationService.GetResource("Admin.System.Warnings.PaymentMethods.NoActive") - }); + int activePaymentMethodCount = 0; + + try + { + activePaymentMethodCount = _paymentService.Value.LoadActivePaymentMethods().Count(); + } + catch { } + + if (activePaymentMethodCount > 0) + { + model.Add(new SystemWarningModel + { + Level = SystemWarningLevel.Pass, + Text = _localizationService.GetResource("Admin.System.Warnings.PaymentMethods.OK") + }); + } + else + { + model.Add(new SystemWarningModel + { + Level = SystemWarningLevel.Fail, + Text = _localizationService.GetResource("Admin.System.Warnings.PaymentMethods.NoActive") + }); + } //incompatible plugins - if (PluginManager.IncompatiblePlugins != null) - foreach (var pluginName in PluginManager.IncompatiblePlugins) - model.Add(new SystemWarningModel() - { - Level = SystemWarningLevel.Warning, - Text = string.Format(_localizationService.GetResource("Admin.System.Warnings.IncompatiblePlugin"), pluginName ) - }); + if (PluginManager.IncompatiblePlugins != null) + { + foreach (var pluginName in PluginManager.IncompatiblePlugins) + { + model.Add(new SystemWarningModel + { + Level = SystemWarningLevel.Warning, + Text = string.Format(_localizationService.GetResource("Admin.System.Warnings.IncompatiblePlugin"), pluginName) + }); + } + } //validate write permissions (the same procedure like during installation) var dirPermissionsOk = true; - var dirsToCheck = FilePermissionHelper.GetDirectoriesWrite(_webHelper); - foreach (string dir in dirsToCheck) - if (!FilePermissionHelper.CheckPermissions(dir, false, true, true, false)) - { - model.Add(new SystemWarningModel() - { - Level = SystemWarningLevel.Warning, - Text = string.Format(_localizationService.GetResource("Admin.System.Warnings.DirectoryPermission.Wrong"), WindowsIdentity.GetCurrent().Name, dir) - }); - dirPermissionsOk = false; - } - if (dirPermissionsOk) - model.Add(new SystemWarningModel() - { - Level = SystemWarningLevel.Pass, - Text = _localizationService.GetResource("Admin.System.Warnings.DirectoryPermission.OK") - }); + var dirsToCheck = FilePermissionHelper.GetDirectoriesWrite(_services.WebHelper); + foreach (string dir in dirsToCheck) + { + if (!FilePermissionHelper.CheckPermissions(dir, false, true, true, false)) + { + model.Add(new SystemWarningModel + { + Level = SystemWarningLevel.Warning, + Text = string.Format(_localizationService.GetResource("Admin.System.Warnings.DirectoryPermission.Wrong"), WindowsIdentity.GetCurrent().Name, dir) + }); + dirPermissionsOk = false; + } + } + if (dirPermissionsOk) + { + model.Add(new SystemWarningModel + { + Level = SystemWarningLevel.Pass, + Text = _localizationService.GetResource("Admin.System.Warnings.DirectoryPermission.OK") + }); + } var filePermissionsOk = true; - var filesToCheck = FilePermissionHelper.GetFilesWrite(_webHelper); - foreach (string file in filesToCheck) - if (!FilePermissionHelper.CheckPermissions(file, false, true, true, true)) - { - model.Add(new SystemWarningModel() - { - Level = SystemWarningLevel.Warning, - Text = string.Format(_localizationService.GetResource("Admin.System.Warnings.FilePermission.Wrong"), WindowsIdentity.GetCurrent().Name, file) - }); - filePermissionsOk = false; - } - if (filePermissionsOk) - model.Add(new SystemWarningModel() - { - Level = SystemWarningLevel.Pass, - Text = _localizationService.GetResource("Admin.System.Warnings.FilePermission.OK") - }); - + var filesToCheck = FilePermissionHelper.GetFilesWrite(_services.WebHelper); + foreach (string file in filesToCheck) + { + if (!FilePermissionHelper.CheckPermissions(file, false, true, true, true)) + { + model.Add(new SystemWarningModel + { + Level = SystemWarningLevel.Warning, + Text = string.Format(_localizationService.GetResource("Admin.System.Warnings.FilePermission.Wrong"), WindowsIdentity.GetCurrent().Name, file) + }); + filePermissionsOk = false; + } + } + if (filePermissionsOk) + { + model.Add(new SystemWarningModel + { + Level = SystemWarningLevel.Pass, + Text = _localizationService.GetResource("Admin.System.Warnings.FilePermission.OK") + }); + } return View(model); } public ActionResult ShrinkDatabase() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) return AccessDeniedView(); try { if (DataSettings.Current.IsSqlServer) { - _dbContext.ExecuteSqlCommand("DBCC SHRINKDATABASE(0)", true); - NotifySuccess(_localizationService.GetResource("Common.ShrinkDatabaseSuccessful")); + _services.DbContext.ExecuteSqlCommand("DBCC SHRINKDATABASE(0)", true); + NotifySuccess(T("Common.ShrinkDatabaseSuccessful")); } } catch (Exception ex) @@ -573,9 +797,28 @@ public ActionResult ShrinkDatabase() return RedirectToReferrer(); } - public ActionResult Maintenance() + public async Task GarbageCollect() + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + return AccessDeniedView(); + + try + { + GC.Collect(); + await Task.Delay(500); + NotifySuccess(T("Admin.System.SystemInfo.GarbageCollectSuccessful")); + } + catch (Exception ex) + { + NotifyError(ex); + } + + return RedirectToReferrer(); + } + + public ActionResult Maintenance() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) return AccessDeniedView(); var model = new MaintenanceModel(); @@ -596,11 +839,14 @@ public ActionResult Maintenance() [FormValueRequired("delete-image-cache")] public ActionResult MaintenanceDeleteImageCache() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) return AccessDeniedView(); _imageCache.Value.DeleteCachedImages(); + // get rid of cached image metadata + _cache("static").Clear(); + return RedirectToAction("Maintenance"); } @@ -608,7 +854,7 @@ public ActionResult MaintenanceDeleteImageCache() [FormValueRequired("delete-guests")] public ActionResult MaintenanceDeleteGuests(MaintenanceModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) return AccessDeniedView(); DateTime? startDateValue = (model.DeleteGuests.StartDate == null) ? null @@ -626,51 +872,93 @@ public ActionResult MaintenanceDeleteGuests(MaintenanceModel model) [FormValueRequired("delete-exported-files")] public ActionResult MaintenanceDeleteFiles(MaintenanceModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) return AccessDeniedView(); DateTime? startDateValue = (model.DeleteExportedFiles.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(model.DeleteExportedFiles.StartDate.Value, _dateTimeHelper.Value.CurrentTimeZone); + : (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(model.DeleteExportedFiles.StartDate.Value, _dateTimeHelper.Value.CurrentTimeZone); DateTime? endDateValue = (model.DeleteExportedFiles.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(model.DeleteExportedFiles.EndDate.Value, _dateTimeHelper.Value.CurrentTimeZone).AddDays(1); + : (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(model.DeleteExportedFiles.EndDate.Value, _dateTimeHelper.Value.CurrentTimeZone).AddDays(1); model.DeleteExportedFiles.NumberOfDeletedFiles = 0; - string path = string.Format("{0}Content\\files\\exportimport\\", this.Request.PhysicalApplicationPath); - foreach (var fullPath in System.IO.Directory.GetFiles(path)) - { - try - { - var fileName = Path.GetFileName(fullPath); - if (fileName.Equals("index.htm", StringComparison.InvariantCultureIgnoreCase)) - continue; + model.DeleteExportedFiles.NumberOfDeletedFolders = 0; - if (fileName.Equals("placeholder", StringComparison.InvariantCultureIgnoreCase)) - continue; + var appPath = this.Request.PhysicalApplicationPath; - var info = new FileInfo(fullPath); - if ((!startDateValue.HasValue || startDateValue.Value < info.CreationTimeUtc)&& - (!endDateValue.HasValue || info.CreationTimeUtc < endDateValue.Value)) - { - System.IO.File.Delete(fullPath); - model.DeleteExportedFiles.NumberOfDeletedFiles++; - } - } - catch (Exception exc) - { - NotifyError(exc, false); - } - } + string[] paths = new string[] + { + appPath + @"Content\files\exportimport\", + appPath + @"Exchange\", + appPath + @"App_Data\ExportProfiles\" + }; - return View(model); + foreach (var path in paths) + { + foreach (var fullPath in System.IO.Directory.GetFiles(path)) + { + try + { + var fileName = Path.GetFileName(fullPath); + if (fileName.Equals("index.htm", StringComparison.InvariantCultureIgnoreCase)) + continue; + + if (fileName.Equals("placeholder", StringComparison.InvariantCultureIgnoreCase)) + continue; + + var info = new FileInfo(fullPath); + + if ((!startDateValue.HasValue || startDateValue.Value < info.CreationTimeUtc) && + (!endDateValue.HasValue || info.CreationTimeUtc < endDateValue.Value)) + { + if (FileSystemHelper.Delete(fullPath)) + ++model.DeleteExportedFiles.NumberOfDeletedFiles; + } + } + catch (Exception exc) + { + NotifyError(exc, false); + } + } + + var dir = new DirectoryInfo(path); + + foreach (var dirInfo in dir.GetDirectories()) + { + if ((!startDateValue.HasValue || startDateValue.Value < dirInfo.LastWriteTimeUtc) && + (!endDateValue.HasValue || dirInfo.LastWriteTimeUtc < endDateValue.Value)) + { + FileSystemHelper.ClearDirectory(dirInfo.FullName, true); + ++model.DeleteExportedFiles.NumberOfDeletedFolders; + } + } + } + + // clear unreferenced profile folders + var importProfileFolders = _importProfileService.Value.GetImportProfiles() + .Select(x => x.FolderName) + .ToList(); + + var infoImportProfiles = new DirectoryInfo(CommonHelper.MapPath("~/App_Data/ImportProfiles")); + + foreach (var infoSubFolder in infoImportProfiles.GetDirectories()) + { + if (!importProfileFolders.Contains(infoSubFolder.Name)) + { + FileSystemHelper.ClearDirectory(infoSubFolder.FullName, true); + ++model.DeleteExportedFiles.NumberOfDeletedFolders; + } + } + + return View(model); } [HttpPost, ActionName("Maintenance"), ValidateInput(false)] [FormValueRequired("execute-sql-query")] public ActionResult MaintenanceExecuteSql(MaintenanceModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) return AccessDeniedView(); if (model.SqlQuery.HasValue()) @@ -680,25 +968,24 @@ public ActionResult MaintenanceExecuteSql(MaintenanceModel model) { dbContext.ExecuteSqlThroughSmo(model.SqlQuery); - NotifySuccess("The sql command was executed successfully."); + NotifySuccess(T("Admin.System.Maintenance.SqlQuery.Succeeded")); } - catch (Exception ex) + catch (Exception exception) { - NotifyError("Error executing sql command: {0}".FormatCurrentUI(ex.Message)); + NotifyError(exception); } } return View(model); } - //language [ChildActionOnly] public ActionResult LanguageSelector() { var model = new LanguageSelectorModel(); - model.CurrentLanguage = _workContext.WorkingLanguage.ToModel(); + model.CurrentLanguage = _services.WorkContext.WorkingLanguage.ToModel(); model.AvailableLanguages = _languageService - .GetAllLanguages(storeId: _storeContext.CurrentStore.Id) + .GetAllLanguages(storeId: _services.StoreContext.CurrentStore.Id) .Select(x => x.ToModel()) .ToList(); return PartialView(model); @@ -708,14 +995,14 @@ public ActionResult LanguageSelected(int customerlanguage) var language = _languageService.GetLanguageById(customerlanguage); if (language != null) { - _workContext.WorkingLanguage = language; + _services.WorkContext.WorkingLanguage = language; } return Content(_localizationService.GetResource("Admin.Common.DataEditSuccess")); } public ActionResult ClearCache(string previousUrl) { - var cacheManager = _cache("static"); + var cacheManager = _services.Cache; cacheManager.Clear(); cacheManager = _cache("aspnet"); @@ -731,10 +1018,10 @@ public ActionResult ClearCache(string previousUrl) public ActionResult RestartApplication(string previousUrl) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) return AccessDeniedView(); - _webHelper.RestartAppDomain(); + _services.WebHelper.RestartAppDomain(); if (previousUrl.HasValue()) return Redirect(previousUrl); @@ -744,80 +1031,6 @@ public ActionResult RestartApplication(string previousUrl) #endregion - #region Search engine friendly names - - public ActionResult SeNames() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMaintenance)) - return AccessDeniedView(); - - var model = new UrlRecordListModel(); - return View(model); - } - - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult SeNames(GridCommand command, UrlRecordListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMaintenance)) - return AccessDeniedView(); - - var urlRecords = _urlRecordService.GetAllUrlRecords(model.SeName, command.Page - 1, command.PageSize); - var gridModel = new GridModel - { - Data = urlRecords.Select(x => - { - string languageName; - if (x.LanguageId == 0) - { - languageName = _localizationService.GetResource("Admin.System.SeNames.Language.Standard"); - } - else - { - var language = _languageService.GetLanguageById(x.LanguageId); - languageName = language != null ? language.Name : "Unknown"; - } - return new UrlRecordModel() - { - Id = x.Id, - Name = x.Slug, - EntityId = x.EntityId, - EntityName = x.EntityName, - IsActive = x.IsActive, - Language = languageName, - }; - }), - Total = urlRecords.TotalCount - }; - return new JsonResult - { - Data = gridModel - }; - } - - [HttpPost] - public ActionResult DeleteSelectedSeNames(ICollection selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMaintenance)) - return AccessDeniedView(); - - if (selectedIds != null) - { - var urlRecords = new List(); - foreach (var id in selectedIds) - { - var urlRecord = _urlRecordService.GetUrlRecordById(id); - if (urlRecord != null) - urlRecords.Add(urlRecord); - } - foreach (var urlRecord in urlRecords) - _urlRecordService.DeleteUrlRecord(urlRecord); - } - - return Json(new { Result = true }); - } - - #endregion - #region Generic Attributes [ChildActionOnly] @@ -832,35 +1045,43 @@ public ActionResult GenericAttributes(string entityName, int entityId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult GenericAttributesSelect(string entityName, int entityId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel)) - return AccessDeniedView(); + var model = new GridModel(); - var storeId = _storeContext.CurrentStore.Id; - ViewBag.StoreId = storeId; + var storeId = _services.StoreContext.CurrentStore.Id; + ViewBag.StoreId = storeId; - var model = new List(); - if (entityName.HasValue() && entityId > 0) - { - var attributes = _genericAttributeService.GetAttributesForEntity(entityId, entityName); - var query = from attr in attributes - where attr.StoreId == storeId || attr.StoreId == 0 - select new GenericAttributeModel - { - Id = attr.Id, - EntityId = attr.EntityId, - EntityName = attr.KeyGroup, - Key = attr.Key, - Value = attr.Value - }; - model.AddRange(query); - } + if (_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) + { + if (entityName.HasValue() && entityId > 0) + { + var attributes = _genericAttributeService.GetAttributesForEntity(entityId, entityName); + model.Data = attributes + .Where(x => x.StoreId == storeId || x.StoreId == 0) + .Select(x => new GenericAttributeModel + { + Id = x.Id, + EntityId = x.EntityId, + EntityName = x.KeyGroup, + Key = x.Key, + Value = x.Value + }) + .ToList(); + + model.Total = model.Data.Count(); + } + else + { + model.Data = Enumerable.Empty(); + } + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var result = new GridModel - { - Data = model, - Total = model.Count - }; return new JsonResult { Data = model @@ -870,38 +1091,36 @@ public ActionResult GenericAttributesSelect(string entityName, int entityId, Gri [GridAction(EnableCustomBinding = true)] public ActionResult GenericAttributeAdd(GenericAttributeModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel)) - return AccessDeniedView(); - - model.Key = model.Key.TrimSafe(); - model.Value = model.Value.TrimSafe(); + if (_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) + { + model.Key = model.Key.TrimSafe(); + model.Value = model.Value.TrimSafe(); - if (!ModelState.IsValid) - { - // display the first model error - var modelStateErrorMessages = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrorMessages.FirstOrDefault()); - } + if (!ModelState.IsValid) + { + var modelStateErrorMessages = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrorMessages.FirstOrDefault()); + } - var storeId = _storeContext.CurrentStore.Id; + var storeId = _services.StoreContext.CurrentStore.Id; - var attr = _genericAttributeService.GetAttribute(model.EntityName, model.EntityId, model.Key, storeId); - if (attr == null) - { - var ga = new GenericAttribute - { - StoreId = storeId, - KeyGroup = model.EntityName, - EntityId = model.EntityId, - Key = model.Key, - Value = model.Value - }; - _genericAttributeService.InsertAttribute(ga); - } - else - { - return Content(string.Format(_localizationService.GetResource("Admin.Common.GenericAttributes.NameAlreadyExists"), model.Key)); - } + var attr = _genericAttributeService.GetAttribute(model.EntityName, model.EntityId, model.Key, storeId); + if (attr == null) + { + _genericAttributeService.InsertAttribute(new GenericAttribute + { + StoreId = storeId, + KeyGroup = model.EntityName, + EntityId = model.EntityId, + Key = model.Key, + Value = model.Value + }); + } + else + { + return Content(T("Admin.Common.GenericAttributes.NameAlreadyExists", model.Key)); + } + } return GenericAttributesSelect(model.EntityName, model.EntityId, command); } @@ -909,38 +1128,38 @@ public ActionResult GenericAttributeAdd(GenericAttributeModel model, GridCommand [GridAction(EnableCustomBinding = true)] public ActionResult GenericAttributeUpdate(GenericAttributeModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) + { + model.Key = model.Key.TrimSafe(); + model.Value = model.Value.TrimSafe(); - model.Key = model.Key.TrimSafe(); - model.Value = model.Value.TrimSafe(); + if (!ModelState.IsValid) + { + var modelStateErrorMessages = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrorMessages.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - // display the first model error - var modelStateErrorMessages = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrorMessages.FirstOrDefault()); - } + var storeId = _services.StoreContext.CurrentStore.Id; - var storeId = _storeContext.CurrentStore.Id; + var attr = _genericAttributeService.GetAttributeById(model.Id); + // if the key changed, ensure it isn't being used by another attribute + if (!attr.Key.IsCaseInsensitiveEqual(model.Key)) + { + var attr2 = _genericAttributeService.GetAttributesForEntity(model.EntityId, model.EntityName) + .Where(x => x.StoreId == storeId && x.Key.Equals(model.Key, StringComparison.InvariantCultureIgnoreCase)) + .FirstOrDefault(); - var attr = _genericAttributeService.GetAttributeById(model.Id); - // if the key changed, ensure it isn't being used by another attribute - if (!attr.Key.IsCaseInsensitiveEqual(model.Key)) - { - var attr2 = _genericAttributeService.GetAttributesForEntity(model.EntityId, model.EntityName) - .Where(x => x.StoreId == storeId && x.Key.Equals(model.Key, StringComparison.InvariantCultureIgnoreCase)) - .FirstOrDefault(); - if (attr2 != null && attr2.Id != attr.Id) - { - return Content(string.Format(_localizationService.GetResource("Admin.Common.GenericAttributes.NameAlreadyExists"), model.Key)); - } - } + if (attr2 != null && attr2.Id != attr.Id) + { + return Content(T("Admin.Common.GenericAttributes.NameAlreadyExists", model.Key)); + } + } - attr.Key = model.Key; - attr.Value = model.Value; + attr.Key = model.Key; + attr.Value = model.Value; - _genericAttributeService.UpdateAttribute(attr); + _genericAttributeService.UpdateAttribute(attr); + } return GenericAttributesSelect(model.EntityName, model.EntityId, command); } @@ -948,17 +1167,12 @@ public ActionResult GenericAttributeUpdate(GenericAttributeModel model, GridComm [GridAction(EnableCustomBinding = true)] public ActionResult GenericAttributeDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel)) - return AccessDeniedView(); - var attr = _genericAttributeService.GetAttributeById(id); - if (attr == null) - { - throw new System.Web.HttpException(404, "No resource found with the specified id"); - } - - _genericAttributeService.DeleteAttribute(attr); + if (_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) + { + _genericAttributeService.DeleteAttribute(attr); + } return GenericAttributesSelect(attr.KeyGroup, attr.EntityId, command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ContentSliderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ContentSliderController.cs index bcde6b6048..73192b52db 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ContentSliderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ContentSliderController.cs @@ -2,20 +2,22 @@ using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.ContentSlider; +using SmartStore.Core; using SmartStore.Core.Domain.Cms; +using SmartStore.Core.Domain.Localization; using SmartStore.Services.Configuration; using SmartStore.Services.Localization; -using SmartStore.Services.Security; -using SmartStore.Web.Framework.Controllers; using SmartStore.Services.Media; -using SmartStore.Core.Domain.Localization; +using SmartStore.Services.Security; using SmartStore.Services.Stores; -using SmartStore.Core; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public class ContentSliderController : AdminControllerBase { #region Fields @@ -62,12 +64,12 @@ public ContentSliderController( ISettingService settingService, private ContentSliderSettingsModel PrepareContentSliderSettingsModel(ContentSliderSettingsModel modelIn = null) { int rowIndex = 0; + var allStores = _storeService.GetAllStores(); var model = _contentSliderSettings.ToModel(); - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var s in _storeService.GetAllStores()) + foreach (var s in allStores) { - model.AvailableStores.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString() }); + model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); } foreach (var slide in model.Slides) @@ -131,6 +133,8 @@ public ActionResult Index(ContentSliderSettingsModel model) _contentSliderSettings.AutoPlay = model.AutoPlay; _contentSliderSettings.AutoPlayDelay = model.AutoPlayDelay; + MediaHelper.UpdatePictureTransientState(0, model.BackgroundPictureId, true); + _settingService.SaveSetting(_contentSliderSettings); var viewModel = PrepareContentSliderSettingsModel(model); @@ -171,6 +175,7 @@ public ActionResult CreateSlide(ContentSliderSlideModel model) model.PictureUrl = _pictureService.GetPictureUrl(model.PictureId); model.LanguageName = _languageService.GetLanguageByCulture(model.LanguageCulture).Name; _contentSliderSettings.Slides.Add(model.ToEntity()); + MediaHelper.UpdatePictureTransientState(0, model.PictureId, true); _settingService.SaveSetting(_contentSliderSettings); NotifySuccess(_localizationService.GetResource("Admin.Configuration.ContentSlider.Slide.Added")); @@ -198,7 +203,7 @@ public ActionResult EditSlide(int index) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult EditSlide(ContentSliderSlideModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageContentSlider)) @@ -229,16 +234,11 @@ public ActionResult EditSlide(ContentSliderSlideModel model, bool continueEditin model.LanguageName = lang.Name; } - model.PictureUrl = _pictureService.GetPictureUrl(model.PictureId); + model.PictureUrl = _pictureService.GetPictureUrl(model.PictureId); - //delete an old picture (if deleted or updated) - int prevPictureId = _contentSliderSettings.Slides[index].PictureId; - if (prevPictureId > 0 && prevPictureId != model.PictureId) - { - var prevPicture = _pictureService.GetPictureById(prevPictureId); - if (prevPicture != null) - _pictureService.DeletePicture(prevPicture); - } + // delete an old picture (if deleted or updated) + int prevPictureId = _contentSliderSettings.Slides[index].PictureId; + MediaHelper.UpdatePictureTransientState(prevPictureId, model.PictureId, true); _contentSliderSettings.Slides[index] = model.ToEntity(); _settingService.SaveSetting(_contentSliderSettings); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs index 9554f642c0..3a4d573b43 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs @@ -3,13 +3,16 @@ using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Directory; -using SmartStore.Core; using SmartStore.Core.Domain.Directory; +using SmartStore.Services; using SmartStore.Services.Common; using SmartStore.Services.Directory; using SmartStore.Services.Localization; using SmartStore.Services.Security; +using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -21,28 +24,31 @@ public class CountryController : AdminControllerBase private readonly ICountryService _countryService; private readonly IStateProvinceService _stateProvinceService; - private readonly ILocalizationService _localizationService; private readonly IAddressService _addressService; - private readonly IPermissionService _permissionService; private readonly ILocalizedEntityService _localizedEntityService; private readonly ILanguageService _languageService; + private readonly IStoreMappingService _storeMappingService; + private readonly ICommonServices _services; - #endregion + #endregion #region Constructors - public CountryController(ICountryService countryService, - IStateProvinceService stateProvinceService, ILocalizationService localizationService, - IAddressService addressService, IPermissionService permissionService, - ILocalizedEntityService localizedEntityService, ILanguageService languageService) + public CountryController(ICountryService countryService, + IStateProvinceService stateProvinceService, + IAddressService addressService, + ILocalizedEntityService localizedEntityService, + ILanguageService languageService, + IStoreMappingService storeMappingService, + ICommonServices services) { - this._countryService = countryService; - this._stateProvinceService = stateProvinceService; - this._localizationService = localizationService; - this._addressService = addressService; - this._permissionService = permissionService; - this._localizedEntityService = localizedEntityService; - this._languageService = languageService; + _countryService = countryService; + _stateProvinceService = stateProvinceService; + _addressService = addressService; + _localizedEntityService = localizedEntityService; + _languageService = languageService; + _storeMappingService = storeMappingService; + _services = services; } #endregion  @@ -50,28 +56,39 @@ public CountryController(ICountryService countryService, #region Utilities [NonAction] - public void UpdateLocales(Country country, CountryModel model) + private void UpdateLocales(Country country, CountryModel model) { foreach (var localized in model.Locales) { - _localizedEntityService.SaveLocalizedValue(country, - x => x.Name, - localized.Name, - localized.LanguageId); + _localizedEntityService.SaveLocalizedValue(country, x => x.Name, localized.Name, localized.LanguageId); } } [NonAction] - public void UpdateLocales(StateProvince stateProvince, StateProvinceModel model) + private void UpdateLocales(StateProvince stateProvince, StateProvinceModel model) { foreach (var localized in model.Locales) { - _localizedEntityService.SaveLocalizedValue(stateProvince, - x => x.Name, - localized.Name, - localized.LanguageId); + _localizedEntityService.SaveLocalizedValue(stateProvince, x => x.Name, localized.Name, localized.LanguageId); } } + + [NonAction] + private void PrepareCountryModel(CountryModel model, Country country, bool excludeProperties) + { + if (model == null) + throw new ArgumentNullException("model"); + + var allStores = _services.StoreService.GetAllStores(); + + model.AvailableStores = allStores.Select(s => s.ToModel()).ToList(); + + if (!excludeProperties) + { + model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(country); + } + } + #endregion #region Countries @@ -83,137 +100,194 @@ public ActionResult Index() public ActionResult List() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); - var countries = _countryService.GetAllCountries(true); - var model = new GridModel - { - Data = countries.Select(x => x.ToModel()), - Total = countries.Count - }; - return View(model); + var allStores = _services.StoreService.GetAllStores(); + + var model = new CountryListModel + { + StoreCount = allStores.Count + }; + + return View(model); } [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CountryList(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) - return AccessDeniedView(); + var model = new GridModel(); - var countries = _countryService.GetAllCountries(true); - var model = new GridModel - { - Data = countries.Select(x => x.ToModel()), - Total = countries.Count - }; + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) + { + var countries = _countryService.GetAllCountries(true); + + model.Data = countries.Select(x => x.ToModel()); + model.Total = countries.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { Data = model }; } - - public ActionResult Create() + + [GridAction(EnableCustomBinding = true)] + public ActionResult CountryUpdate(CountryModel model, GridCommand command) + { + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } + + var country = _countryService.GetCountryById(model.Id); + + country = model.ToEntity(country); + _countryService.UpdateCountry(country); + } + + return CountryList(command); + } + + [GridAction(EnableCustomBinding = true)] + public ActionResult CountryDelete(int id, GridCommand command) + { + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) + { + if (_addressService.GetAddressTotalByCountryId(id) > 0) + { + return Content(T("Admin.Configuration.Countries.CannotDeleteDueToAssociatedAddresses")); + } + + var country = _countryService.GetCountryById(id); + + _countryService.DeleteCountry(country); + } + + return CountryList(command); + } + + public ActionResult Create() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var model = new CountryModel(); - //locales + AddLocales(_languageService, model.Locales); - //default values + PrepareCountryModel(model, null, false); + + //default values model.Published = true; model.AllowsBilling = true; model.AllowsShipping = true; + return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(CountryModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); if (ModelState.IsValid) { var country = model.ToEntity(); _countryService.InsertCountry(country); - //locales + UpdateLocales(country, model); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Countries.Added")); + _storeMappingService.SaveStoreMappings(country, model.SelectedStoreIds); + + NotifySuccess(T("Admin.Configuration.Countries.Added")); + return continueEditing ? RedirectToAction("Edit", new { id = country.Id }) : RedirectToAction("List"); } - //If we got this far, something failed, redisplay form + PrepareCountryModel(model, null, true); + return View(model); } public ActionResult Edit(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var country = _countryService.GetCountryById(id); if (country == null) - //No country found with the specified id return RedirectToAction("List"); var model = country.ToModel(); - //locales + AddLocales(_languageService, model.Locales, (locale, languageId) => { locale.Name = country.GetLocalized(x => x.Name, languageId, false, false); }); + + PrepareCountryModel(model, country, false); + return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(CountryModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var country = _countryService.GetCountryById(model.Id); if (country == null) - //No country found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) { country = model.ToEntity(country); _countryService.UpdateCountry(country); - //locales + UpdateLocales(country, model); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Countries.Updated")); + _storeMappingService.SaveStoreMappings(country, model.SelectedStoreIds); + + NotifySuccess(T("Admin.Configuration.Countries.Updated")); + return continueEditing ? RedirectToAction("Edit", new { id = country.Id }) : RedirectToAction("List"); } - //If we got this far, something failed, redisplay form + PrepareCountryModel(model, country, true); + return View(model); } [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var country = _countryService.GetCountryById(id); if (country == null) - //No country found with the specified id return RedirectToAction("List"); try { if (_addressService.GetAddressTotalByCountryId(country.Id) > 0) - throw new SmartException("The country can't be deleted. It has associated addresses"); + throw new SmartException(T("Admin.Configuration.Countries.CannotDeleteDueToAssociatedAddresses")); _countryService.DeleteCountry(country); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Countries.Deleted")); + NotifySuccess(T("Admin.Configuration.Countries.Deleted")); + return RedirectToAction("List"); } catch (Exception exc) @@ -223,7 +297,6 @@ public ActionResult DeleteConfirmed(int id) } } - #endregion #region States / provinces @@ -231,46 +304,53 @@ public ActionResult DeleteConfirmed(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult States(int countryId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) - return AccessDeniedView(); + var model = new GridModel(); - var states = _stateProvinceService.GetStateProvincesByCountryId(countryId, true) - .Select(x => x.ToModel()); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) + { + var states = _stateProvinceService.GetStateProvincesByCountryId(countryId, true) + .Select(x => x.ToModel()); + + model.Data = states; + model.Total = states.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var model = new GridModel - { - Data = states, - Total = states.Count() - }; return new JsonResult { Data = model }; } - //create public ActionResult StateCreatePopup(int countryId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); - var model = new StateProvinceModel(); - model.CountryId = countryId; - //locales + var model = new StateProvinceModel + { + CountryId = countryId + }; + AddLocales(_languageService, model.Locales); + return View(model); } [HttpPost] public ActionResult StateCreatePopup(string btnId, string formId, StateProvinceModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var country = _countryService.GetCountryById(model.CountryId); if (country == null) - //No country found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -283,6 +363,7 @@ public ActionResult StateCreatePopup(string btnId, string formId, StateProvinceM ViewBag.RefreshPage = true; ViewBag.btnId = btnId; ViewBag.formId = formId; + return View(model); } @@ -293,16 +374,15 @@ public ActionResult StateCreatePopup(string btnId, string formId, StateProvinceM //edit public ActionResult StateEditPopup(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var sp = _stateProvinceService.GetStateProvinceById(id); if (sp == null) - //No state found with the specified id return RedirectToAction("List"); var model = sp.ToModel(); - //locales + AddLocales(_languageService, model.Locales, (locale, languageId) => { locale.Name = sp.GetLocalized(x => x.Name, languageId, false, false); @@ -314,12 +394,11 @@ public ActionResult StateEditPopup(int id) [HttpPost] public ActionResult StateEditPopup(string btnId, string formId, StateProvinceModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var sp = _stateProvinceService.GetStateProvinceById(model.Id); if (sp == null) - //No state found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -332,6 +411,7 @@ public ActionResult StateEditPopup(string btnId, string formId, StateProvinceMod ViewBag.RefreshPage = true; ViewBag.btnId = btnId; ViewBag.formId = formId; + return View(model); } @@ -342,45 +422,39 @@ public ActionResult StateEditPopup(string btnId, string formId, StateProvinceMod [GridAction(EnableCustomBinding = true)] public ActionResult StateDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) - return AccessDeniedView(); - - var state = _stateProvinceService.GetStateProvinceById(id); - if (state == null) - throw new ArgumentException("No state found with the specified id"); - - if (_addressService.GetAddressTotalByStateProvinceId(state.Id) > 0) - return Content(_localizationService.GetResource("Admin.Configuration.Countries.States.CantDeleteWithAddresses")); + var state = _stateProvinceService.GetStateProvinceById(id); + var countryId = state.CountryId; - int countryId = state.CountryId; - _stateProvinceService.DeleteStateProvince(state); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) + { + if (_addressService.GetAddressTotalByStateProvinceId(state.Id) > 0) + { + return Content(T("Admin.Configuration.Countries.States.CantDeleteWithAddresses")); + } + _stateProvinceService.DeleteStateProvince(state); + } return States(countryId, command); } [AcceptVerbs(HttpVerbs.Get)] - public ActionResult GetStatesByCountryId(string countryId, - bool? addEmptyStateIfRequired, bool? addAsterisk) + public ActionResult GetStatesByCountryId(string countryId, bool? addEmptyStateIfRequired, bool? addAsterisk) { - //permission validation is not required here - + // permission validation is not required here // This action method gets called via an ajax request - // codehint: sm-edit - int cid = 0; - int.TryParse(countryId, out cid); - /*if (String.IsNullOrEmpty(countryId)) - throw new ArgumentNullException("countryId");*/ + var country = _countryService.GetCountryById(countryId.ToInt()); - var country = _countryService.GetCountryById(cid /* Convert.ToInt32(countryId) */); var states = country != null ? _stateProvinceService.GetStateProvincesByCountryId(country.Id, true).ToList() : new List(); - var result = (from s in states - select new { id = s.Id, name = s.Name }).ToList(); + var result = (from s in states select new { id = s.Id, name = s.Name }).ToList(); + if (addEmptyStateIfRequired.HasValue && addEmptyStateIfRequired.Value && result.Count == 0) - result.Insert(0, new { id = 0, name = _localizationService.GetResource("Admin.Address.OtherNonUS") }); + result.Insert(0, new { id = 0, name = T("Admin.Address.OtherNonUS").Text }); + if (addAsterisk.HasValue && addAsterisk.Value) result.Insert(0, new { id = 0, name = "*" }); + return Json(result, JsonRequestBehavior.AllowGet); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CurrencyController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CurrencyController.cs index 6af5cd0a89..e20b59013c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CurrencyController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CurrencyController.cs @@ -4,14 +4,17 @@ using System.Web.Mvc; using SmartStore.Admin.Models.Directory; using SmartStore.Core.Domain.Directory; -using SmartStore.Services.Configuration; +using SmartStore.Core.Domain.Stores; +using SmartStore.Services; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Security; using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -23,40 +26,34 @@ public class CurrencyController : AdminControllerBase private readonly ICurrencyService _currencyService; private readonly CurrencySettings _currencySettings; - private readonly ISettingService _settingService; private readonly IDateTimeHelper _dateTimeHelper; - private readonly ILocalizationService _localizationService; - private readonly IPermissionService _permissionService; private readonly ILocalizedEntityService _localizedEntityService; private readonly ILanguageService _languageService; - private readonly IStoreService _storeService; private readonly IStoreMappingService _storeMappingService; private readonly PluginMediator _pluginMediator; + private readonly ICommonServices _services; #endregion #region Constructors public CurrencyController(ICurrencyService currencyService, - CurrencySettings currencySettings, ISettingService settingService, - IDateTimeHelper dateTimeHelper, ILocalizationService localizationService, - IPermissionService permissionService, - ILocalizedEntityService localizedEntityService, ILanguageService languageService, - IStoreService storeService, + CurrencySettings currencySettings, + IDateTimeHelper dateTimeHelper, + ILocalizedEntityService localizedEntityService, + ILanguageService languageService, IStoreMappingService storeMappingService, - PluginMediator pluginMediator) + PluginMediator pluginMediator, + ICommonServices services) { this._currencyService = currencyService; this._currencySettings = currencySettings; - this._settingService = settingService; this._dateTimeHelper = dateTimeHelper; - this._localizationService = localizationService; - this._permissionService = permissionService; this._localizedEntityService = localizedEntityService; this._languageService = languageService; - this._storeService = storeService; this._storeMappingService = storeMappingService; this._pluginMediator = pluginMediator; + this._services = services; } #endregion @@ -75,27 +72,68 @@ public void UpdateLocales(Currency currency, CurrencyModel model) } } - [NonAction] - private void PrepareStoresMappingModel(CurrencyModel model, Currency currency, bool excludeProperties) + private void PrepareCurrencyModel(CurrencyModel model, Currency currency, bool excludeProperties) { if (model == null) throw new ArgumentNullException("model"); - model.AvailableStores = _storeService - .GetAllStores() - .Select(s => s.ToModel()) - .ToList(); + var allStores = _services.StoreService.GetAllStores(); + + model.AvailableStores = allStores.Select(s => s.ToModel()).ToList(); + + if (currency != null) + { + model.PrimaryStoreCurrencyStores = allStores + .Where(x => x.PrimaryStoreCurrencyId == currency.Id) + .Select(x => new SelectListItem + { + Text = x.Name, + Value = Url.Action("Edit", "Store", new { id = x.Id }) + }) + .ToList(); + + model.PrimaryExchangeRateCurrencyStores = allStores + .Where(x => x.PrimaryExchangeRateCurrencyId == currency.Id) + .Select(x => new SelectListItem + { + Text = x.Name, + Value = Url.Action("Edit", "Store", new { id = x.Id }) + }) + .ToList(); + } + if (!excludeProperties) { - if (currency != null) - { - model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(currency); - } - else + model.SelectedStoreIds = (currency == null ? new int[0] : _storeMappingService.GetStoresIdsWithAccess(currency)); + } + } + + private bool IsAttachedToStore(Currency currency, IList stores, bool force) + { + var attachedStore = stores.FirstOrDefault(x => x.PrimaryStoreCurrencyId == currency.Id || x.PrimaryExchangeRateCurrencyId == currency.Id); + + if (attachedStore != null) + { + if (force || (!force && !currency.Published)) { - model.SelectedStoreIds = new int[0]; + NotifyError(T("Admin.Configuration.Currencies.DeleteOrPublishStoreConflict", attachedStore.Name)); + return true; } + + // Must store limitations include the store where the currency is attached as primary or exchange rate currency? + //if (currency.LimitedToStores) + //{ + // if (selectedStoreIds == null) + // selectedStoreIds = _storeMappingService.GetStoreMappingsFor("Currency", currency.Id).Select(x => x.StoreId).ToArray(); + + // if (!selectedStoreIds.Contains(attachedStore.Id)) + // { + // NotifyError(T("Admin.Configuration.Currencies.StoreLimitationConflict", attachedStore.Name)); + // return true; + // } + //} } + return false; } #endregion @@ -109,19 +147,35 @@ public ActionResult Index() public ActionResult List(bool liveRates = false) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCurrencies)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCurrencies)) return AccessDeniedView(); - var currenciesModel = _currencyService.GetAllCurrencies(true).Select(x => x.ToModel()).ToList(); - foreach (var currency in currenciesModel) - currency.IsPrimaryExchangeRateCurrency = currency.Id == _currencySettings.PrimaryExchangeRateCurrencyId ? true : false; - foreach (var currency in currenciesModel) - currency.IsPrimaryStoreCurrency = currency.Id == _currencySettings.PrimaryStoreCurrencyId ? true : false; + var store = _services.StoreContext.CurrentStore; + var models = _currencyService.GetAllCurrencies(true).Select(x => x.ToModel()).ToList(); + + foreach (var model in models) + { + model.IsPrimaryStoreCurrency = (store.PrimaryStoreCurrencyId == model.Id); + model.IsPrimaryExchangeRateCurrency = (store.PrimaryExchangeRateCurrencyId == model.Id); + } + + //var allStores = _services.StoreService.GetAllStores(); + //foreach (var model in models) + //{ + // var storeNames = allStores.Where(x => x.PrimaryStoreCurrencyId == model.Id).Select(x => x.Name).ToList(); + + // model.IsPrimaryStoreCurrency = string.Join("
    ", storeNames); + + // storeNames = allStores.Where(x => x.PrimaryExchangeRateCurrencyId == model.Id).Select(x => x.Name).ToList(); + + // model.IsPrimaryExchangeRateCurrency = string.Join("
    ", storeNames); + //} + if (liveRates) { try { - var primaryExchangeCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryExchangeRateCurrencyId); + var primaryExchangeCurrency = _services.StoreContext.CurrentStore.PrimaryExchangeRateCurrency; if (primaryExchangeCurrency == null) throw new SmartException("Primary exchange rate currency is not set"); @@ -132,28 +186,32 @@ public ActionResult List(bool liveRates = false) NotifyError(exc, false); } } + ViewBag.ExchangeRateProviders = new List(); + foreach (var erp in _currencyService.LoadAllExchangeRateProviders()) { - ViewBag.ExchangeRateProviders.Add(new SelectListItem() + ViewBag.ExchangeRateProviders.Add(new SelectListItem { Text = _pluginMediator.GetLocalizedFriendlyName(erp.Metadata), Value = erp.Metadata.SystemName, Selected = erp.Metadata.SystemName.Equals(_currencySettings.ActiveExchangeRateProviderSystemName, StringComparison.InvariantCultureIgnoreCase) }); } + ViewBag.AutoUpdateEnabled = _currencySettings.AutoUpdateEnabled; + var gridModel = new GridModel { - Data = currenciesModel, - Total = currenciesModel.Count() + Data = models, + Total = models.Count() }; return View(gridModel); } public ActionResult ApplyRate(string currencyCode, decimal rate) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCurrencies)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCurrencies)) return AccessDeniedView(); Currency currency = _currencyService.GetCurrencyByCode(currencyCode); @@ -169,51 +227,38 @@ public ActionResult ApplyRate(string currencyCode, decimal rate) [HttpPost] public ActionResult Save(FormCollection formValues) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCurrencies)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCurrencies)) return AccessDeniedView(); _currencySettings.ActiveExchangeRateProviderSystemName = formValues["exchangeRateProvider"]; _currencySettings.AutoUpdateEnabled = formValues["autoUpdateEnabled"].Equals("false") ? false : true; - _settingService.SaveSetting(_currencySettings); + _services.Settings.SaveSetting(_currencySettings); return RedirectToAction("List", "Currency"); } [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCurrencies)) - return AccessDeniedView(); + var model = new GridModel(); - var currencies = _currencyService.GetAllCurrencies(true); - var gridModel = new GridModel - { - Data = currencies.Select(x => x.ToModel()), - Total = currencies.Count() - }; - return new JsonResult - { - Data = gridModel - }; - } - - public ActionResult MarkAsPrimaryExchangeRateCurrency(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCurrencies)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCurrencies)) + { + var currencies = _currencyService.GetAllCurrencies(true); - _currencySettings.PrimaryExchangeRateCurrencyId = id; - _settingService.SaveSetting(_currencySettings); - return RedirectToAction("List"); - } + model.Data = currencies.Select(x => x.ToModel()); + model.Total = currencies.Count(); + } + else + { + model.Data = Enumerable.Empty(); - public ActionResult MarkAsPrimaryStoreCurrency(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCurrencies)) - return AccessDeniedView(); + NotifyAccessDenied(); + } - _currencySettings.PrimaryStoreCurrencyId = id; - _settingService.SaveSetting(_currencySettings); - return RedirectToAction("List"); + return new JsonResult + { + Data = model + }; } #endregion @@ -222,24 +267,24 @@ public ActionResult MarkAsPrimaryStoreCurrency(int id) public ActionResult Create() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCurrencies)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCurrencies)) return AccessDeniedView(); var model = new CurrencyModel(); //locales AddLocales(_languageService, model.Locales); //Stores - PrepareStoresMappingModel(model, null, false); + PrepareCurrencyModel(model, null, false); //default values model.Published = true; model.Rate = 1; return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(CurrencyModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCurrencies)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCurrencies)) return AccessDeniedView(); if (ModelState.IsValid) @@ -256,21 +301,21 @@ public ActionResult Create(CurrencyModel model, bool continueEditing) //Stores _storeMappingService.SaveStoreMappings(currency, model.SelectedStoreIds); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Currencies.Added")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Currencies.Added")); return continueEditing ? RedirectToAction("Edit", new { id = currency.Id }) : RedirectToAction("List"); } //If we got this far, something failed, redisplay form //Stores - PrepareStoresMappingModel(model, null, true); + PrepareCurrencyModel(model, null, true); return View(model); } public ActionResult Edit(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCurrencies)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCurrencies)) return AccessDeniedView(); var currency = _currencyService.GetCurrencyById(id); @@ -296,15 +341,15 @@ public ActionResult Edit(int id) } //Stores - PrepareStoresMappingModel(model, currency, false); + PrepareCurrencyModel(model, currency, false); return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(CurrencyModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCurrencies)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCurrencies)) return AccessDeniedView(); var currency = _currencyService.GetCurrencyById(model.Id); @@ -314,25 +359,29 @@ public ActionResult Edit(CurrencyModel model, bool continueEditing) if (ModelState.IsValid) { currency = model.ToEntity(currency); - currency.UpdatedOnUtc = DateTime.UtcNow; + + if (!IsAttachedToStore(currency, _services.StoreService.GetAllStores(), false)) + { + currency.UpdatedOnUtc = DateTime.UtcNow; - _currencyService.UpdateCurrency(currency); + _currencyService.UpdateCurrency(currency); - //locales - UpdateLocales(currency, model); + //locales + UpdateLocales(currency, model); - //Stores - _storeMappingService.SaveStoreMappings(currency, model.SelectedStoreIds); + //Stores + _storeMappingService.SaveStoreMappings(currency, model.SelectedStoreIds); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Currencies.Updated")); - return continueEditing ? RedirectToAction("Edit", new { id = currency.Id }) : RedirectToAction("List"); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Currencies.Updated")); + return continueEditing ? RedirectToAction("Edit", new { id = currency.Id }) : RedirectToAction("List"); + } } //If we got this far, something failed, redisplay form model.CreatedOn = _dateTimeHelper.ConvertToUserTime(currency.CreatedOnUtc, DateTimeKind.Utc); //Stores - PrepareStoresMappingModel(model, currency, true); + PrepareCurrencyModel(model, currency, true); return View(model); } @@ -340,7 +389,7 @@ public ActionResult Edit(CurrencyModel model, bool continueEditing) [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCurrencies)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCurrencies)) return AccessDeniedView(); var currency = _currencyService.GetCurrencyById(id); @@ -349,22 +398,20 @@ public ActionResult DeleteConfirmed(int id) try { - if (currency.Id == _currencySettings.PrimaryStoreCurrencyId) - throw new SmartException(_localizationService.GetResource("Admin.Configuration.Currencies.CantDeletePrimary")); - - if (currency.Id == _currencySettings.PrimaryExchangeRateCurrencyId) - throw new SmartException(_localizationService.GetResource("Admin.Configuration.Currencies.CantDeleteExchange")); - - _currencyService.DeleteCurrency(currency); + if (!IsAttachedToStore(currency, _services.StoreService.GetAllStores(), true)) + { + _currencyService.DeleteCurrency(currency); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Currencies.Deleted")); - return RedirectToAction("List"); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Currencies.Deleted")); + return RedirectToAction("List"); + } } catch (Exception exc) { NotifyError(exc); - return RedirectToAction("Edit", new { id = currency.Id }); } + + return RedirectToAction("Edit", new { id = currency.Id }); } #endregion diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs index 0a8e0c6858..44b6c10a0f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Text; -using System.Web.ModelBinding; using System.Web.Mvc; using SmartStore.Admin.Models.Common; using SmartStore.Admin.Models.Customers; @@ -26,7 +24,6 @@ using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Directory; -using SmartStore.Services.ExportImport; using SmartStore.Services.Forums; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; @@ -37,13 +34,15 @@ using SmartStore.Services.Tax; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public partial class CustomerController : AdminControllerBase { #region Fields @@ -67,7 +66,6 @@ public partial class CustomerController : AdminControllerBase private readonly IStoreContext _storeContext; private readonly IPriceFormatter _priceFormatter; private readonly IOrderService _orderService; - private readonly IExportManager _exportManager; private readonly ICustomerActivityService _customerActivityService; private readonly IPriceCalculationService _priceCalculationService; private readonly IPermissionService _permissionService; @@ -84,11 +82,11 @@ public partial class CustomerController : AdminControllerBase private readonly PluginMediator _pluginMediator; private readonly IAffiliateService _affiliateService; - #endregion + #endregion - #region Constructors + #region Constructors - public CustomerController(ICustomerService customerService, + public CustomerController(ICustomerService customerService, INewsLetterSubscriptionService newsLetterSubscriptionService, IGenericAttributeService genericAttributeService, ICustomerRegistrationService customerRegistrationService, @@ -101,7 +99,6 @@ public CustomerController(ICustomerService customerService, IWorkContext workContext, IStoreContext storeContext, IPriceFormatter priceFormatter, IOrderService orderService, - IExportManager exportManager, ICustomerActivityService customerActivityService, IPriceCalculationService priceCalculationService, IPermissionService permissionService, AdminAreaSettings adminAreaSettings, @@ -112,7 +109,7 @@ public CustomerController(ICustomerService customerService, IEventPublisher eventPublisher, PluginMediator pluginMediator, IAffiliateService affiliateService) - { + { this._customerService = customerService; this._newsLetterSubscriptionService = newsLetterSubscriptionService; this._genericAttributeService = genericAttributeService; @@ -132,7 +129,6 @@ public CustomerController(ICustomerService customerService, this._storeContext = storeContext; this._priceFormatter = priceFormatter; this._orderService = orderService; - this._exportManager = exportManager; this._customerActivityService = customerActivityService; this._priceCalculationService = priceCalculationService; this._permissionService = permissionService; @@ -148,7 +144,7 @@ public CustomerController(ICustomerService customerService, this._eventPublisher = eventPublisher; this._pluginMediator = pluginMediator; this._affiliateService = affiliateService; - } + } #endregion @@ -234,6 +230,7 @@ protected CustomerModel PrepareCustomerModelForList(Customer customer) Username = customer.Username, FullName = customer.GetFullName(), Company = customer.GetAttribute(SystemCustomerAttributeNames.Company), + CustomerNumber = customer.GetAttribute(SystemCustomerAttributeNames.CustomerNumber), Phone = customer.GetAttribute(SystemCustomerAttributeNames.Phone), ZipPostalCode = customer.GetAttribute(SystemCustomerAttributeNames.ZipPostalCode), CustomerRoleNames = GetCustomerRolesNames(customer.CustomerRoles.ToList()), @@ -253,11 +250,10 @@ private void PrepareCustomerModelForCreate(CustomerModel model) foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) { - model.AvailableTimeZones.Add(new SelectListItem() { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == timeZoneId) }); + model.AvailableTimeZones.Add(new SelectListItem { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == timeZoneId) }); } model.DisplayVatNumber = false; - //customer roles model.AvailableCustomerRoles = _customerService .GetAllCustomerRoles(true) .Select(cr => cr.ToModel()) @@ -281,14 +277,15 @@ private void PrepareCustomerModelForCreate(CustomerModel model) model.StateProvinceEnabled = _customerSettings.StateProvinceEnabled; model.PhoneEnabled = _customerSettings.PhoneEnabled; model.FaxEnabled = _customerSettings.FaxEnabled; - + model.CustomerNumberEnabled = _customerSettings.CustomerNumberMethod != CustomerNumberMethod.Disabled; + if (_customerSettings.CountryEnabled) { - model.AvailableCountries.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + model.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); foreach (var c in _countryService.GetAllCountries()) { - model.AvailableCountries.Add(new SelectListItem() { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.CountryId) }); + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.CountryId) }); } if (_customerSettings.StateProvinceEnabled) @@ -299,12 +296,12 @@ private void PrepareCustomerModelForCreate(CustomerModel model) { foreach (var s in states) { - model.AvailableStates.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); + model.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); } } else { - model.AvailableStates.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); + model.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); } } } @@ -399,28 +396,37 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CustomerList(GridCommand command, CustomerListModel model, [ModelBinderAttribute(typeof(CommaSeparatedModelBinder))] int[] searchCustomerRoleIds) { - //we use own own binder for searchCustomerRoleIds property - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); + // we use own own binder for searchCustomerRoleIds property + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + { + var searchDayOfBirth = 0; + var searchMonthOfBirth = 0; + + if (!String.IsNullOrWhiteSpace(model.SearchDayOfBirth)) + searchDayOfBirth = Convert.ToInt32(model.SearchDayOfBirth); + + if (!String.IsNullOrWhiteSpace(model.SearchMonthOfBirth)) + searchMonthOfBirth = Convert.ToInt32(model.SearchMonthOfBirth); + + var customers = _customerService.GetAllCustomers(null, null, + searchCustomerRoleIds, model.SearchEmail, model.SearchUsername, + model.SearchFirstName, model.SearchLastName, + searchDayOfBirth, searchMonthOfBirth, + model.SearchCompany, model.SearchPhone, model.SearchZipPostalCode, + false, null, command.Page - 1, command.PageSize); + + gridModel.Data = customers.Select(PrepareCustomerModelForList); + gridModel.Total = customers.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var searchDayOfBirth = 0; - int searchMonthOfBirth = 0; - if (!String.IsNullOrWhiteSpace(model.SearchDayOfBirth)) - searchDayOfBirth = Convert.ToInt32(model.SearchDayOfBirth); - if (!String.IsNullOrWhiteSpace(model.SearchMonthOfBirth)) - searchMonthOfBirth = Convert.ToInt32(model.SearchMonthOfBirth); - - var customers = _customerService.GetAllCustomers(null, null, - searchCustomerRoleIds, model.SearchEmail, model.SearchUsername, - model.SearchFirstName, model.SearchLastName, - searchDayOfBirth, searchMonthOfBirth, - model.SearchCompany, model.SearchPhone, model.SearchZipPostalCode, - false, null, command.Page - 1, command.PageSize); - var gridModel = new GridModel - { - Data = customers.Select(PrepareCustomerModelForList), - Total = customers.TotalCount - }; return new JsonResult { Data = gridModel @@ -442,7 +448,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [FormValueRequired("save", "save-continue")] [ValidateInput(false)] public ActionResult Create(CustomerModel model, bool continueEditing, FormCollection form) @@ -525,6 +531,25 @@ public ActionResult Create(CustomerModel model, bool continueEditing, FormCollec _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Phone, model.Phone); if (_customerSettings.FaxEnabled) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Fax, model.Fax); + if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.AutomaticallySet && String.IsNullOrEmpty(model.CustomerNumber)) + { + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CustomerNumber, customer.Id); + _eventPublisher.Publish(new CustomerRegisteredEvent { Customer = customer }); + } + else + { + var customerNumbers = _genericAttributeService.GetAttributes(SystemCustomerAttributeNames.CustomerNumber, "customer"); + + if (customerNumbers.Where(x => x.Value == model.CustomerNumber).Any()) + { + this.NotifyError("Common.CustomerNumberAlreadyExists"); + } + else + { + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CustomerNumber, model.CustomerNumber); + } + } + //password if (!String.IsNullOrWhiteSpace(model.Password)) @@ -568,7 +593,6 @@ public ActionResult Edit(int id) var customer = _customerService.GetCustomerById(id); if (customer == null || customer.Deleted) - //No customer found with the specified id return RedirectToAction("List"); var model = new CustomerModel(); @@ -582,12 +606,18 @@ public ActionResult Edit(int id) model.UsernamesEnabled = _customerSettings.UsernamesEnabled; model.AllowUsersToChangeUsernames = _customerSettings.AllowUsersToChangeUsernames; model.AllowCustomersToSetTimeZone = _dateTimeSettings.AllowCustomersToSetTimeZone; - foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) - model.AvailableTimeZones.Add(new SelectListItem() { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == model.TimeZoneId) }); + + foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) + { + model.AvailableTimeZones.Add(new SelectListItem { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == model.TimeZoneId) }); + } + model.DisplayVatNumber = _taxSettings.EuVatEnabled; model.VatNumber = customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); + model.VatNumberStatusNote = ((VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId)) .GetLocalizedEnum(_localizationService, _workContext); + model.CreatedOn = _dateTimeHelper.ConvertToUserTime(customer.CreatedOnUtc, DateTimeKind.Utc); model.LastActivityDate = _dateTimeHelper.ConvertToUserTime(customer.LastActivityDateUtc, DateTimeKind.Utc); model.LastIpAddress = customer.LastIpAddress; @@ -607,6 +637,7 @@ public ActionResult Edit(int id) model.Gender = customer.GetAttribute(SystemCustomerAttributeNames.Gender); model.DateOfBirth = customer.GetAttribute(SystemCustomerAttributeNames.DateOfBirth); model.Company = customer.GetAttribute(SystemCustomerAttributeNames.Company); + model.CustomerNumber = customer.GetAttribute(SystemCustomerAttributeNames.CustomerNumber); model.StreetAddress = customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress); model.StreetAddress2 = customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress2); model.ZipPostalCode = customer.GetAttribute(SystemCustomerAttributeNames.ZipPostalCode); @@ -619,6 +650,7 @@ public ActionResult Edit(int id) model.GenderEnabled = _customerSettings.GenderEnabled; model.DateOfBirthEnabled = _customerSettings.DateOfBirthEnabled; model.CompanyEnabled = _customerSettings.CompanyEnabled; + model.CustomerNumberEnabled = _customerSettings.CustomerNumberMethod != CustomerNumberMethod.Disabled; model.StreetAddressEnabled = _customerSettings.StreetAddressEnabled; model.StreetAddress2Enabled = _customerSettings.StreetAddress2Enabled; model.ZipPostalCodeEnabled = _customerSettings.ZipPostalCodeEnabled; @@ -631,10 +663,10 @@ public ActionResult Edit(int id) //countries and states if (_customerSettings.CountryEnabled) { - model.AvailableCountries.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + model.AvailableCountries.Add(new SelectListItem { Text = T("Admin.Address.SelectCountry"), Value = "0" }); foreach (var c in _countryService.GetAllCountries()) { - model.AvailableCountries.Add(new SelectListItem() + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), @@ -646,14 +678,17 @@ public ActionResult Edit(int id) { //states var states = _stateProvinceService.GetStateProvincesByCountryId(model.CountryId).ToList(); - if (states.Count > 0) - { - foreach (var s in states) - model.AvailableStates.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); - } - else - model.AvailableStates.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - + if (states.Count > 0) + { + foreach (var s in states) + { + model.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); + } + } + else + { + model.AvailableStates.Add(new SelectListItem { Text = T("Admin.Address.OtherNonUS"), Value = "0" }); + } } } @@ -662,19 +697,21 @@ public ActionResult Edit(int id) .GetAllCustomerRoles(true) .Select(cr => cr.ToModel()) .ToList(); + model.SelectedCustomerRoleIds = customer.CustomerRoles.Select(cr => cr.Id).ToArray(); model.AllowManagingCustomerRoles = _permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles); + //reward points gistory model.DisplayRewardPointsHistory = _rewardPointsSettings.Enabled; model.AddRewardPointsValue = 0; - model.AddRewardPointsMessage = "Some comment here..."; + //external authentication records model.AssociatedExternalAuthRecords = GetAssociatedExternalAuthRecords(customer); return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [FormValueRequired("save", "save-continue")] [ValidateInput(false)] public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollection form) @@ -684,19 +721,26 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti var customer = _customerService.GetCustomerById(model.Id); if (customer == null || customer.Deleted) - //No customer found with the specified id return RedirectToAction("List"); //validate customer roles - var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - var newCustomerRoles = new List(); - foreach (var customerRole in allCustomerRoles) - if (model.SelectedCustomerRoleIds != null && model.SelectedCustomerRoleIds.Contains(customerRole.Id)) - newCustomerRoles.Add(customerRole); - var customerRolesError = ValidateCustomerRoles(newCustomerRoles); - if (!String.IsNullOrEmpty(customerRolesError)) - ModelState.AddModelError("", customerRolesError); - bool allowManagingCustomerRoles = _permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles); + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); + var allowManagingCustomerRoles = _permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles); + + if (allowManagingCustomerRoles) + { + var newCustomerRoles = new List(); + + foreach (var customerRole in allCustomerRoles) + { + if (model.SelectedCustomerRoleIds != null && model.SelectedCustomerRoleIds.Contains(customerRole.Id)) + newCustomerRoles.Add(customerRole); + } + + var customerRolesError = ValidateCustomerRoles(newCustomerRoles); + if (customerRolesError.HasValue()) + ModelState.AddModelError("", customerRolesError); + } if (ModelState.IsValid) { @@ -705,6 +749,7 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti customer.AdminComment = model.AdminComment; customer.IsTaxExempt = model.IsTaxExempt; customer.Active = model.Active; + //email if (!String.IsNullOrWhiteSpace(model.Email)) { @@ -759,8 +804,10 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.TimeZoneId, model.TimeZoneId); if (_customerSettings.GenderEnabled) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, model.FirstName); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.LastName, model.LastName); + if (_customerSettings.DateOfBirthEnabled) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.DateOfBirth, model.DateOfBirth); if (_customerSettings.CompanyEnabled) @@ -782,6 +829,21 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti if (_customerSettings.FaxEnabled) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Fax, model.Fax); + //customer number + if (_customerSettings.CustomerNumberMethod != CustomerNumberMethod.Disabled) + { + var customerNumbers = _genericAttributeService.GetAttributes(SystemCustomerAttributeNames.CustomerNumber, "customer"); + var currentCustomerNumber = customer.GetAttribute(SystemCustomerAttributeNames.CustomerNumber); + + if (model.CustomerNumber != currentCustomerNumber && customerNumbers.Where(x => x.Value == model.CustomerNumber).Any()) + { + this.NotifyError("Common.CustomerNumberAlreadyExists"); + } + else + { + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CustomerNumber, model.CustomerNumber); + } + } //customer roles if (allowManagingCustomerRoles) @@ -810,6 +872,7 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti _customerActivityService.InsertActivity("EditCustomer", _localizationService.GetResource("ActivityLog.EditCustomer"), customer.Id); NotifySuccess(_localizationService.GetResource("Admin.Customers.Customers.Updated")); + return continueEditing ? RedirectToAction("Edit", customer.Id) : RedirectToAction("List"); } catch (Exception exc) @@ -823,18 +886,25 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti model.UsernamesEnabled = _customerSettings.UsernamesEnabled; model.AllowUsersToChangeUsernames = _customerSettings.AllowUsersToChangeUsernames; model.AllowCustomersToSetTimeZone = _dateTimeSettings.AllowCustomersToSetTimeZone; - foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) - model.AvailableTimeZones.Add(new SelectListItem() { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == model.TimeZoneId) }); + + foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) + { + model.AvailableTimeZones.Add(new SelectListItem { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == model.TimeZoneId) }); + } + model.DisplayVatNumber = _taxSettings.EuVatEnabled; model.VatNumberStatusNote = ((VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId)) .GetLocalizedEnum(_localizationService, _workContext); + model.CreatedOn = _dateTimeHelper.ConvertToUserTime(customer.CreatedOnUtc, DateTimeKind.Utc); model.LastActivityDate = _dateTimeHelper.ConvertToUserTime(customer.LastActivityDateUtc, DateTimeKind.Utc); model.LastIpAddress = model.LastIpAddress; model.LastVisitedPage = customer.GetAttribute(SystemCustomerAttributeNames.LastVisitedPage); + //form fields model.GenderEnabled = _customerSettings.GenderEnabled; model.DateOfBirthEnabled = _customerSettings.DateOfBirthEnabled; + model.CustomerNumberEnabled = _customerSettings.CustomerNumberMethod != CustomerNumberMethod.Disabled; model.CompanyEnabled = _customerSettings.CompanyEnabled; model.StreetAddressEnabled = _customerSettings.StreetAddressEnabled; model.StreetAddress2Enabled = _customerSettings.StreetAddress2Enabled; @@ -844,13 +914,14 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti model.StateProvinceEnabled = _customerSettings.StateProvinceEnabled; model.PhoneEnabled = _customerSettings.PhoneEnabled; model.FaxEnabled = _customerSettings.FaxEnabled; + //countries and states if (_customerSettings.CountryEnabled) { - model.AvailableCountries.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + model.AvailableCountries.Add(new SelectListItem { Text = T("Admin.Address.SelectCountry"), Value = "0" }); foreach (var c in _countryService.GetAllCountries()) { - model.AvailableCountries.Add(new SelectListItem() + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), @@ -862,28 +933,35 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti { //states var states = _stateProvinceService.GetStateProvincesByCountryId(model.CountryId).ToList(); - if (states.Count > 0) - { - foreach (var s in states) - model.AvailableStates.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); - } - else - model.AvailableStates.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - + if (states.Count > 0) + { + foreach (var s in states) + { + model.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); + } + } + else + { + model.AvailableStates.Add(new SelectListItem { Text = T("Admin.Address.OtherNonUS"), Value = "0" }); + } } } + //customer roles model.AvailableCustomerRoles = _customerService .GetAllCustomerRoles(true) .Select(cr => cr.ToModel()) .ToList(); + model.AllowManagingCustomerRoles = allowManagingCustomerRoles; - //reward points gistory + + //reward points gistory model.DisplayRewardPointsHistory = _rewardPointsSettings.Enabled; model.AddRewardPointsValue = 0; - model.AddRewardPointsMessage = "Some comment here..."; - //external authentication records + + //external authentication records model.AssociatedExternalAuthRecords = GetAssociatedExternalAuthRecords(customer); + return View(model); } @@ -896,19 +974,22 @@ public ActionResult ChangePassword(CustomerModel model) var customer = _customerService.GetCustomerById(model.Id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) { - var changePassRequest = new ChangePasswordRequest(model.Email, - false, _customerSettings.DefaultPasswordFormat, model.Password); + var changePassRequest = new ChangePasswordRequest(model.Email, false, _customerSettings.DefaultPasswordFormat, model.Password); var changePassResult = _customerRegistrationService.ChangePassword(changePassRequest); - if (changePassResult.Success) - NotifySuccess(_localizationService.GetResource("Admin.Customers.Customers.PasswordChanged")); - else - foreach (var error in changePassResult.Errors) + + if (changePassResult.Success) + { + NotifySuccess(_localizationService.GetResource("Admin.Customers.Customers.PasswordChanged")); + } + else + { + foreach (var error in changePassResult.Errors) NotifyError(error); + } } return RedirectToAction("Edit", customer.Id); @@ -923,7 +1004,6 @@ public ActionResult MarkVatNumberAsValid(CustomerModel model) var customer = _customerService.GetCustomerById(model.Id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); _genericAttributeService.SaveAttribute(customer, @@ -942,7 +1022,6 @@ public ActionResult MarkVatNumberAsInvalid(CustomerModel model) var customer = _customerService.GetCustomerById(model.Id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); _genericAttributeService.SaveAttribute(customer, @@ -960,30 +1039,33 @@ public ActionResult Delete(int id) var customer = _customerService.GetCustomerById(id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); try { _customerService.DeleteCustomer(customer); - //remove newsletter subscriptions (if exists) - var subscriptions = _newsLetterSubscriptionService.GetAllNewsLetterSubscriptions(customer.Email, 0, int.MaxValue, true); - - foreach (var subscription in subscriptions) + if (customer.Email.HasValue()) { - _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); + foreach (var store in _storeService.GetAllStores()) + { + var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(customer.Email, store.Id); + if (subscription != null) + { + _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); + } + } } - //activity log - _customerActivityService.InsertActivity("DeleteCustomer", _localizationService.GetResource("ActivityLog.DeleteCustomer"), customer.Id); + _customerActivityService.InsertActivity("DeleteCustomer", T("ActivityLog.DeleteCustomer", customer.Id)); + + NotifySuccess(T("Admin.Customers.Customers.Deleted")); - NotifySuccess(_localizationService.GetResource("Admin.Customers.Customers.Deleted")); return RedirectToAction("List"); } - catch (Exception exc) + catch (Exception exception) { - NotifyError(exc.Message); + NotifyError(exception.Message); return RedirectToAction("Edit", new { id = customer.Id }); } } @@ -997,11 +1079,10 @@ public ActionResult Impersonate(int id) var customer = _customerService.GetCustomerById(id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); - //ensure that a non-admin user cannot impersonate as an administrator - //otherwise, that user can simply impersonate as an administrator and gain additional administrative privileges + // ensure that a non-admin user cannot impersonate as an administrator + // otherwise, that user can simply impersonate as an administrator and gain additional administrative privileges if (!_workContext.CurrentCustomer.IsAdmin() && customer.IsAdmin()) { NotifyError("A non-admin user cannot impersonate as an administrator"); @@ -1021,7 +1102,6 @@ public ActionResult SendEmail(CustomerModel model) var customer = _customerService.GetCustomerById(model.Id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); try @@ -1035,13 +1115,11 @@ public ActionResult SendEmail(CustomerModel model) if (String.IsNullOrWhiteSpace(model.SendEmail.Body)) throw new SmartException("Email body is empty"); - var emailAccount = _emailAccountService.GetEmailAccountById(_emailAccountSettings.DefaultEmailAccountId); - if (emailAccount == null) - emailAccount = _emailAccountService.GetAllEmailAccounts().FirstOrDefault(); + var emailAccount = _emailAccountService.GetDefaultEmailAccount(); if (emailAccount == null) - throw new SmartException("Email account can't be loaded"); + throw new SmartException(T("Common.Error.NoEmailAccount")); - var email = new QueuedEmail() + var email = new QueuedEmail { EmailAccountId = emailAccount.Id, FromName = emailAccount.DisplayName, @@ -1052,8 +1130,10 @@ public ActionResult SendEmail(CustomerModel model) Body = model.SendEmail.Body, CreatedOnUtc = DateTime.UtcNow, }; - _queuedEmailService.InsertQueuedEmail(email); - NotifySuccess(_localizationService.GetResource("Admin.Customers.Customers.SendEmail.Queued")); + + _queuedEmailService.InsertQueuedEmail(email); + + NotifySuccess(T("Admin.Customers.Customers.SendEmail.Queued")); } catch (Exception exc) { @@ -1070,19 +1150,18 @@ public ActionResult SendPm(CustomerModel model) var customer = _customerService.GetCustomerById(model.Id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); try { if (!_forumSettings.AllowPrivateMessages) - throw new SmartException("Private messages are disabled"); + throw new SmartException(T("PrivateMessages.Disabled")); if (customer.IsGuest()) - throw new SmartException("Customer should be registered"); + throw new SmartException(T("Common.MethodNotSupportedForGuests")); if (String.IsNullOrWhiteSpace(model.SendPm.Subject)) - throw new SmartException("PM subject is empty"); + throw new SmartException(T("Admin.Customers.Customers.SendPM.Subject.Hint")); if (String.IsNullOrWhiteSpace(model.SendPm.Message)) - throw new SmartException("PM message is empty"); + throw new SmartException(T("Admin.Customers.Customers.SendPM.Message.Hint")); var privateMessage = new PrivateMessage @@ -1234,7 +1313,6 @@ public ActionResult AddressCreate(int customerId) var customer = _customerService.GetCustomerById(customerId); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); var model = new CustomerAddressModel(); @@ -1279,7 +1357,6 @@ public ActionResult AddressCreate(CustomerAddressModel model) var customer = _customerService.GetCustomerById(model.CustomerId); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -1328,12 +1405,10 @@ public ActionResult AddressEdit(int addressId, int customerId) var customer = _customerService.GetCustomerById(customerId); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); var address = _addressService.GetAddressById(addressId); if (address == null) - //No address found with the specified id return RedirectToAction("Edit", new { id = customer.Id }); var model = new CustomerAddressModel(); @@ -1386,12 +1461,10 @@ public ActionResult AddressEdit(CustomerAddressModel model) var customer = _customerService.GetCustomerById(model.CustomerId); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); var address = _addressService.GetAddressById(model.Address.Id); if (address == null) - //No address found with the specified id return RedirectToAction("Edit", new { id = customer.Id }); if (ModelState.IsValid) @@ -1452,17 +1525,15 @@ public ActionResult AddressEdit(CustomerAddressModel model) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult OrderList(int customerId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); + var model = new GridModel(); - var orders = _orderService.SearchOrders(0, customerId, - null, null, null, null, null, null, null, null, 0, int.MaxValue); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + { + var orders = _orderService.SearchOrders(0, customerId, null, null, null, null, null, null, null, null, 0, int.MaxValue); - var model = new GridModel - { - Data = orders.PagedForCommand(command) - .Select(order => - { + model.Data = orders.PagedForCommand(command) + .Select(order => + { var store = _storeService.GetStoreById(order.StoreId); var orderModel = new CustomerModel.OrderModel() { @@ -1471,13 +1542,20 @@ public ActionResult OrderList(int customerId, GridCommand command) PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false), - StoreName = store != null ? store.Name : "Unknown", + StoreName = store != null ? store.Name : "".NaIfEmpty(), CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc), }; - return orderModel; - }), - Total = orders.Count - }; + return orderModel; + }); + + model.Total = orders.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1495,23 +1573,18 @@ public ActionResult Reports() return AccessDeniedView(); var model = new CustomerReportsModel(); + //customers by number of orders model.BestCustomersByNumberOfOrders = new BestCustomersReportModel(); model.BestCustomersByNumberOfOrders.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); - model.BestCustomersByNumberOfOrders.AvailableOrderStatuses.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); model.BestCustomersByNumberOfOrders.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.BestCustomersByNumberOfOrders.AvailablePaymentStatuses.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); model.BestCustomersByNumberOfOrders.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); - model.BestCustomersByNumberOfOrders.AvailableShippingStatuses.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); //customers by order total model.BestCustomersByOrderTotal = new BestCustomersReportModel(); model.BestCustomersByOrderTotal.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); - model.BestCustomersByOrderTotal.AvailableOrderStatuses.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); model.BestCustomersByOrderTotal.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.BestCustomersByOrderTotal.AvailablePaymentStatuses.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); model.BestCustomersByOrderTotal.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); - model.BestCustomersByOrderTotal.AvailableShippingStatuses.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); return View(model); } @@ -1529,9 +1602,8 @@ public ActionResult ReportBestCustomersByOrderTotalList(GridCommand command, Bes PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; ShippingStatus? shippingStatus = model.ShippingStatusId > 0 ? (ShippingStatus?)(model.ShippingStatusId) : null; + var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, orderStatus, paymentStatus, shippingStatus, 1); - var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, - orderStatus, paymentStatus, shippingStatus, 1); var gridModel = new GridModel { Data = items.Select(x => @@ -1542,23 +1614,25 @@ public ActionResult ReportBestCustomersByOrderTotalList(GridCommand command, Bes OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), OrderCount = x.OrderCount, }; + var customer = _customerService.GetCustomerById(x.CustomerId); if (customer != null) { - m.CustomerName = customer.IsGuest() - ? _localizationService.GetResource("Admin.Customers.Guest") - : customer.Email; + m.CustomerName = customer.IsGuest() ? T("Admin.Customers.Guest").Text : customer.Email; } + return m; }), Total = items.Count }; + return new JsonResult { Data = gridModel }; } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult ReportBestCustomersByNumberOfOrdersList(GridCommand command, BestCustomersReportModel model) { DateTime? startDateValue = (model.StartDate == null) ? null @@ -1571,26 +1645,25 @@ public ActionResult ReportBestCustomersByNumberOfOrdersList(GridCommand command, PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; ShippingStatus? shippingStatus = model.ShippingStatusId > 0 ? (ShippingStatus?)(model.ShippingStatusId) : null; + var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, orderStatus, paymentStatus, shippingStatus, 2); - var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, - orderStatus, paymentStatus, shippingStatus, 2); var gridModel = new GridModel { Data = items.Select(x => { - var m = new BestCustomerReportLineModel() + var m = new BestCustomerReportLineModel { CustomerId = x.CustomerId, OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), OrderCount = x.OrderCount, }; + var customer = _customerService.GetCustomerById(x.CustomerId); if (customer != null) { - m.CustomerName = customer.IsGuest() - ? _localizationService.GetResource("Admin.Customers.Guest") - : customer.Email; + m.CustomerName = customer.IsGuest() ? T("Admin.Customers.Guest").Text : customer.Email; } + return m; }), Total = items.Count @@ -1607,15 +1680,18 @@ public ActionResult ReportRegisteredCustomers() var model = GetReportRegisteredCustomersModel(); return PartialView(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult ReportRegisteredCustomersList(GridCommand command) { var model = GetReportRegisteredCustomersModel(); - var gridModel = new GridModel + + var gridModel = new GridModel { Data = model, Total = model.Count }; + return new JsonResult { Data = gridModel @@ -1632,16 +1708,17 @@ public ActionResult GetCartList(int customerId, int cartTypeId) var customer = _customerService.GetCustomerById(customerId); var cart = customer.GetCartItems((ShoppingCartType)cartTypeId); - var gridModel = new GridModel() + var gridModel = new GridModel { Data = cart.Select(sci => { decimal taxRate; var store = _storeService.GetStoreById(sci.Item.StoreId); - var sciModel = new ShoppingCartItemModel() + + var sciModel = new ShoppingCartItemModel { Id = sci.Item.Id, - Store = store != null ? store.Name : "Unknown", + Store = store != null ? store.Name : "".NaIfEmpty(), ProductId = sci.Item.ProductId, Quantity = sci.Item.Quantity, ProductName = sci.Item.Product.Name, @@ -1655,6 +1732,7 @@ public ActionResult GetCartList(int customerId, int cartTypeId) }), Total = cart.Count }; + return new JsonResult { Data = gridModel @@ -1669,11 +1747,12 @@ public ActionResult GetCartList(int customerId, int cartTypeId) public JsonResult ListActivityLog(GridCommand command, int customerId) { var activityLog = _customerActivityService.GetAllActivities(null, null, customerId, 0, command.Page - 1, command.PageSize); + var gridModel = new GridModel { Data = activityLog.Select(x => { - var m = new CustomerModel.ActivityLogModel() + var m = new CustomerModel.ActivityLogModel { Id = x.Id, ActivityLogTypeName = x.ActivityLogType.Name, @@ -1681,107 +1760,13 @@ public JsonResult ListActivityLog(GridCommand command, int customerId) CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) }; return m; - }), Total = activityLog.TotalCount }; - return new JsonResult { Data = gridModel }; ; - } - #endregion - - #region Export / Import - - public ActionResult ExportExcelAll() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - try - { - var customers = _customerService.GetAllCustomers(null, null, null, null, - null, null, null, 0, 0, null, null, null, - false, null, 0, int.MaxValue); - - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportCustomersToXlsx(stream, customers); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "customers.xlsx"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } - } - - public ActionResult ExportExcelSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customers = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - customers.AddRange(_customerService.GetCustomersByIds(ids)); - } - - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportCustomersToXlsx(stream, customers); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "customers.xlsx"); - } - - public ActionResult ExportXmlAll() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - try - { - var customers = _customerService.GetAllCustomers(null, null, null, null, - null, null, null, 0, 0, null, null, null, - false, null, 0, int.MaxValue); - - var xml = _exportManager.ExportCustomersToXml(customers); - return new XmlDownloadResult(xml, "customers.xml"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } + return new JsonResult { Data = gridModel }; } - public ActionResult ExportXmlSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customers = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - customers.AddRange(_customerService.GetCustomersByIds(ids)); - } - - var xml = _exportManager.ExportCustomersToXml(customers); - return new XmlDownloadResult(xml, "customers.xml"); - } - - #endregion + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerRoleController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerRoleController.cs index 270d77a83f..6bb2d10b47 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerRoleController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerRoleController.cs @@ -12,6 +12,8 @@ using SmartStore.Web.Framework; using Telerik.Web.Mvc; using System.Collections.Generic; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; namespace SmartStore.Admin.Controllers { @@ -38,7 +40,6 @@ public CustomerRoleController(ICustomerService customerService, this._localizationService = localizationService; this._customerActivityService = customerActivityService; this._permissionService = permissionService; - //codehint: sm-add this._taxSettings = taxSettings; } @@ -101,18 +102,25 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles)) - return AccessDeniedView(); - - var customerRoles = _customerService.GetAllCustomerRoles(true); - var gridModel = new GridModel + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles)) { - Data = customerRoles.Select(x => x.ToModel()), - Total = customerRoles.Count() - }; + var customerRoles = _customerService.GetAllCustomerRoles(true); + + model.Data = customerRoles.Select(x => x.ToModel()); + model.Total = customerRoles.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { - Data = gridModel + Data = model }; } @@ -128,7 +136,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(CustomerRoleModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles)) @@ -166,7 +174,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(CustomerRoleModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles)) @@ -198,7 +206,7 @@ public ActionResult Edit(CustomerRoleModel model, bool continueEditing) return continueEditing ? RedirectToAction("Edit", customerRole.Id) : RedirectToAction("List"); } - //If we got this far, something failed, redisplay form + // If we got this far, something failed, redisplay form return View(model); } catch (Exception exc) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/DeliveryTimeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/DeliveryTimeController.cs index b478a97c46..da109ed57d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/DeliveryTimeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/DeliveryTimeController.cs @@ -11,6 +11,8 @@ using SmartStore.Services.Localization; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -124,7 +126,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(DeliveryTimeModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageDeliveryTimes)) @@ -165,7 +167,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(DeliveryTimeModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageDeliveryTimes)) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/DiscountController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/DiscountController.cs index 799f4a7a51..c9ab6e4496 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/DiscountController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/DiscountController.cs @@ -2,22 +2,18 @@ using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Discounts; -using SmartStore.Core.Plugins; -using SmartStore.Core; -using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Discounts; +using SmartStore.Core.Logging; +using SmartStore.Services; using SmartStore.Services.Catalog; -using SmartStore.Services.Directory; using SmartStore.Services.Discounts; using SmartStore.Services.Helpers; -using SmartStore.Services.Localization; -using SmartStore.Core.Logging; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; -using Telerik.Web.Mvc; -using SmartStore.Core.ComponentModel; -using System.Collections.Generic; +using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; +using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { @@ -27,40 +23,33 @@ public class DiscountController : AdminControllerBase #region Fields private readonly IDiscountService _discountService; - private readonly ILocalizationService _localizationService; - private readonly IWebHelper _webHelper; private readonly IDateTimeHelper _dateTimeHelper; private readonly ICustomerActivityService _customerActivityService; - private readonly ICurrencyService _currencyService; private readonly ICategoryService _categoryService; private readonly IProductService _productService; - private readonly CurrencySettings _currencySettings; - private readonly IPermissionService _permissionService; private readonly PluginMediator _pluginMediator; + private readonly ICommonServices _services; #endregion #region Constructors - public DiscountController(IDiscountService discountService, - ILocalizationService localizationService, ICurrencyService currencyService, - ICategoryService categoryService, IProductService productService, - IWebHelper webHelper, IDateTimeHelper dateTimeHelper, - ICustomerActivityService customerActivityService, CurrencySettings currencySettings, - IPermissionService permissionService, - PluginMediator pluginMediator) + public DiscountController( + IDiscountService discountService, + ICategoryService categoryService, + IProductService productService, + IDateTimeHelper dateTimeHelper, + ICustomerActivityService customerActivityService, + PluginMediator pluginMediator, + ICommonServices services) { this._discountService = discountService; - this._localizationService = localizationService; - this._currencyService = currencyService; this._categoryService = categoryService; this._productService = productService; - this._webHelper = webHelper; this._dateTimeHelper = dateTimeHelper; this._customerActivityService = customerActivityService; - this._currencySettings = currencySettings; - this._permissionService = permissionService; this._pluginMediator = pluginMediator; + this._services = services; } #endregion @@ -76,7 +65,7 @@ public string GetRequirementUrlInternal(IDiscountRequirementRule discountRequire if (discount == null) throw new ArgumentNullException("discount"); - string url = string.Format("{0}{1}", _webHelper.GetStoreLocation(), discountRequirementRule.GetConfigurationUrl(discount.Id, discountRequirementId)); + string url = string.Format("{0}{1}", _services.WebHelper.GetStoreLocation(), discountRequirementRule.GetConfigurationUrl(discount.Id, discountRequirementId)); return url; } @@ -86,9 +75,10 @@ private void PrepareDiscountModel(DiscountModel model, Discount discount) if (model == null) throw new ArgumentNullException("model"); - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; - model.AvailableDiscountRequirementRules.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Promotions.Discounts.Requirements.DiscountRequirementType.Select"), Value = "" }); - var discountRules = _discountService.LoadAllDiscountRequirementRules(); + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; + model.AvailableDiscountRequirementRules.Add(new SelectListItem() { Text = _services.Localization.GetResource("Admin.Promotions.Discounts.Requirements.DiscountRequirementType.Select"), Value = "" }); + + var discountRules = _discountService.LoadAllDiscountRequirementRules(); foreach (var discountRule in discountRules) { model.AvailableDiscountRequirementRules.Add(new SelectListItem() @@ -148,7 +138,6 @@ private void PrepareDiscountModel(DiscountModel model, Discount discount) #region Discounts - //list public ActionResult Index() { return RedirectToAction("List"); @@ -156,7 +145,7 @@ public ActionResult Index() public ActionResult List() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) return AccessDeniedView(); var discounts = _discountService.GetAllDiscounts(null, null, true); @@ -171,25 +160,31 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) + { + var discounts = _discountService.GetAllDiscounts(null, null, true); + + model.Data = discounts.Select(x => x.ToModel()); + model.Total = discounts.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var discounts = _discountService.GetAllDiscounts(null, null, true); - var gridModel = new GridModel - { - Data = discounts.Select(x => x.ToModel()), - Total = discounts.Count() - }; return new JsonResult { - Data = gridModel + Data = model }; } - //create public ActionResult Create() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) return AccessDeniedView(); var model = new DiscountModel(); @@ -199,10 +194,10 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(DiscountModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) return AccessDeniedView(); if (ModelState.IsValid) @@ -211,9 +206,9 @@ public ActionResult Create(DiscountModel model, bool continueEditing) _discountService.InsertDiscount(discount); //activity log - _customerActivityService.InsertActivity("AddNewDiscount", _localizationService.GetResource("ActivityLog.AddNewDiscount"), discount.Name); + _customerActivityService.InsertActivity("AddNewDiscount", _services.Localization.GetResource("ActivityLog.AddNewDiscount"), discount.Name); - NotifySuccess(_localizationService.GetResource("Admin.Promotions.Discounts.Added")); + NotifySuccess(_services.Localization.GetResource("Admin.Promotions.Discounts.Added")); return continueEditing ? RedirectToAction("Edit", new { id = discount.Id }) : RedirectToAction("List"); } @@ -222,10 +217,9 @@ public ActionResult Create(DiscountModel model, bool continueEditing) return View(model); } - //edit public ActionResult Edit(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) return AccessDeniedView(); var discount = _discountService.GetDiscountById(id); @@ -238,10 +232,10 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(DiscountModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) return AccessDeniedView(); var discount = _discountService.GetDiscountById(model.Id); @@ -280,9 +274,9 @@ public ActionResult Edit(DiscountModel model, bool continueEditing) } //activity log - _customerActivityService.InsertActivity("EditDiscount", _localizationService.GetResource("ActivityLog.EditDiscount"), discount.Name); + _customerActivityService.InsertActivity("EditDiscount", _services.Localization.GetResource("ActivityLog.EditDiscount"), discount.Name); - NotifySuccess(_localizationService.GetResource("Admin.Promotions.Discounts.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.Promotions.Discounts.Updated")); return continueEditing ? RedirectToAction("Edit", discount.Id) : RedirectToAction("List"); } @@ -291,11 +285,10 @@ public ActionResult Edit(DiscountModel model, bool continueEditing) return View(model); } - //delete [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) return AccessDeniedView(); var discount = _discountService.GetDiscountById(id); @@ -317,9 +310,9 @@ public ActionResult DeleteConfirmed(int id) _productService.UpdateHasDiscountsApplied(product); //activity log - _customerActivityService.InsertActivity("DeleteDiscount", _localizationService.GetResource("ActivityLog.DeleteDiscount"), discount.Name); + _customerActivityService.InsertActivity("DeleteDiscount", _services.Localization.GetResource("ActivityLog.DeleteDiscount"), discount.Name); - NotifySuccess(_localizationService.GetResource("Admin.Promotions.Discounts.Deleted")); + NotifySuccess(_services.Localization.GetResource("Admin.Promotions.Discounts.Deleted")); return RedirectToAction("List"); } @@ -330,7 +323,7 @@ public ActionResult DeleteConfirmed(int id) [AcceptVerbs(HttpVerbs.Get)] public ActionResult GetDiscountRequirementConfigurationUrl(string systemName, int discountId, int? discountRequirementId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) return AccessDeniedView(); if (String.IsNullOrEmpty(systemName)) @@ -350,7 +343,7 @@ public ActionResult GetDiscountRequirementConfigurationUrl(string systemName, in public ActionResult GetDiscountRequirementMetaInfo(int discountRequirementId, int discountId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) return AccessDeniedView(); var discount = _discountService.GetDiscountById(discountId); @@ -373,7 +366,7 @@ public ActionResult GetDiscountRequirementMetaInfo(int discountRequirementId, in public ActionResult DeleteDiscountRequirement(int discountRequirementId, int discountId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) return AccessDeniedView(); var discount = _discountService.GetDiscountById(discountId); @@ -396,28 +389,31 @@ public ActionResult DeleteDiscountRequirement(int discountRequirementId, int dis [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult UsageHistoryList(int discountId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) - return AccessDeniedView(); + var model = new GridModel(); - var discount = _discountService.GetDiscountById(discountId); - if (discount == null) - throw new ArgumentException("No discount found with the specified id"); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) + { + var discount = _discountService.GetDiscountById(discountId); + + var discountHistories = _discountService.GetAllDiscountUsageHistory(discount.Id, null, command.Page - 1, command.PageSize); + + model.Data = discountHistories.Select(x => new DiscountModel.DiscountUsageHistoryModel + { + Id = x.Id, + DiscountId = x.DiscountId, + OrderId = x.OrderId, + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) + }); + + model.Total = discountHistories.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var duh = _discountService.GetAllDiscountUsageHistory(discount.Id, null, command.Page - 1, command.PageSize); - var model = new GridModel - { - Data = duh.Select(x => - { - return new DiscountModel.DiscountUsageHistoryModel() - { - Id = x.Id, - DiscountId = x.DiscountId, - OrderId = x.OrderId, - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) - }; - }), - Total = duh.TotalCount - }; return new JsonResult { Data = model @@ -427,16 +423,13 @@ public ActionResult UsageHistoryList(int discountId, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult UsageHistoryDelete(int discountId, int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageDiscounts)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) + { + var discountHistory = _discountService.GetDiscountUsageHistoryById(id); + + _discountService.DeleteDiscountUsageHistory(discountHistory); + } - var discount = _discountService.GetDiscountById(discountId); - if (discount == null) - throw new ArgumentException("No discount found with the specified id"); - - var duh = _discountService.GetDiscountUsageHistoryById(id); - if (duh != null) - _discountService.DeleteDiscountUsageHistory(duh); return UsageHistoryList(discountId, command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/DownloadController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/DownloadController.cs index b152f65b46..c215d9b7e9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/DownloadController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/DownloadController.cs @@ -5,13 +5,16 @@ using SmartStore.Core.Domain.Media; using SmartStore.Services.Media; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; namespace SmartStore.Admin.Controllers { [AdminAuthorize] public class DownloadController : AdminControllerBase { - private readonly IDownloadService _downloadService; + const string TEMPLATE = "EditorTemplates/Download"; + + private readonly IDownloadService _downloadService; public DownloadController(IDownloadService downloadService) { @@ -43,77 +46,70 @@ public ActionResult DownloadFile(int downloadId) [HttpPost] [ValidateInput(false)] - public ActionResult SaveDownloadUrl(string downloadUrl) + public ActionResult SaveDownloadUrl(string downloadUrl, bool minimalMode = false, string fieldName = null) { - //insert - var download = new Download() - { - DownloadGuid = Guid.NewGuid(), - UseDownloadUrl = true, - DownloadUrl = downloadUrl, - IsNew = true - }; + var download = new Download + { + DownloadGuid = Guid.NewGuid(), + UseDownloadUrl = true, + DownloadUrl = downloadUrl, + IsNew = true, + IsTransient = true + }; + _downloadService.InsertDownload(download); - return Json(new { downloadId = download.Id }, JsonRequestBehavior.AllowGet); + return Json(new + { + success = true, + downloadId = download.Id, + html = this.RenderPartialViewToString(TEMPLATE, download.Id, new { minimalMode = minimalMode, fieldName = fieldName }) + }, JsonRequestBehavior.AllowGet); } [HttpPost] - public ActionResult AsyncUpload() + public ActionResult AsyncUpload(bool minimalMode = false, string fieldName = null) { - //we process it distinct ways based on a browser - //find more info here http://stackoverflow.com/questions/4884920/mvc3-valums-ajax-file-upload - Stream stream = null; - var fileName = ""; - var contentType = ""; - if (String.IsNullOrEmpty(Request["qqfile"])) - { - // IE - HttpPostedFileBase httpPostedFile = Request.Files[0]; - if (httpPostedFile == null) - throw new ArgumentException("No file uploaded"); - stream = httpPostedFile.InputStream; - fileName = Path.GetFileName(httpPostedFile.FileName); - contentType = httpPostedFile.ContentType; - } - else - { - //Webkit, Mozilla - stream = Request.InputStream; - fileName = Request["qqfile"]; - } - - var fileBinary = new byte[stream.Length]; - stream.Read(fileBinary, 0, fileBinary.Length); + var postedFile = Request.ToPostedFileResult(); + if (postedFile == null) + { + throw new ArgumentException(T("Common.NoFileUploaded")); + } - var fileExtension = Path.GetExtension(fileName); - if (!String.IsNullOrEmpty(fileExtension)) - fileExtension = fileExtension.ToLowerInvariant(); - - var download = new Download() + var download = new Download { DownloadGuid = Guid.NewGuid(), UseDownloadUrl = false, DownloadUrl = "", - DownloadBinary = fileBinary, - ContentType = contentType, - //we store filename without extension for downloads - Filename = Path.GetFileNameWithoutExtension(fileName), - Extension = fileExtension, - IsNew = true + DownloadBinary = postedFile.Buffer, + ContentType = postedFile.ContentType, + // we store filename without extension for downloads + Filename = postedFile.FileTitle, + Extension = postedFile.FileExtension, + IsNew = true, + IsTransient = true }; + _downloadService.InsertDownload(download); - //when returning JSON the mime-type must be set to text/plain - //otherwise some browsers will pop-up a "Save As" dialog. return Json(new { success = true, - downloadId = download.Id, - fileName = download.Filename + download.Extension, - downloadUrl = Url.Action("DownloadFile", new { downloadId = download.Id }) - }, - "text/plain"); + downloadId = download.Id, + html = this.RenderPartialViewToString(TEMPLATE, download.Id, new { minimalMode = minimalMode, fieldName = fieldName }) + }); } + + [HttpPost] + public ActionResult DeleteDownload(bool minimalMode = false, string fieldName = null) + { + // We don't actually delete here. We just return the editor in it's init state + // so the download entity can be set to transient state and deleted later by a scheduled task. + return Json(new + { + success = true, + html = this.RenderPartialViewToString(TEMPLATE, null, new { minimalMode = minimalMode, fieldName = fieldName }), + }); + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs index 7426f8e60f..2d6716d284 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs @@ -1,11 +1,8 @@ using System; using System.Linq; -using System.Net.Mail; -using System.Threading.Tasks; using System.Web.Mvc; using SmartStore.Admin.Models.Messages; using SmartStore.Core; -using SmartStore.Core.Domain; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Email; using SmartStore.Services.Configuration; @@ -13,6 +10,8 @@ using SmartStore.Services.Messages; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -76,24 +75,32 @@ public ActionResult List(string id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageEmailAccounts)) - return AccessDeniedView(); + var model = new GridModel(); - var emailAccountModels = _emailAccountService.GetAllEmailAccounts() - .Select(x => x.ToModel()) - .ToList(); - foreach (var eam in emailAccountModels) - eam.IsDefaultEmailAccount = eam.Id == _emailAccountSettings.DefaultEmailAccountId; + if (_permissionService.Authorize(StandardPermissionProvider.ManageEmailAccounts)) + { + var emailAccountModels = _emailAccountService.GetAllEmailAccounts() + .Select(x => x.ToModel()) + .ToList(); - var gridModel = new GridModel - { - Data = emailAccountModels, - Total = emailAccountModels.Count() - }; + foreach (var eam in emailAccountModels) + { + eam.IsDefaultEmailAccount = eam.Id == _emailAccountSettings.DefaultEmailAccountId; + } + + model.Data = emailAccountModels; + model.Total = emailAccountModels.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { - Data = gridModel + Data = model }; } @@ -108,7 +115,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(EmailAccountModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageEmailAccounts)) @@ -140,7 +147,7 @@ public ActionResult Edit(int id) return View(emailAccount.ToModel()); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [FormValueRequired("save", "save-continue")] public ActionResult Edit(EmailAccountModel model, bool continueEditing) { @@ -174,27 +181,32 @@ public ActionResult SendTestEmail(EmailAccountModel model) var emailAccount = _emailAccountService.GetEmailAccountById(model.Id); if (emailAccount == null) - //No email account found with the specified id return RedirectToAction("List"); try { - if (String.IsNullOrWhiteSpace(model.SendTestEmailTo)) - throw new SmartException("Enter test email address"); - + if (model.SendTestEmailTo.IsEmpty()) + { + NotifyError(T("Admin.Common.EnterEmailAdress")); + } + else + { + var to = new EmailAddress(model.SendTestEmailTo); + var from = new EmailAddress(emailAccount.Email, emailAccount.DisplayName); + var subject = string.Concat(_storeContext.CurrentStore.Name, ". ", T("Admin.Configuration.EmailAccounts.TestingEmail")); + var body = T("Admin.Common.EmailSuccessfullySent"); - var to = new EmailAddress(model.SendTestEmailTo); - var from = new EmailAddress(emailAccount.Email, emailAccount.DisplayName); - string subject = _storeContext.CurrentStore.Name + ". Testing email functionality."; - string body = "Email works fine."; + var msg = new EmailMessage(to, subject, body, from); - _emailSender.SendEmail(new SmtpContext(emailAccount), new EmailMessage(to, subject, body, from)); + _emailSender.SendEmail(new SmtpContext(emailAccount), msg); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.EmailAccounts.SendTestEmail.Success"), false); + NotifySuccess(T("Admin.Configuration.EmailAccounts.SendTestEmail.Success"), false); + } } - catch (Exception exc) + catch (Exception exception) { - NotifyError(exc.Message, false); + model.TestEmailShortErrorMessage = exception.ToAllMessages(); + model.TestEmailFullErrorMessage = exception.ToString(); } //If we got this far, something failed, redisplay form diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs new file mode 100644 index 0000000000..95b26a0da0 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -0,0 +1,1413 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Text; +using System.Web.Mvc; +using SmartStore.Admin.Extensions; +using SmartStore.Admin.Models.DataExchange; +using SmartStore.Core; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.IO; +using SmartStore.Core.Plugins; +using SmartStore.Services.Catalog; +using SmartStore.Services.Customers; +using SmartStore.Services.DataExchange.Export; +using SmartStore.Services.DataExchange.Export.Deployment; +using SmartStore.Services.Directory; +using SmartStore.Services.Helpers; +using SmartStore.Services.Localization; +using SmartStore.Services.Messages; +using SmartStore.Services.Security; +using SmartStore.Services.Tasks; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; +using SmartStore.Web.Framework.UI; +using Telerik.Web.Mvc; + +namespace SmartStore.Admin.Controllers +{ + [AdminAuthorize] + public class ExportController : AdminControllerBase + { + private readonly IExportProfileService _exportService; + private readonly PluginMediator _pluginMediator; + private readonly ICategoryService _categoryService; + private readonly IManufacturerService _manufacturerService; + private readonly IProductTagService _productTagService; + private readonly ICustomerService _customerService; + private readonly ILanguageService _languageService; + private readonly ICurrencyService _currencyService; + private readonly IEmailAccountService _emailAccountService; + private readonly ICountryService _countryService; + private readonly IDateTimeHelper _dateTimeHelper; + private readonly DataExchangeSettings _dataExchangeSettings; + private readonly ITaskScheduler _taskScheduler; + private readonly IDataExporter _dataExporter; + private readonly Lazy _customerSettings; + + public ExportController( + IExportProfileService exportService, + PluginMediator pluginMediator, + ICategoryService categoryService, + IManufacturerService manufacturerService, + IProductTagService productTagService, + ICustomerService customerService, + ILanguageService languageService, + ICurrencyService currencyService, + IEmailAccountService emailAccountService, + ICountryService countryService, + IDateTimeHelper dateTimeHelper, + DataExchangeSettings dataExchangeSettings, + ITaskScheduler taskScheduler, + IDataExporter dataExporter, + Lazy customerSettings) + { + _exportService = exportService; + _pluginMediator = pluginMediator; + _categoryService = categoryService; + _manufacturerService = manufacturerService; + _productTagService = productTagService; + _customerService = customerService; + _languageService = languageService; + _currencyService = currencyService; + _emailAccountService = emailAccountService; + _countryService = countryService; + _dateTimeHelper = dateTimeHelper; + _dataExchangeSettings = dataExchangeSettings; + _taskScheduler = taskScheduler; + _dataExporter = dataExporter; + _customerSettings = customerSettings; + } + + #region Utilities + + private string GetThumbnailUrl(Provider provider) + { + string url = null; + + if (provider != null) + url = _pluginMediator.GetIconUrl(provider.Metadata); + + if (url.IsEmpty()) + url = _pluginMediator.GetDefaultIconUrl(null); + + url = Url.Content(url); + + return url; + } + + private void ModelToEntity(ExportDeploymentModel model, ExportDeployment deployment) + { + deployment.ProfileId = model.ProfileId; + deployment.Name = model.Name; + deployment.Enabled = model.Enabled; + deployment.DeploymentType = model.DeploymentType; + deployment.Username = model.Username; + deployment.Password = model.Password; + deployment.Url = model.Url; + deployment.HttpTransmissionType = model.HttpTransmissionType; + deployment.FileSystemPath = model.FileSystemPath; + deployment.SubFolder = model.SubFolder; + deployment.EmailAddresses = model.EmailAddresses; + deployment.EmailSubject = model.EmailSubject; + deployment.EmailAccountId = model.EmailAccountId; + deployment.PassiveMode = model.PassiveMode; + deployment.UseSsl = model.UseSsl; + } + + private ActionResult SmartRedirect(bool continueEditing, int profileId, int deploymentId) + { + if (!continueEditing) + { + TempData["SelectedTab.export-profile-edit"] = new SelectedTabInfo + { + TabId = "export-profile-edit-6", + Path = Url.Action("Edit", new { id = profileId }) + }; + } + + return (continueEditing ? + RedirectToAction("EditDeployment", new { id = deploymentId }) : + RedirectToAction("Edit", new { id = profileId })); + } + + private void AddFileInfo(List list, string path, string publicFolderUrl = null, Store store = null) + { + if (System.IO.File.Exists(path) && !list.Any(x => x.FilePath == path)) + { + var fileInfo = new ExportFileDetailsModel.FileInfo(); + fileInfo.FilePath = path; + fileInfo.FileName = Path.GetFileName(path); + fileInfo.FileExtension = Path.GetExtension(path); + fileInfo.DisplayOrder = (fileInfo.FileExtension.IsCaseInsensitiveEqual(".zip") ? 0 : 1); + + if (store != null) + { + fileInfo.StoreId = store.Id; + fileInfo.StoreName = store.Name; + } + + if (publicFolderUrl.HasValue()) + { + fileInfo.FileUrl = publicFolderUrl + fileInfo.FileName; + } + + list.Add(fileInfo); + } + } + + private ExportFileDetailsModel CreateFileDetailsModel(ExportProfile profile, Provider provider, ExportDeployment deployment) + { + var model = new ExportFileDetailsModel + { + Id = (deployment == null ? profile.Id : deployment.Id), + IsForDeployment = (deployment != null), + ExportFiles = new List(), + PublicFiles = new List() + }; + + try + { + // add export files + var zipPath = profile.GetExportZipPath(); + + if (deployment == null) + { + AddFileInfo(model.ExportFiles, zipPath); + + foreach (var path in profile.GetExportFiles(provider)) + { + AddFileInfo(model.ExportFiles, path); + } + } + else if (deployment.DeploymentType == ExportDeploymentType.FileSystem) + { + var deploymentFolder = deployment.GetDeploymentFolder(); + var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); + + if (resultInfo.Files != null) + { + foreach (var file in resultInfo.Files) + { + AddFileInfo(model.ExportFiles, Path.Combine(deploymentFolder, file.FileName)); + } + } + } + + // add public files + ExportDeployment publicDeployment = null; + + if (deployment == null) + publicDeployment = profile.Deployments.FirstOrDefault(x => x.DeploymentType == ExportDeploymentType.PublicFolder); + else + publicDeployment = (deployment.DeploymentType == ExportDeploymentType.PublicFolder ? deployment : null); + + if (publicDeployment != null) + { + var currentStore = Services.StoreContext.CurrentStore; + var deploymentFolder = publicDeployment.GetDeploymentFolder(); + + // note public folder not cleaned up during export, so only display files that has been created during last export. + // otherwise the merchant might publish URLs of old export files. + if (profile.CreateZipArchive) + { + AddFileInfo( + model.PublicFiles, + Path.Combine(deploymentFolder, Path.GetFileName(zipPath)), + publicDeployment.GetPublicFolderUrl(Services, currentStore)); + } + else + { + var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); + if (resultInfo.Files != null) + { + var allStores = Services.StoreService.GetAllStores(); + + foreach (var file in resultInfo.Files) + { + var store = (file.StoreId == 0 ? null : allStores.FirstOrDefault(x => x.Id == file.StoreId)); + + AddFileInfo( + model.PublicFiles, + Path.Combine(deploymentFolder, file.FileName), + publicDeployment.GetPublicFolderUrl(Services, store ?? currentStore), + store); + } + } + } + } + } + catch (Exception exception) + { + NotifyError(exception); + } + + return model; + } + + private ExportDeploymentModel CreateDeploymentModel(ExportProfile profile, ExportDeployment deployment, Provider provider, bool forEdit) + { + var model = new ExportDeploymentModel + { + Id = deployment.Id, + ProfileId = deployment.ProfileId, + Name = deployment.Name, + Enabled = deployment.Enabled, + DeploymentType = deployment.DeploymentType, + DeploymentTypeName = deployment.DeploymentType.GetLocalizedEnum(Services.Localization, Services.WorkContext), + Username = deployment.Username, + Password = deployment.Password, + Url = deployment.Url, + HttpTransmissionType = deployment.HttpTransmissionType, + FileSystemPath = deployment.FileSystemPath, + SubFolder = deployment.SubFolder, + EmailAddresses = deployment.EmailAddresses, + EmailSubject = deployment.EmailSubject, + EmailAccountId = deployment.EmailAccountId, + PassiveMode = deployment.PassiveMode, + UseSsl = deployment.UseSsl + }; + + model.PublicFolderUrl = deployment.GetPublicFolderUrl(Services); + + if (forEdit) + { + var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); + + model.CreateZip = profile.CreateZipArchive; + model.AvailableDeploymentTypes = ExportDeploymentType.FileSystem.ToSelectList(false).ToList(); + model.AvailableHttpTransmissionTypes = ExportHttpTransmissionType.SimplePost.ToSelectList(false).ToList(); + + model.SerializedEmailAddresses = string.Join(",", deployment.EmailAddresses.SplitSafe(",").Select(x => x.EncodeJsString())); + + model.AvailableEmailAccounts = allEmailAccounts + .Select(x => new SelectListItem { Text = x.FriendlyName, Value = x.Id.ToString() }) + .ToList(); + + if (provider != null) + { + model.ThumbnailUrl = GetThumbnailUrl(provider); + } + } + else + { + var fileDetailsModel = CreateFileDetailsModel(profile, null, deployment); + + model.FileCount = fileDetailsModel.FileCount; + } + + return model; + } + + private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) + { + model.Id = profile.Id; + model.Name = profile.Name; + model.SystemName = profile.SystemName; + model.IsSystemProfile = profile.IsSystemProfile; + model.ProviderSystemName = profile.ProviderSystemName; + model.FolderName = profile.FolderName; + model.FileNamePattern = profile.FileNamePattern; + model.Enabled = profile.Enabled; + model.ScheduleTaskId = profile.SchedulingTaskId; + model.ScheduleTaskName = profile.ScheduleTask.Name.NaIfEmpty(); + model.IsTaskRunning = profile.ScheduleTask.IsRunning; + model.IsTaskEnabled = profile.ScheduleTask.Enabled; + model.LogFileExists = System.IO.File.Exists(profile.GetExportLogPath()); + model.HasActiveProvider = (provider != null); + model.FileNamePatternDescriptions = T("Admin.DataExchange.Export.FileNamePatternDescriptions").Text.SplitSafe(";"); + + model.Provider = new ExportProfileModel.ProviderModel(); + model.Provider.ThumbnailUrl = GetThumbnailUrl(provider); + + var descriptor = provider.Metadata.PluginDescriptor; + + if (descriptor != null) + { + model.Provider.Url = descriptor.Url; + model.Provider.Author = descriptor.Author; + model.Provider.Version = descriptor.Version.ToString(); + } + + model.Provider.FriendlyName = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); + model.Provider.Description = _pluginMediator.GetLocalizedDescription(provider.Metadata); + model.Provider.EntityType = provider.Value.EntityType; + model.Provider.EntityTypeName = provider.Value.EntityType.GetLocalizedEnum(Services.Localization, Services.WorkContext); + model.Provider.FileExtension = provider.Value.FileExtension; + } + + private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile profile, Provider provider) + { + var filter = XmlHelper.Deserialize(profile.Filtering); + var projection = XmlHelper.Deserialize(profile.Projection); + + var language = Services.WorkContext.WorkingLanguage; + var store = Services.StoreContext.CurrentStore; + + var allStores = Services.StoreService.GetAllStores(); + var allLanguages = _languageService.GetAllLanguages(true); + var allCurrencies = _currencyService.GetAllCurrencies(true); + var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); + + model.AllString = T("Admin.Common.All"); + model.UnspecifiedString = T("Common.Unspecified"); + model.StoreCount = allStores.Count; + model.Offset = profile.Offset; + model.Limit = (profile.Limit == 0 ? (int?)null : profile.Limit); + model.BatchSize = (profile.BatchSize == 0 ? (int?)null : profile.BatchSize); + model.PerStore = profile.PerStore; + model.EmailAccountId = profile.EmailAccountId; + model.CompletedEmailAddresses = profile.CompletedEmailAddresses; + model.CreateZipArchive = profile.CreateZipArchive; + model.Cleanup = profile.Cleanup; + + model.FileNamePatternExample = profile.ResolveFileNamePattern(store, 1, _dataExchangeSettings.MaxFileNameLength); + + model.AvailableEmailAccounts = allEmailAccounts + .Select(x => new SelectListItem { Text = x.FriendlyName, Value = x.Id.ToString() }) + .ToList(); + + model.SerializedCompletedEmailAddresses = string.Join(",", profile.CompletedEmailAddresses.SplitSafe(",").Select(x => x.EncodeJsString())); + + // projection + model.Projection = new ExportProjectionModel + { + StoreId = projection.StoreId, + LanguageId = projection.LanguageId, + CurrencyId = projection.CurrencyId, + CustomerId = projection.CustomerId, + NumberOfPictures = projection.NumberOfPictures, + DescriptionMergingId = projection.DescriptionMergingId, + DescriptionToPlainText = projection.DescriptionToPlainText, + AppendDescriptionText = projection.AppendDescriptionText, + RemoveCriticalCharacters = projection.RemoveCriticalCharacters, + CriticalCharacters = projection.CriticalCharacters, + PriceType = projection.PriceType, + ConvertNetToGrossPrices = projection.ConvertNetToGrossPrices, + Brand = projection.Brand, + PictureSize = projection.PictureSize, + ShippingTime = projection.ShippingTime, + ShippingCosts = projection.ShippingCosts, + FreeShippingThreshold = projection.FreeShippingThreshold, + AttributeCombinationAsProduct = projection.AttributeCombinationAsProduct, + AttributeCombinationValueMergingId = projection.AttributeCombinationValueMergingId, + NoGroupedProducts = projection.NoGroupedProducts, + OrderStatusChangeId = projection.OrderStatusChangeId + }; + + model.Projection.AvailableStores = allStores + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); + + model.Projection.AvailableLanguages = allLanguages + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); + + model.Projection.AvailableCurrencies = allCurrencies + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); + + // filtering + model.Filter = new ExportFilterModel + { + StoreId = filter.StoreId, + CreatedFrom = filter.CreatedFrom, + CreatedTo = filter.CreatedTo, + PriceMinimum = filter.PriceMinimum, + PriceMaximum = filter.PriceMaximum, + AvailabilityMinimum = filter.AvailabilityMinimum, + AvailabilityMaximum = filter.AvailabilityMaximum, + IsPublished = filter.IsPublished, + CategoryIds = filter.CategoryIds, + WithoutCategories = filter.WithoutCategories, + ManufacturerId = filter.ManufacturerId, + WithoutManufacturers = filter.WithoutManufacturers, + ProductTagId = filter.ProductTagId, + FeaturedProducts = filter.FeaturedProducts, + IsActiveCustomer = filter.IsActiveCustomer, + IsTaxExempt = filter.IsTaxExempt, + BillingCountryIds = filter.BillingCountryIds, + ShippingCountryIds = filter.ShippingCountryIds, + LastActivityFrom = filter.LastActivityFrom, + LastActivityTo = filter.LastActivityTo, + HasSpentAtLeastAmount = filter.HasSpentAtLeastAmount, + HasPlacedAtLeastOrders = filter.HasPlacedAtLeastOrders, + ProductType = filter.ProductType, + IdMinimum = filter.IdMinimum, + IdMaximum = filter.IdMaximum, + OrderStatusIds = filter.OrderStatusIds, + PaymentStatusIds = filter.PaymentStatusIds, + ShippingStatusIds = filter.ShippingStatusIds, + CustomerRoleIds = filter.CustomerRoleIds, + IsActiveSubscriber = filter.IsActiveSubscriber + }; + + model.Filter.AvailableStores = allStores + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); + + // deployment + model.Deployments = profile.Deployments + .Select(x => + { + var deploymentModel = CreateDeploymentModel(profile, x, null, false); + + if (x.ResultInfo.HasValue()) + { + var resultInfo = XmlHelper.Deserialize(x.ResultInfo); + + deploymentModel.LastResult = new ExportDeploymentModel.LastResultInfo + { + Execution = _dateTimeHelper.ConvertToUserTime(resultInfo.LastExecutionUtc, DateTimeKind.Utc), + ExecutionPretty = resultInfo.LastExecutionUtc.RelativeFormat(true, "f"), + Error = resultInfo.LastError + }; + } + return deploymentModel; + }) + .ToList(); + + + if (provider != null) + { + model.Provider.Feature = provider.Metadata.ExportFeatures; + + if (model.Provider.EntityType == ExportEntityType.Product) + { + var allCategories = _categoryService.GetAllCategories(showHidden: true); + var mappedCategories = allCategories.ToDictionary(x => x.Id); + var allManufacturers = _manufacturerService.GetAllManufacturers(true); + var allProductTags = _productTagService.GetAllProductTags(); + + model.Projection.AvailableAttributeCombinationValueMerging = ExportAttributeValueMerging.AppendAllValuesToName.ToSelectList(false); + + model.Projection.AvailableDescriptionMergings = ExportDescriptionMerging.Description.ToSelectList(false); + + model.Projection.AvailablePriceTypes = PriceDisplayType.LowestPrice + .ToSelectList(false) + .Where(x => x.Value != ((int)PriceDisplayType.Hide).ToString()) + .ToList(); + + model.Projection.SerializedAppendDescriptionText = string.Join(",", projection.AppendDescriptionText.SplitSafe(",").Select(x => x.EncodeJsString())); + model.Projection.SerializedCriticalCharacters = string.Join(",", projection.CriticalCharacters.SplitSafe(",").Select(x => x.EncodeJsString())); + + model.Filter.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); + + model.Filter.AvailableCategories = allCategories + .Select(x => new SelectListItem { Text = x.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = x.Id.ToString() }) + .ToList(); + + model.Filter.AvailableManufacturers = allManufacturers + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.Filter.AvailableProductTags = allProductTags + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + } + else if (model.Provider.EntityType == ExportEntityType.Customer) + { + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); + var allCountries = _countryService.GetAllCountries(true); + + model.Filter.AvailableCustomerRoles = allCustomerRoles + .OrderBy(x => x.Name) + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.Filter.AvailableCountries = allCountries + .Select(x => new SelectListItem { Text = x.GetLocalized(y => y.Name, language.Id, true, false), Value = x.Id.ToString() }) + .ToList(); + } + else if (model.Provider.EntityType == ExportEntityType.Order) + { + model.Projection.AvailableOrderStatusChange = ExportOrderStatusChange.Processing.ToSelectList(false); + + model.Filter.AvailableOrderStates = OrderStatus.Pending.ToSelectList(false).ToList(); + model.Filter.AvailablePaymentStates = PaymentStatus.Pending.ToSelectList(false).ToList(); + model.Filter.AvailableShippingStates = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); + } + + try + { + var configInfo = provider.Value.ConfigurationInfo; + if (configInfo != null) + { + model.Provider.ConfigPartialViewName = configInfo.PartialViewName; + model.Provider.ConfigDataType = configInfo.ModelType; + model.Provider.ConfigData = XmlHelper.Deserialize(profile.ProviderConfigData, configInfo.ModelType); + + if (configInfo.Initialize != null) + { + try + { + configInfo.Initialize(model.Provider.ConfigData); + } + catch (Exception exc) + { + NotifyWarning(exc.ToAllMessages()); + } + } + } + } + catch (Exception exc) + { + NotifyError(exc); + } + } + } + + #endregion + + public ActionResult Index() + { + return RedirectToAction("List"); + } + + public ActionResult List() + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var providers = _exportService.LoadAllExportProviders(0, false).ToList(); + var profiles = _exportService.GetExportProfiles().ToList(); + var model = new List(); + + foreach (var profile in profiles) + { + var provider = providers.FirstOrDefault(x => x.Metadata.SystemName == profile.ProviderSystemName); + if (provider != null) + { + var profileModel = new ExportProfileModel(); + var fileDetailsModel = CreateFileDetailsModel(profile, provider, null); + + PrepareProfileModel(profileModel, profile, provider); + + profileModel.FileCount = fileDetailsModel.FileCount; + profileModel.TaskModel = profile.ScheduleTask.ToScheduleTaskModel(Services.Localization, _dateTimeHelper, Url); + + model.Add(profileModel); + } + } + + return View(model); + } + + public ActionResult ProfileListDetails(int profileId) + { + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + { + var profile = _exportService.GetExportProfileById(profileId); + if (profile != null) + { + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + if (provider != null && !provider.Metadata.IsHidden) + { + var model = CreateFileDetailsModel(profile, provider, null); + return Json(this.RenderPartialViewToString("ProfileFileCount", model.FileCount), JsonRequestBehavior.AllowGet); + } + } + } + + return new EmptyResult(); + } + + public ActionResult ProfileFileDetails(int profileId, int deploymentId) + { + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + { + if (profileId != 0) + { + var profile = _exportService.GetExportProfileById(profileId); + if (profile != null) + { + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + if (provider != null && !provider.Metadata.IsHidden) + { + var model = CreateFileDetailsModel(profile, provider, null); + return PartialView(model); + } + } + } + else if (deploymentId != 0) + { + var deployment = _exportService.GetExportDeploymentById(deploymentId); + if (deployment != null) + { + var model = CreateFileDetailsModel(deployment.Profile, null, deployment); + return PartialView(model); + } + } + } + + return new EmptyResult(); + } + + public ActionResult Create() + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return Content(T("Admin.AccessDenied.Description")); + + var count = 0; + var allProviders = _exportService.LoadAllExportProviders(0, false); + + var model = new ExportProfileModel(); + model.UnspecifiedString = T("Common.Unspecified"); + + model.Provider = new ExportProfileModel.ProviderModel(); + + model.AvailableProviders = allProviders + .Select(x => + { + var item = new ExportProfileModel.ProviderSelectItem + { + Id = ++count, + SystemName = x.Metadata.SystemName, + FriendlyName = _pluginMediator.GetLocalizedFriendlyName(x.Metadata), + ImageUrl = GetThumbnailUrl(x), + Description = _pluginMediator.GetLocalizedDescription(x.Metadata) + }; + return item; + }) + .ToList(); + + model.AvailableProfiles = _exportService.GetExportProfiles() + .ToList() + .Select(x => + { + var item = new ExportProfileModel.ProviderSelectItem + { + Id = x.Id, + SystemName = x.ProviderSystemName, + FriendlyName = x.Name, + ImageUrl = GetThumbnailUrl(allProviders.FirstOrDefault(y => y.Metadata.SystemName.IsCaseInsensitiveEqual(x.ProviderSystemName))) + }; + return item; + }) + .ToList(); + + return PartialView(model); + } + + [HttpPost] + public ActionResult Create(ExportProfileModel model) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + if (model.ProviderSystemName.HasValue()) + { + var provider = _exportService.LoadProvider(model.ProviderSystemName); + if (provider != null) + { + var profile = _exportService.InsertExportProfile(provider, false, null, model.CloneProfileId ?? 0); + + return RedirectToAction("Edit", new { id = profile.Id }); + } + } + + NotifyError(T("Admin.Common.ProviderNotLoaded", model.ProviderSystemName.NaIfEmpty())); + + return RedirectToAction("List"); + } + + public ActionResult Edit(int id) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + if (provider == null || provider.Metadata.IsHidden) + return RedirectToAction("List"); + + var model = new ExportProfileModel(); + + PrepareProfileModel(model, profile, provider); + PrepareProfileModelForEdit(model, profile, provider); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + public ActionResult Edit(ExportProfileModel model, bool continueEditing) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(model.Id); + if (profile == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + if (provider == null || provider.Metadata.IsHidden) + return RedirectToAction("List"); + + if (!ModelState.IsValid) + { + PrepareProfileModel(model, profile, provider); + PrepareProfileModelForEdit(model, profile, provider); + return View(model); + } + + profile.Name = model.Name; + profile.FileNamePattern = model.FileNamePattern; + profile.FolderName = model.FolderName; + profile.Enabled = model.Enabled; + profile.Offset = model.Offset; + profile.Limit = model.Limit ?? 0; + profile.BatchSize = model.BatchSize ?? 0; + profile.PerStore = model.PerStore; + profile.CompletedEmailAddresses = model.CompletedEmailAddresses; + profile.EmailAccountId = model.EmailAccountId ?? 0; + profile.CreateZipArchive = model.CreateZipArchive; + profile.Cleanup = model.Cleanup; + + if (profile.Name.IsEmpty()) + profile.Name = provider.Metadata.FriendlyName; + + if (profile.Name.IsEmpty()) + profile.Name = provider.Metadata.SystemName; + + // projection + if (model.Projection != null) + { + var projection = new ExportProjection + { + StoreId = model.Projection.StoreId, + LanguageId = model.Projection.LanguageId, + CurrencyId = model.Projection.CurrencyId, + CustomerId = model.Projection.CustomerId, + NumberOfPictures = model.Projection.NumberOfPictures, + DescriptionMergingId = model.Projection.DescriptionMergingId, + DescriptionToPlainText = model.Projection.DescriptionToPlainText, + AppendDescriptionText = model.Projection.AppendDescriptionText, + RemoveCriticalCharacters = model.Projection.RemoveCriticalCharacters, + CriticalCharacters = model.Projection.CriticalCharacters, + PriceType = model.Projection.PriceType, + ConvertNetToGrossPrices = model.Projection.ConvertNetToGrossPrices, + Brand = model.Projection.Brand, + PictureSize = model.Projection.PictureSize, + ShippingTime = model.Projection.ShippingTime, + ShippingCosts = model.Projection.ShippingCosts, + FreeShippingThreshold = model.Projection.FreeShippingThreshold, + AttributeCombinationAsProduct = model.Projection.AttributeCombinationAsProduct, + AttributeCombinationValueMergingId = model.Projection.AttributeCombinationValueMergingId, + NoGroupedProducts = model.Projection.NoGroupedProducts, + OrderStatusChangeId = model.Projection.OrderStatusChangeId + }; + + profile.Projection = XmlHelper.Serialize(projection); + } + + // filtering + if (model.Filter != null) + { + var filter = new ExportFilter + { + StoreId = model.Filter.StoreId ?? 0, + CreatedFrom = model.Filter.CreatedFrom, + CreatedTo = model.Filter.CreatedTo, + PriceMinimum = model.Filter.PriceMinimum, + PriceMaximum = model.Filter.PriceMaximum, + AvailabilityMinimum = model.Filter.AvailabilityMinimum, + AvailabilityMaximum = model.Filter.AvailabilityMaximum, + IsPublished = model.Filter.IsPublished, + CategoryIds = model.Filter.CategoryIds, + WithoutCategories = model.Filter.WithoutCategories, + ManufacturerId = model.Filter.ManufacturerId, + WithoutManufacturers = model.Filter.WithoutManufacturers, + ProductTagId = model.Filter.ProductTagId, + FeaturedProducts = model.Filter.FeaturedProducts, + IsActiveCustomer = model.Filter.IsActiveCustomer, + IsTaxExempt = model.Filter.IsTaxExempt, + BillingCountryIds = model.Filter.BillingCountryIds, + ShippingCountryIds = model.Filter.ShippingCountryIds, + LastActivityFrom = model.Filter.LastActivityFrom, + LastActivityTo = model.Filter.LastActivityTo, + HasSpentAtLeastAmount = model.Filter.HasSpentAtLeastAmount, + HasPlacedAtLeastOrders = model.Filter.HasPlacedAtLeastOrders, + ProductType = model.Filter.ProductType, + IdMinimum = model.Filter.IdMinimum, + IdMaximum = model.Filter.IdMaximum, + OrderStatusIds = model.Filter.OrderStatusIds, + PaymentStatusIds = model.Filter.PaymentStatusIds, + ShippingStatusIds = model.Filter.ShippingStatusIds, + CustomerRoleIds = model.Filter.CustomerRoleIds, + IsActiveSubscriber = model.Filter.IsActiveSubscriber + }; + + profile.Filtering = XmlHelper.Serialize(filter); + } + + // provider configuration + profile.ProviderConfigData = null; + try + { + var configInfo = provider.Value.ConfigurationInfo; + if (configInfo != null && model.CustomProperties.ContainsKey("ProviderConfigData")) + { + profile.ProviderConfigData = XmlHelper.Serialize(model.CustomProperties["ProviderConfigData"], configInfo.ModelType); + } + } + catch (Exception exc) + { + NotifyError(exc); + } + + _exportService.UpdateExportProfile(profile); + + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + + return (continueEditing ? RedirectToAction("Edit", new { id = profile.Id }) : RedirectToAction("List")); + } + + [HttpPost, ActionName("Delete")] + public ActionResult DeleteConfirmed(int id) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + if (provider == null || provider.Metadata.IsHidden) + return RedirectToAction("List"); + + try + { + _exportService.DeleteExportProfile(profile); + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + + return RedirectToAction("List"); + } + catch (Exception exc) + { + NotifyError(exc); + } + + return RedirectToAction("Edit", new { id = profile.Id }); + } + + public ActionResult Preview(int id) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + if (provider == null || provider.Metadata.IsHidden) + return RedirectToAction("List"); + + if (!profile.Enabled) + { + NotifyInfo(T("Admin.DataExchange.Export.EnableProfileForPreview")); + + return RedirectToAction("Edit", new { id = profile.Id }); + } + + var request = new DataExportRequest(profile, provider); + var totalRecords = _dataExporter.GetDataCount(request); + + var model = new ExportPreviewModel + { + Id = profile.Id, + Name = profile.Name, + ThumbnailUrl = GetThumbnailUrl(provider), + GridPageSize = DataExporter.PageSize, + EntityType = provider.Value.EntityType, + TotalRecords = totalRecords, + LogFileExists = System.IO.File.Exists(profile.GetExportLogPath()), + UsernamesEnabled = _customerSettings.Value.UsernamesEnabled + }; + + return View(model); + } + + [HttpPost, GridAction(EnableCustomBinding = true)] + public ActionResult PreviewList(GridCommand command, int id, int totalRecords) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + { + NotifyAccessDenied(); + + return new JsonResult { Data = Enumerable.Empty() }; + } + + ExportProfile profile = null; + Provider provider = null; + + if ((profile = _exportService.GetExportProfileById(id)) != null && + (provider = _exportService.LoadProvider(profile.ProviderSystemName)) != null && + !provider.Metadata.IsHidden) + { + var productModel = new List(); + var orderModel = new List(); + var categoryModel = new List(); + var manuModel = new List(); + var customerModel = new List(); + var subscriberModel = new List(); + + object gridData = null; + Dictionary allCategories = null; + IList allStores = null; + + var request = new DataExportRequest(profile, provider); + var normalizedTotal = totalRecords; + + if (profile.Limit > 0 && normalizedTotal > profile.Limit) + normalizedTotal = profile.Limit; + + var data = _dataExporter.Preview(request, command.Page - 1, totalRecords); + + foreach (dynamic item in data) + { + if (provider.Value.EntityType == ExportEntityType.Product) + { + var product = item.Entity as Product; + + productModel.Add(new ExportPreviewProductModel + { + Id = product.Id, + ProductTypeId = product.ProductTypeId, + ProductTypeName = product.GetProductTypeLabel(Services.Localization), + ProductTypeLabelHint = product.ProductTypeLabelHint, + Name = item.Name, + Sku = item.Sku, + Price = item.Price, + Published = product.Published, + StockQuantity = product.StockQuantity, + AdminComment = item.AdminComment + }); + } + else if (provider.Value.EntityType == ExportEntityType.Order) + { + orderModel.Add(new ExportPreviewOrderModel + { + Id = item.Id, + HasNewPaymentNotification = item.HasNewPaymentNotification, + OrderNumber = item.OrderNumber, + OrderStatus = item.OrderStatus, + PaymentStatus = item.PaymentStatus, + ShippingStatus = item.ShippingStatus, + CustomerEmail = item.Customer.Email, + StoreName = (item.Store == null ? "".NaIfEmpty() : item.Store.Name), + CreatedOn = _dateTimeHelper.ConvertToUserTime(item.CreatedOnUtc, DateTimeKind.Utc), + OrderTotal = item.OrderTotal + }); + } + else if (provider.Value.EntityType == ExportEntityType.Category) + { + var category = item.Entity as Category; + + if (allCategories == null) + { + allCategories = _categoryService.GetAllCategories(showHidden: true, applyNavigationFilters: false) + .ToDictionary(x => x.Id); + } + + categoryModel.Add(new ExportPreviewCategoryModel + { + Id = category.Id, + Breadcrumb = category.GetCategoryBreadCrumb(_categoryService, allCategories), + FullName = item.FullName, + Alias = item.Alias, + Published = category.Published, + DisplayOrder = category.DisplayOrder, + LimitedToStores = category.LimitedToStores + }); + } + else if (provider.Value.EntityType == ExportEntityType.Manufacturer) + { + manuModel.Add(new ExportPreviewManufacturerModel + { + Id = item.Id, + Name = item.Name, + Published = item.Published, + DisplayOrder = item.DisplayOrder, + LimitedToStores = item.LimitedToStores + }); + } + else if (provider.Value.EntityType == ExportEntityType.Customer) + { + var customer = item.Entity as Customer; + var customerRoles = item.CustomerRoles as List; + var customerRolesString = string.Join(", ", customerRoles.Select(x => x.Name)); + + customerModel.Add(new ExportPreviewCustomerModel + { + Id = customer.Id, + Active = customer.Active, + CreatedOn = _dateTimeHelper.ConvertToUserTime(customer.CreatedOnUtc, DateTimeKind.Utc), + CustomerRoleNames = customerRolesString, + Email = customer.Email, + FullName = item._FullName, + LastActivityDate = _dateTimeHelper.ConvertToUserTime(customer.LastActivityDateUtc, DateTimeKind.Utc), + Username = customer.Username + }); + } + else if (provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) + { + var subscription = item.Entity as NewsLetterSubscription; + + if (allStores == null) + allStores = Services.StoreService.GetAllStores(); + + var store = allStores.FirstOrDefault(x => x.Id == subscription.StoreId); + + subscriberModel.Add(new ExportPreviewNewsLetterSubscriptionModel + { + Id = subscription.Id, + Active = subscription.Active, + CreatedOn = _dateTimeHelper.ConvertToUserTime(subscription.CreatedOnUtc, DateTimeKind.Utc), + Email = subscription.Email, + StoreName = (store == null ? "".NaIfEmpty() : store.Name) + }); + } + } + + if (provider.Value.EntityType == ExportEntityType.Product) + { + gridData = new GridModel { Data = productModel, Total = normalizedTotal }; + } + else if (provider.Value.EntityType == ExportEntityType.Order) + { + gridData = new GridModel { Data = orderModel, Total = normalizedTotal }; + } + else if (provider.Value.EntityType == ExportEntityType.Category) + { + gridData = new GridModel { Data = categoryModel, Total = normalizedTotal }; + } + else if (provider.Value.EntityType == ExportEntityType.Manufacturer) + { + gridData = new GridModel { Data = manuModel, Total = normalizedTotal }; + } + else if (provider.Value.EntityType == ExportEntityType.Customer) + { + gridData = new GridModel { Data = customerModel, Total = normalizedTotal }; + } + else if (provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) + { + gridData = new GridModel { Data = subscriberModel, Total = normalizedTotal }; + } + + return new JsonResult { Data = gridData }; + } + + return new JsonResult { Data = Enumerable.Empty() }; + } + + [HttpPost] + public ActionResult Execute(int id, string selectedIds) + { + // permissions checked internally by DataExporter + + var profile = _exportService.GetExportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + if (provider == null || provider.Metadata.IsHidden) + return RedirectToAction("List"); + + var taskParams = new Dictionary(); + taskParams.Add(TaskExecutor.CurrentCustomerIdParamName, Services.WorkContext.CurrentCustomer.Id.ToString()); + + if (selectedIds.HasValue()) + taskParams.Add("SelectedIds", selectedIds); + + _taskScheduler.RunSingleTask(profile.SchedulingTaskId, taskParams); + + NotifyInfo(T("Admin.System.ScheduleTasks.RunNow.Progress.DataExportTask")); + + var referrer = Services.WebHelper.GetUrlReferrer(); + if (referrer.HasValue()) + return Redirect(referrer); + + return RedirectToAction("List"); + } + + [ChildActionOnly] + public ActionResult InfoProfile(string systemName, string returnUrl) + { + var profiles = _exportService.GetExportProfilesBySystemName(systemName); + + var model = new ProfileInfoForProviderModel + { + ReturnUrl = returnUrl, + SystemName = systemName + }; + + model.Profiles = profiles + .OrderBy(x => x.Enabled) + .Select(x => + { + var profileModel = new ProfileInfoForProviderModel.ProfileModel + { + Id = x.Id, + Name = x.Name, + Enabled = x.Enabled, + ScheduleTaskId = (x.Enabled ? x.SchedulingTaskId : (int?)null) + }; + + return profileModel; + }) + .ToList(); + + return PartialView(model); + } + + public ActionResult DownloadLogFile(int id) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(id); + if (profile != null) + { + var path = profile.GetExportLogPath(); + if (System.IO.File.Exists(path)) + { + try + { + var stream = new FileStream(path, FileMode.Open); + var result = new FileStreamResult(stream, MediaTypeNames.Text.Plain); + + return result; + } + catch (IOException) + { + NotifyWarning(T("Admin.Common.FileInUse")); + } + } + } + + return RedirectToAction("List"); + } + + public ActionResult DownloadExportFile(int id, string name, bool? isDeployment) + { + string message = null; + string path = null; + + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + { + if (isDeployment ?? false) + { + var deployment = _exportService.GetExportDeploymentById(id); + if (deployment != null) + { + var deploymentFolder = deployment.GetDeploymentFolder(); + if (deploymentFolder.HasValue()) + path = Path.Combine(deploymentFolder, name); + } + } + else + { + var profile = _exportService.GetExportProfileById(id); + if (profile != null) + { + path = Path.Combine(profile.GetExportFolder(true), name); + if (!System.IO.File.Exists(path)) + path = Path.Combine(profile.GetExportFolder(false), name); + } + } + + if (System.IO.File.Exists(path)) + { + try + { + var stream = new FileStream(path, FileMode.Open); + + var result = new FileStreamResult(stream, MimeTypes.MapNameToMimeType(path)); + result.FileDownloadName = Path.GetFileName(path); + + return result; + } + catch (IOException) + { + NotifyWarning(T("Admin.Common.FileInUse")); + } + } + } + else + { + message = T("Admin.AccessDenied.Description"); + } + + if (message.IsEmpty()) + { + message = T("Admin.Common.ResourceNotFound"); + } + + return File(Encoding.UTF8.GetBytes(message), MediaTypeNames.Text.Plain, "DownloadExportFile.txt"); + } + + public ActionResult ResolveFileNamePatternExample(int id, string pattern) + { + var profile = _exportService.GetExportProfileById(id); + + Services.DbContext.DetachEntity(profile); + profile.FileNamePattern = pattern.EmptyNull(); + + var resolvedPattern = profile.ResolveFileNamePattern(Services.StoreContext.CurrentStore, 1, _dataExchangeSettings.MaxFileNameLength); + + return this.Content(resolvedPattern); + } + + #region Export deployment + + public ActionResult CreateDeployment(int id) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + if (provider.Metadata.IsHidden) + return RedirectToAction("List"); + + var model = CreateDeploymentModel(profile, new ExportDeployment + { + ProfileId = id, + Enabled = true, + DeploymentType = ExportDeploymentType.FileSystem, + Name = profile.Name + }, provider, true); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + public ActionResult CreateDeployment(ExportDeploymentModel model, bool continueEditing, ExportDeploymentType deploymentType) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(model.ProfileId); + if (profile == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + if (provider == null || provider.Metadata.IsHidden) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + var deployment = new ExportDeployment(); + + ModelToEntity(model, deployment); + + profile.Deployments.Add(deployment); + + _exportService.UpdateExportProfile(profile); + + return SmartRedirect(continueEditing, profile.Id, deployment.Id); + } + + return CreateDeployment(profile.Id); + } + + public ActionResult EditDeployment(int id) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var deployment = _exportService.GetExportDeploymentById(id); + if (deployment == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(deployment.Profile.ProviderSystemName); + if (provider == null || provider.Metadata.IsHidden) + return RedirectToAction("List"); + + var model = CreateDeploymentModel(deployment.Profile, deployment, provider, true); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + public ActionResult EditDeployment(ExportDeploymentModel model, bool continueEditing) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var deployment = _exportService.GetExportDeploymentById(model.Id); + if (deployment == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(deployment.Profile.ProviderSystemName); + if (provider == null || provider.Metadata.IsHidden) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + ModelToEntity(model, deployment); + + _exportService.UpdateExportProfile(deployment.Profile); + + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + + return SmartRedirect(continueEditing, deployment.ProfileId, deployment.Id); + } + + model = CreateDeploymentModel(deployment.Profile, deployment, _exportService.LoadProvider(deployment.Profile.ProviderSystemName), true); + + return View(model); + } + + [HttpPost] + public ActionResult DeleteDeployment(int id) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var deployment = _exportService.GetExportDeploymentById(id); + if (deployment == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(deployment.Profile.ProviderSystemName); + if (provider == null || provider.Metadata.IsHidden) + return RedirectToAction("List"); + + int profileId = deployment.ProfileId; + + _exportService.DeleteExportDeployment(deployment); + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + + return SmartRedirect(false, profileId, 0); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExternalAuthenticationController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExternalAuthenticationController.cs index a85981d8cd..c65ac1fc88 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExternalAuthenticationController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExternalAuthenticationController.cs @@ -10,6 +10,7 @@ using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs index 6c71ef3dcd..d1a4320591 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ForumController.cs @@ -2,11 +2,17 @@ using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Forums; +using SmartStore.Core.Domain.Forums; +using SmartStore.Services; using SmartStore.Services.Forums; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Security; +using SmartStore.Services.Seo; +using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; namespace SmartStore.Admin.Controllers { @@ -14,20 +20,86 @@ namespace SmartStore.Admin.Controllers public class ForumController : AdminControllerBase { private readonly IForumService _forumService; + private readonly ICommonServices _services; private readonly IDateTimeHelper _dateTimeHelper; - private readonly ILocalizationService _localizationService; - private readonly IPermissionService _permissionService; + private readonly IStoreMappingService _storeMappingService; + private readonly ILanguageService _languageService; + private readonly ILocalizedEntityService _localizedEntityService; + private readonly IUrlRecordService _urlRecordService; public ForumController(IForumService forumService, - IDateTimeHelper dateTimeHelper, ILocalizationService localizationService, - IPermissionService permissionService) + ICommonServices services, + IDateTimeHelper dateTimeHelper, + IStoreMappingService storeMappingService, + ILanguageService languageService, + ILocalizedEntityService localizedEntityService, + IUrlRecordService urlRecordService) { - this._forumService = forumService; - this._dateTimeHelper = dateTimeHelper; - this._localizationService = localizationService; - this._permissionService = permissionService; + _forumService = forumService; + _services = services; + _dateTimeHelper = dateTimeHelper; + _storeMappingService = storeMappingService; + _languageService = languageService; + _localizedEntityService = localizedEntityService; + _urlRecordService = urlRecordService; } + #region Utilities + + [NonAction] + private void PrepareForumGroupModel(ForumGroupModel model, ForumGroup forumGroup, bool excludeProperties) + { + if (model == null) + throw new ArgumentNullException("model"); + + var allStores = _services.StoreService.GetAllStores(); + + model.AvailableStores = allStores.Select(s => s.ToModel()).ToList(); + + if (forumGroup != null) + { + model.CreatedOn = _dateTimeHelper.ConvertToUserTime(forumGroup.CreatedOnUtc, DateTimeKind.Utc); + } + + if (!excludeProperties) + { + if (forumGroup != null) + model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(forumGroup); + else + model.SelectedStoreIds = new int[0]; + } + + ViewBag.StoreCount = allStores.Count; + } + + [NonAction] + private void UpdateLocales(ForumGroupModel model, ForumGroup forumGroup) + { + foreach (var localized in model.Locales) + { + _localizedEntityService.SaveLocalizedValue(forumGroup, x => x.Name, localized.Name, localized.LanguageId); + _localizedEntityService.SaveLocalizedValue(forumGroup, x => x.Description, localized.Description, localized.LanguageId); + + var seName = forumGroup.ValidateSeName(localized.SeName, localized.Name, false, localized.LanguageId); + _urlRecordService.SaveSlug(forumGroup, seName, localized.LanguageId); + } + } + + [NonAction] + private void UpdateLocales(ForumModel model, Forum forum) + { + foreach (var localized in model.Locales) + { + _localizedEntityService.SaveLocalizedValue(forum, x => x.Name, localized.Name, localized.LanguageId); + _localizedEntityService.SaveLocalizedValue(forum, x => x.Description, localized.Description, localized.LanguageId); + + var seName = forum.ValidateSeName(localized.SeName, localized.Name, false, localized.LanguageId); + _urlRecordService.SaveSlug(forum, seName, localized.LanguageId); + } + } + + #endregion + #region List public ActionResult Index() @@ -37,23 +109,27 @@ public ActionResult Index() public ActionResult List() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageForums)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); - var forumGroupsModel = _forumService.GetAllForumGroups() + var forumGroupsModel = _forumService.GetAllForumGroups(true) .Select(fg => { var forumGroupModel = fg.ToModel(); - forumGroupModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(fg.CreatedOnUtc, DateTimeKind.Utc); + + PrepareForumGroupModel(forumGroupModel, fg, false); + foreach (var f in fg.Forums.OrderBy(x=>x.DisplayOrder)) { var forumModel = f.ToModel(); forumModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(f.CreatedOnUtc, DateTimeKind.Utc); + forumGroupModel.ForumModels.Add(forumModel); } return forumGroupModel; }) .ToList(); + return View(forumGroupsModel); } @@ -63,67 +139,95 @@ public ActionResult List() public ActionResult CreateForumGroup() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageForums)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); - return View(new ForumGroupModel { DisplayOrder = 1 }); + var model = new ForumGroupModel { DisplayOrder = 1 }; + + AddLocales(_languageService, model.Locales); + + PrepareForumGroupModel(model, null, false); + + return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult CreateForumGroup(ForumGroupModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageForums)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); if (ModelState.IsValid) { - var forumGroup = model.ToEntity(); - forumGroup.CreatedOnUtc = DateTime.UtcNow; - forumGroup.UpdatedOnUtc = DateTime.UtcNow; + var utcNow = DateTime.UtcNow; + var forumGroup = model.ToEntity(); + forumGroup.CreatedOnUtc = utcNow; + forumGroup.UpdatedOnUtc = utcNow; + _forumService.InsertForumGroup(forumGroup); - NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.Forums.ForumGroup.Added")); + model.SeName = forumGroup.ValidateSeName(model.SeName, forumGroup.Name, true); + _urlRecordService.SaveSlug(forumGroup, model.SeName, 0); + + UpdateLocales(model, forumGroup); + + _storeMappingService.SaveStoreMappings(forumGroup, model.SelectedStoreIds); + + NotifySuccess(_services.Localization.GetResource("Admin.ContentManagement.Forums.ForumGroup.Added")); + return continueEditing ? RedirectToAction("EditForumGroup", new { forumGroup.Id }) : RedirectToAction("List"); } //If we got this far, something failed, redisplay form + + PrepareForumGroupModel(model, null, true); + return View(model); } public ActionResult CreateForum() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageForums)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); - var model = new ForumModel(); - foreach (var forumGroup in _forumService.GetAllForumGroups()) + var model = new ForumModel { DisplayOrder = 1 }; + + AddLocales(_languageService, model.Locales); + + foreach (var forumGroup in _forumService.GetAllForumGroups(true)) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); } - model.DisplayOrder = 1; + return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult CreateForum(ForumModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageForums)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); if (ModelState.IsValid) { + var utcNow = DateTime.UtcNow; var forum = model.ToEntity(); - forum.CreatedOnUtc = DateTime.UtcNow; - forum.UpdatedOnUtc = DateTime.UtcNow; + forum.CreatedOnUtc = utcNow; + forum.UpdatedOnUtc = utcNow; + _forumService.InsertForum(forum); - NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.Forums.Forum.Added")); + model.SeName = forum.ValidateSeName(model.SeName, forum.Name, true); + _urlRecordService.SaveSlug(forum, model.SeName, 0); + + NotifySuccess(_services.Localization.GetResource("Admin.ContentManagement.Forums.Forum.Added")); + return continueEditing ? RedirectToAction("EditForum", new { forum.Id }) : RedirectToAction("List"); } //If we got this far, something failed, redisplay form - foreach (var forumGroup in _forumService.GetAllForumGroups()) + foreach (var forumGroup in _forumService.GetAllForumGroups(true)) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); @@ -137,55 +241,82 @@ public ActionResult CreateForum(ForumModel model, bool continueEditing) public ActionResult EditForumGroup(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageForums)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); var forumGroup = _forumService.GetForumGroupById(id); if (forumGroup == null) - //No forum group found with the specified id return RedirectToAction("List"); var model = forumGroup.ToModel(); + + AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.Name = forumGroup.GetLocalized(x => x.Name, languageId, false, false); + locale.Description = forumGroup.GetLocalized(x => x.Description, languageId, false, false); + locale.SeName = forumGroup.GetSeName(languageId, false, false); + }); + + PrepareForumGroupModel(model, forumGroup, false); + return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult EditForumGroup(ForumGroupModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageForums)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); var forumGroup = _forumService.GetForumGroupById(model.Id); if (forumGroup == null) - //No forum group found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) { forumGroup = model.ToEntity(forumGroup); forumGroup.UpdatedOnUtc = DateTime.UtcNow; + _forumService.UpdateForumGroup(forumGroup); - NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.Forums.ForumGroup.Updated")); + model.SeName = forumGroup.ValidateSeName(model.SeName, forumGroup.Name, true); + _urlRecordService.SaveSlug(forumGroup, model.SeName, 0); + + UpdateLocales(model, forumGroup); + + _storeMappingService.SaveStoreMappings(forumGroup, model.SelectedStoreIds); + + NotifySuccess(_services.Localization.GetResource("Admin.ContentManagement.Forums.ForumGroup.Updated")); + return continueEditing ? RedirectToAction("EditForumGroup", forumGroup.Id) : RedirectToAction("List"); } //If we got this far, something failed, redisplay form + + PrepareForumGroupModel(model, forumGroup, true); + return View(model); } public ActionResult EditForum(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageForums)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); var forum = _forumService.GetForumById(id); if (forum == null) - //No forum found with the specified id return RedirectToAction("List"); var model = forum.ToModel(); - foreach (var forumGroup in _forumService.GetAllForumGroups()) + + AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.Name = forum.GetLocalized(x => x.Name, languageId, false, false); + locale.Description = forum.GetLocalized(x => x.Description, languageId, false, false); + locale.SeName = forum.GetSeName(languageId, false, false); + }); + + foreach (var forumGroup in _forumService.GetAllForumGroups(true)) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); @@ -193,29 +324,35 @@ public ActionResult EditForum(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult EditForum(ForumModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageForums)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); var forum = _forumService.GetForumById(model.Id); if (forum == null) - //No forum found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) { forum = model.ToEntity(forum); forum.UpdatedOnUtc = DateTime.UtcNow; + _forumService.UpdateForum(forum); - NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.Forums.Forum.Updated")); + model.SeName = forum.ValidateSeName(model.SeName, forum.Name, true); + _urlRecordService.SaveSlug(forum, model.SeName, 0); + + UpdateLocales(model, forum); + + NotifySuccess(_services.Localization.GetResource("Admin.ContentManagement.Forums.Forum.Updated")); + return continueEditing ? RedirectToAction("EditForum", forum.Id) : RedirectToAction("List"); } //If we got this far, something failed, redisplay form - foreach (var forumGroup in _forumService.GetAllForumGroups()) + foreach (var forumGroup in _forumService.GetAllForumGroups(true)) { var forumGroupModel = forumGroup.ToModel(); model.ForumGroups.Add(forumGroupModel); @@ -230,34 +367,32 @@ public ActionResult EditForum(ForumModel model, bool continueEditing) [HttpPost] public ActionResult DeleteForumGroup(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageForums)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); var forumGroup = _forumService.GetForumGroupById(id); if (forumGroup == null) - //No forum group found with the specified id return RedirectToAction("List"); _forumService.DeleteForumGroup(forumGroup); - NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.Forums.ForumGroup.Deleted")); + NotifySuccess(_services.Localization.GetResource("Admin.ContentManagement.Forums.ForumGroup.Deleted")); return RedirectToAction("List"); } [HttpPost] public ActionResult DeleteForum(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageForums)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageForums)) return AccessDeniedView(); var forum = _forumService.GetForumById(id); if (forum == null) - //No forum found with the specified id return RedirectToAction("List"); _forumService.DeleteForum(forum); - NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.Forums.Forum.Deleted")); + NotifySuccess(_services.Localization.GetResource("Admin.ContentManagement.Forums.Forum.Deleted")); return RedirectToAction("List"); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs index 17f6cc0416..0900557d31 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs @@ -2,19 +2,20 @@ using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Orders; -using SmartStore.Core; -using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Logging; +using SmartStore.Services; using SmartStore.Services.Catalog; -using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; -using SmartStore.Core.Logging; using SmartStore.Services.Messages; using SmartStore.Services.Orders; using SmartStore.Services.Security; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -29,35 +30,34 @@ public class GiftCardController : AdminControllerBase private readonly IWorkflowMessageService _workflowMessageService; private readonly IDateTimeHelper _dateTimeHelper; private readonly LocalizationSettings _localizationSettings; - private readonly ICurrencyService _currencyService; - private readonly CurrencySettings _currencySettings; - private readonly ILocalizationService _localizationService; private readonly ILanguageService _languageService; private readonly ICustomerActivityService _customerActivityService; - private readonly IPermissionService _permissionService; - - #endregion - - #region Constructors - - public GiftCardController(IGiftCardService giftCardService, - IPriceFormatter priceFormatter, IWorkflowMessageService workflowMessageService, - IDateTimeHelper dateTimeHelper, LocalizationSettings localizationSettings, - ICurrencyService currencyService, CurrencySettings currencySettings, - ILocalizationService localizationService, ILanguageService languageService, - ICustomerActivityService customerActivityService, IPermissionService permissionService) + private readonly ICommonServices _services; + private readonly AdminAreaSettings _adminAreaSettings; + + #endregion + + #region Constructors + + public GiftCardController(IGiftCardService giftCardService, + IPriceFormatter priceFormatter, + IWorkflowMessageService workflowMessageService, + IDateTimeHelper dateTimeHelper, + LocalizationSettings localizationSettings, + ILanguageService languageService, + ICustomerActivityService customerActivityService, + ICommonServices services, + AdminAreaSettings adminAreaSettings) { this._giftCardService = giftCardService; this._priceFormatter = priceFormatter; this._workflowMessageService = workflowMessageService; this._dateTimeHelper = dateTimeHelper; this._localizationSettings = localizationSettings; - this._currencyService = currencyService; - this._currencySettings = currencySettings; - this._localizationService = localizationService; this._languageService = languageService; this._customerActivityService = customerActivityService; - this._permissionService = permissionService; + this._services = services; + this._adminAreaSettings = adminAreaSettings; } #endregion @@ -72,53 +72,63 @@ public ActionResult Index() public ActionResult List() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageGiftCards)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) return AccessDeniedView(); - var model = new GiftCardListModel(); - model.ActivatedList.Add(new SelectListItem() - { - Value = "0", - Selected = true, - Text = _localizationService.GetResource("Common.All", logIfNotFound: false, defaultValue: "All") - }); - model.ActivatedList.Add(new SelectListItem() + var model = new GiftCardListModel + { + GridPageSize = _adminAreaSettings.GridPageSize + }; + + model.ActivatedList.Add(new SelectListItem { Value = "1", - Text = _localizationService.GetResource("Common.Activated", logIfNotFound: false, defaultValue: "Activated") + Text = _services.Localization.GetResource("Common.Activated", logIfNotFound: false, defaultValue: "Activated") }); - model.ActivatedList.Add(new SelectListItem() + + model.ActivatedList.Add(new SelectListItem { Value = "2", - Text = _localizationService.GetResource("Common.Deactivated", logIfNotFound: false, defaultValue: "Deactivated") + Text = _services.Localization.GetResource("Common.Deactivated", logIfNotFound: false, defaultValue: "Deactivated") }); + return View(model); } [GridAction(EnableCustomBinding = true)] public ActionResult GiftCardList(GridCommand command, GiftCardListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageGiftCards)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) + { + bool? isGiftCardActivated = null; + + if (model.ActivatedId == 1) + isGiftCardActivated = true; + else if (model.ActivatedId == 2) + isGiftCardActivated = false; + + var giftCards = _giftCardService.GetAllGiftCards(null, null, null, isGiftCardActivated, model.CouponCode); + + gridModel.Data = giftCards.PagedForCommand(command).Select(x => + { + var m = x.ToModel(); + m.RemainingAmountStr = _priceFormatter.FormatPrice(x.GetGiftCardRemainingAmount(), true, false); + m.AmountStr = _priceFormatter.FormatPrice(x.Amount, true, false); + m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + return m; + }); + + gridModel.Total = giftCards.Count(); + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - bool? isGiftCardActivated = null; - if (model.ActivatedId == 1) - isGiftCardActivated = true; - else if (model.ActivatedId == 2) - isGiftCardActivated = false; - var giftCards = _giftCardService.GetAllGiftCards(null, null, null, isGiftCardActivated, model.CouponCode); - var gridModel = new GridModel - { - Data = giftCards.PagedForCommand(command).Select(x => - { - var m = x.ToModel(); - m.RemainingAmountStr = _priceFormatter.FormatPrice(x.GetGiftCardRemainingAmount(), true, false); - m.AmountStr = _priceFormatter.FormatPrice(x.Amount, true, false); - m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - return m; - }), - Total = giftCards.Count() - }; return new JsonResult { Data = gridModel @@ -127,19 +137,19 @@ public ActionResult GiftCardList(GridCommand command, GiftCardListModel model) public ActionResult Create() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageGiftCards)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) return AccessDeniedView(); var model = new GiftCardModel(); - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(GiftCardModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageGiftCards)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) return AccessDeniedView(); if (ModelState.IsValid) @@ -149,20 +159,21 @@ public ActionResult Create(GiftCardModel model, bool continueEditing) _giftCardService.InsertGiftCard(giftCard); //activity log - _customerActivityService.InsertActivity("AddNewGiftCard", _localizationService.GetResource("ActivityLog.AddNewGiftCard"), giftCard.GiftCardCouponCode); + _customerActivityService.InsertActivity("AddNewGiftCard", _services.Localization.GetResource("ActivityLog.AddNewGiftCard"), giftCard.GiftCardCouponCode); - NotifySuccess(_localizationService.GetResource("Admin.GiftCards.Added")); + NotifySuccess(_services.Localization.GetResource("Admin.GiftCards.Added")); return continueEditing ? RedirectToAction("Edit", new { id = giftCard.Id }) : RedirectToAction("List"); } //If we got this far, something failed, redisplay form - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; + return View(model); } public ActionResult Edit(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageGiftCards)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) return AccessDeniedView(); var giftCard = _giftCardService.GetGiftCardById(id); @@ -175,16 +186,16 @@ public ActionResult Edit(int id) model.RemainingAmountStr = _priceFormatter.FormatPrice(giftCard.GetGiftCardRemainingAmount(), true, false); model.AmountStr = _priceFormatter.FormatPrice(giftCard.Amount, true, false); model.CreatedOn = _dateTimeHelper.ConvertToUserTime(giftCard.CreatedOnUtc, DateTimeKind.Utc); - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [FormValueRequired("save", "save-continue")] public ActionResult Edit(GiftCardModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageGiftCards)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) return AccessDeniedView(); var giftCard = _giftCardService.GetGiftCardById(model.Id); @@ -193,7 +204,7 @@ public ActionResult Edit(GiftCardModel model, bool continueEditing) model.RemainingAmountStr = _priceFormatter.FormatPrice(giftCard.GetGiftCardRemainingAmount(), true, false); model.AmountStr = _priceFormatter.FormatPrice(giftCard.Amount, true, false); model.CreatedOn = _dateTimeHelper.ConvertToUserTime(giftCard.CreatedOnUtc, DateTimeKind.Utc); - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; if (ModelState.IsValid) { @@ -201,9 +212,9 @@ public ActionResult Edit(GiftCardModel model, bool continueEditing) _giftCardService.UpdateGiftCard(giftCard); //activity log - _customerActivityService.InsertActivity("EditGiftCard", _localizationService.GetResource("ActivityLog.EditGiftCard"), giftCard.GiftCardCouponCode); + _customerActivityService.InsertActivity("EditGiftCard", _services.Localization.GetResource("ActivityLog.EditGiftCard"), giftCard.GiftCardCouponCode); - NotifySuccess(_localizationService.GetResource("Admin.GiftCards.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.GiftCards.Updated")); return continueEditing ? RedirectToAction("Edit", giftCard.Id) : RedirectToAction("List"); } @@ -221,7 +232,7 @@ public ActionResult GenerateCouponCode() [FormValueRequired("notifyRecipient")] public ActionResult NotifyRecipient(GiftCardModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageGiftCards)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) return AccessDeniedView(); var giftCard = _giftCardService.GetGiftCardById(model.Id); @@ -231,7 +242,7 @@ public ActionResult NotifyRecipient(GiftCardModel model) model.RemainingAmountStr = _priceFormatter.FormatPrice(giftCard.GetGiftCardRemainingAmount(), true, false); model.AmountStr = _priceFormatter.FormatPrice(giftCard.Amount, true, false); model.CreatedOn = _dateTimeHelper.ConvertToUserTime(giftCard.CreatedOnUtc, DateTimeKind.Utc); - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; try { @@ -259,6 +270,8 @@ public ActionResult NotifyRecipient(GiftCardModel model) { giftCard.IsRecipientNotified = true; _giftCardService.UpdateGiftCard(giftCard); + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); } } catch (Exception exc) @@ -272,51 +285,53 @@ public ActionResult NotifyRecipient(GiftCardModel model) [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageGiftCards)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) return AccessDeniedView(); var giftCard = _giftCardService.GetGiftCardById(id); if (giftCard == null) - //No gift card found with the specified id return RedirectToAction("List"); _giftCardService.DeleteGiftCard(giftCard); - //activity log - _customerActivityService.InsertActivity("DeleteGiftCard", _localizationService.GetResource("ActivityLog.DeleteGiftCard"), giftCard.GiftCardCouponCode); + _customerActivityService.InsertActivity("DeleteGiftCard", _services.Localization.GetResource("ActivityLog.DeleteGiftCard"), giftCard.GiftCardCouponCode); - NotifySuccess(_localizationService.GetResource("Admin.GiftCards.Deleted")); + NotifySuccess(_services.Localization.GetResource("Admin.GiftCards.Deleted")); return RedirectToAction("List"); } - //Gif card usage history [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult UsageHistoryList(int giftCardId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageGiftCards)) - return AccessDeniedView(); - - var giftCard = _giftCardService.GetGiftCardById(giftCardId); - if (giftCard == null) - throw new ArgumentException("No gift card found with the specified id"); - - var usageHistoryModel = giftCard.GiftCardUsageHistory.OrderByDescending(gcuh => gcuh.CreatedOnUtc) - .Select(x => - { - return new GiftCardModel.GiftCardUsageHistoryModel() - { - Id = x.Id, - OrderId = x.UsedWithOrderId, - UsedValue = _priceFormatter.FormatPrice(x.UsedValue, true, false), - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) - }; - }) - .ToList(); - var model = new GridModel - { - Data = usageHistoryModel.PagedForCommand(command), - Total = usageHistoryModel.Count - }; + var model = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) + { + var giftCard = _giftCardService.GetGiftCardById(giftCardId); + + var usageHistoryModel = giftCard.GiftCardUsageHistory + .OrderByDescending(gcuh => gcuh.CreatedOnUtc) + .Select(x => + { + return new GiftCardModel.GiftCardUsageHistoryModel() + { + Id = x.Id, + OrderId = x.UsedWithOrderId, + UsedValue = _priceFormatter.FormatPrice(x.UsedValue, true, false), + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) + }; + }) + .ToList(); + + model.Data = usageHistoryModel.PagedForCommand(command); + model.Total = usageHistoryModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/HomeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/HomeController.cs index be044ba9f2..8b0a5ae889 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/HomeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/HomeController.cs @@ -9,8 +9,10 @@ using SmartStore.Core; using SmartStore.Core.Domain.Common; using SmartStore.Services; +using SmartStore.Services.Common; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; namespace SmartStore.Admin.Controllers { @@ -21,15 +23,17 @@ public class HomeController : AdminControllerBase private readonly ICommonServices _services; private readonly CommonSettings _commonSettings; + private readonly Lazy _userAgent; #endregion #region Ctor - public HomeController(ICommonServices services, CommonSettings commonSettings) + public HomeController(ICommonServices services, CommonSettings commonSettings, Lazy userAgent) { this._commonSettings = commonSettings; this._services = services; + this._userAgent = userAgent; } #endregion @@ -46,6 +50,15 @@ public ActionResult About() return View(); } + public ActionResult UaTester(string ua = null) + { + if (ua.HasValue()) + { + _userAgent.Value.RawValue = ua; + } + return View(_userAgent.Value); + } + [ChildActionOnly] public ActionResult SmartStoreNews() { @@ -80,8 +93,9 @@ public ActionResult MarketplaceFeed() try { string url = "http://community.smartstore.com/index.php?/rss/downloads/"; - var request = WebRequest.Create(url); + var request = (HttpWebRequest)WebRequest.Create(url); request.Timeout = 3000; + request.UserAgent = "SmartStore.NET {0}".FormatInvariant(SmartStoreVersion.CurrentFullVersion); using (WebResponse response = request.GetResponse()) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImageBrowserController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImageBrowserController.cs index 84241a23dd..3a61cf6cf8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImageBrowserController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImageBrowserController.cs @@ -1,5 +1,6 @@ using System.IO; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc.UI; //Original code can be found here http://demos.telerik.com/aspnet-mvc/razor/editor/imagetool diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs new file mode 100644 index 0000000000..00a216f5d4 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -0,0 +1,771 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Text; +using System.Web.Mvc; +using SmartStore.Admin.Extensions; +using SmartStore.Admin.Models.DataExchange; +using SmartStore.Core; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.IO; +using SmartStore.Services.Catalog.Importer; +using SmartStore.Services.Customers.Importer; +using SmartStore.Services.DataExchange.Csv; +using SmartStore.Services.DataExchange.Import; +using SmartStore.Services.Helpers; +using SmartStore.Services.Localization; +using SmartStore.Services.Messages.Importer; +using SmartStore.Services.Security; +using SmartStore.Services.Tasks; +using SmartStore.Utilities; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; + +namespace SmartStore.Admin.Controllers +{ + [AdminAuthorize] + public class ImportController : AdminControllerBase + { + private readonly IImportProfileService _importProfileService; + private readonly IDateTimeHelper _dateTimeHelper; + private readonly ITaskScheduler _taskScheduler; + private readonly ILanguageService _languageService; + + public ImportController( + IImportProfileService importService, + IDateTimeHelper dateTimeHelper, + ITaskScheduler taskScheduler, + ILanguageService languageService) + { + _importProfileService = importService; + _dateTimeHelper = dateTimeHelper; + _taskScheduler = taskScheduler; + _languageService = languageService; + } + + #region Utilities + + private bool IsValidImportFile(string path, out string error) + { + error = null; + + try + { + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var unused = LightweightDataTable.FromFile(path, stream, stream.Length, CsvConfiguration.ExcelFriendlyConfiguration, 0, 1); + } + + return true; + } + catch (Exception exception) + { + error = exception.ToAllMessages(); + + FileSystemHelper.Delete(path); + + return false; + } + } + + private bool IsDefaultValueDisabled(string column, string property, string[] disabledFieldNames) + { + if (disabledFieldNames.Contains(property)) + return true; + + string columnWithoutIndex, columnIndex; + if (ColumnMap.ParseSourceName(property, out columnWithoutIndex, out columnIndex)) + return disabledFieldNames.Contains(columnWithoutIndex); + + return false; + } + + private string[] GetDisabledDefaultFieldNames(ImportProfile profile) + { + switch (profile.EntityType) + { + case ImportEntityType.Product: + return new string[] { "Name", "Sku", "ManufacturerPartNumber", "Gtin", "SeName" }; + case ImportEntityType.Category: + return new string[] { "Name", "SeName" }; + case ImportEntityType.Customer: + return new string[] { "CustomerGuid", "Email" }; + case ImportEntityType.NewsLetterSubscription: + return new string[] { "Email" }; + default: + return new string[0]; + } + } + + private string GetPropertyDescription(Dictionary allProperties, string property) + { + if (property.HasValue() && allProperties.ContainsKey(property)) + { + var result = allProperties[property]; + if (result.HasValue()) + return result; + } + return property; + } + + private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile, bool forEdit, ColumnMap invalidMap = null) + { + model.Id = profile.Id; + model.Name = profile.Name; + model.EntityType = profile.EntityType; + model.Enabled = profile.Enabled; + model.Skip = (profile.Skip == 0 ? (int?)null : profile.Skip); + model.Take = (profile.Take == 0 ? (int?)null : profile.Take); + model.UpdateOnly = profile.UpdateOnly; + model.KeyFieldNames = profile.KeyFieldNames.SplitSafe(",").Distinct().ToArray(); + model.ScheduleTaskId = profile.SchedulingTaskId; + model.ScheduleTaskName = profile.ScheduleTask.Name.NaIfEmpty(); + model.IsTaskRunning = profile.ScheduleTask.IsRunning; + model.IsTaskEnabled = profile.ScheduleTask.Enabled; + model.LogFileExists = System.IO.File.Exists(profile.GetImportLogPath()); + model.EntityTypeName = profile.EntityType.GetLocalizedEnum(Services.Localization, Services.WorkContext); + + model.ExistingFileNames = profile.GetImportFiles() + .Select(x => Path.GetFileName(x)) + .ToList(); + + if (profile.ResultInfo.HasValue()) + model.ImportResult = XmlHelper.Deserialize(profile.ResultInfo); + + if (!forEdit) + return; + + CsvConfiguration csvConfiguration = null; + + if (profile.FileType == ImportFileType.CSV) + { + var csvConverter = new CsvConfigurationConverter(); + csvConfiguration = csvConverter.ConvertFrom(profile.FileTypeConfiguration) ?? CsvConfiguration.ExcelFriendlyConfiguration; + + model.CsvConfiguration = new CsvConfigurationModel(csvConfiguration); + } + else + { + csvConfiguration = CsvConfiguration.ExcelFriendlyConfiguration; + } + + // common configuration + var extraData = XmlHelper.Deserialize(profile.ExtraData); + model.ExtraData.NumberOfPictures = extraData.NumberOfPictures; + + // column mapping + model.AvailableSourceColumns = new List(); + model.AvailableEntityProperties = new List(); + model.AvailableKeyFieldNames = new List(); + model.ColumnMappings = new List(); + + try + { + string[] availableKeyFieldNames = null; + string[] disabledDefaultFieldNames = GetDisabledDefaultFieldNames(profile); + var mapConverter = new ColumnMapConverter(); + var storedMap = mapConverter.ConvertFrom(profile.ColumnMapping); + var map = (invalidMap ?? storedMap) ?? new ColumnMap(); + + // property name to localized property name + var allProperties = _importProfileService.GetImportableEntityProperties(profile.EntityType); + + switch (profile.EntityType) + { + case ImportEntityType.Product: + availableKeyFieldNames = ProductImporter.SupportedKeyFields; + break; + case ImportEntityType.Category: + availableKeyFieldNames = CategoryImporter.SupportedKeyFields; + break; + case ImportEntityType.Customer: + availableKeyFieldNames = CustomerImporter.SupportedKeyFields; + break; + case ImportEntityType.NewsLetterSubscription: + availableKeyFieldNames = NewsLetterSubscriptionImporter.SupportedKeyFields; + break; + } + + model.AvailableEntityProperties = allProperties + .Select(x => + { + var mapping = new ColumnMappingItemModel + { + Property = x.Key, + PropertyDescription = x.Value, + IsDefaultDisabled = IsDefaultValueDisabled(x.Key, x.Key, disabledDefaultFieldNames) + }; + + return mapping; + }) + .ToList(); + + model.AvailableKeyFieldNames = availableKeyFieldNames + .Select(x => + { + var item = new SelectListItem { Value = x, Text = x }; + + if (x == "Id") + item.Text = T("Admin.Common.Entity.Fields.Id"); + else if (allProperties.ContainsKey(x)) + item.Text = allProperties[x]; + + return item; + }) + .ToList(); + + model.ColumnMappings = map.Mappings + .Select(x => + { + var mapping = new ColumnMappingItemModel + { + Column = x.Value.MappedName, + Property = x.Key, + Default = x.Value.Default + }; + + if (x.Value.IgnoreProperty) + { + // explicitly ignore the property + mapping.Column = null; + mapping.Default = null; + } + + mapping.PropertyDescription = GetPropertyDescription(allProperties, mapping.Property); + mapping.IsDefaultDisabled = IsDefaultValueDisabled(mapping.Column, mapping.Property, disabledDefaultFieldNames); + + return mapping; + }) + .ToList(); + + var files = profile.GetImportFiles(); + if (!files.Any()) + return; + + var filePath = files.First(); + + using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var dataTable = LightweightDataTable.FromFile(Path.GetFileName(filePath), stream, stream.Length, csvConfiguration, 0, 1); + + foreach (var column in dataTable.Columns.Where(x => x.Name.HasValue())) + { + string columnWithoutIndex, columnIndex; + ColumnMap.ParseSourceName(column.Name, out columnWithoutIndex, out columnIndex); + + model.AvailableSourceColumns.Add(new ColumnMappingItemModel + { + Index = dataTable.Columns.IndexOf(column), + Column = column.Name, + ColumnWithoutIndex = columnWithoutIndex, + ColumnIndex = columnIndex, + PropertyDescription = GetPropertyDescription(allProperties, column.Name) + }); + + // auto map where field equals property name + if (!model.ColumnMappings.Any(x => x.Column == column.Name)) + { + var kvp = allProperties.FirstOrDefault(x => x.Key.IsCaseInsensitiveEqual(column.Name)); + if (kvp.Key.IsEmpty()) + { + var alternativeName = LightweightDataTable.GetAlternativeColumnNameFor(column.Name); + kvp = allProperties.FirstOrDefault(x => x.Key.IsCaseInsensitiveEqual(alternativeName)); + } + + if (kvp.Key.HasValue() && !model.ColumnMappings.Any(x => x.Property == kvp.Key)) + { + model.ColumnMappings.Add(new ColumnMappingItemModel + { + Column = column.Name, + Property = kvp.Key, + PropertyDescription = kvp.Value, + IsDefaultDisabled = IsDefaultValueDisabled(column.Name, kvp.Key, disabledDefaultFieldNames) + }); + } + } + } + + // sorting + model.AvailableSourceColumns = model.AvailableSourceColumns + .OrderBy(x => x.PropertyDescription) + .ToList(); + + model.AvailableEntityProperties = model.AvailableEntityProperties + .OrderBy(x => x.PropertyDescription) + .ToList(); + + model.ColumnMappings = model.ColumnMappings + .OrderBy(x => x.PropertyDescription) + .ToList(); + } + } + catch (Exception exception) + { + NotifyError(exception, true, false); + } + } + + #endregion + + public ActionResult Index() + { + return RedirectToAction("List"); + } + + public ActionResult List() + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + return AccessDeniedView(); + + var model = new ImportProfileListModel + { + Profiles = new List(), + AvailableEntityTypes = ImportEntityType.Product.ToSelectList(false).ToList() + }; + + var profiles = _importProfileService.GetImportProfiles().ToList(); + + foreach (var profile in profiles) + { + var profileModel = new ImportProfileModel(); + + PrepareProfileModel(profileModel, profile, false); + + profileModel.TaskModel = profile.ScheduleTask.ToScheduleTaskModel(Services.Localization, _dateTimeHelper, Url); + + model.Profiles.Add(profileModel); + } + + return View(model); + } + + public ActionResult ProfileListDetails(int profileId) + { + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + { + var profile = _importProfileService.GetImportProfileById(profileId); + if (profile != null) + { + var importResult = XmlHelper.Deserialize(profile.ResultInfo); + + return Json(new + { + importResult = this.RenderPartialViewToString("ProfileImportResult", importResult) + }, + JsonRequestBehavior.AllowGet); + } + } + + return new EmptyResult(); + } + + [HttpPost] + public ActionResult Create(ImportProfileModel model) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + return AccessDeniedView(); + + var importFile = Path.Combine(FileSystemHelper.TempDir(), model.TempFileName.EmptyNull()); + + if (System.IO.File.Exists(importFile)) + { + var profile = _importProfileService.InsertImportProfile(model.TempFileName, model.Name, model.EntityType); + + if (profile != null && profile.Id != 0) + { + var importFileDestination = Path.Combine(profile.GetImportFolder(true, true), model.TempFileName); + + FileSystemHelper.Copy(importFile, importFileDestination, true, true); + + return RedirectToAction("Edit", new { id = profile.Id }); + } + } + else + { + NotifyError(T("Admin.DataExchange.Import.MissingImportFile")); + } + + return RedirectToAction("List"); + } + + public ActionResult Edit(int id) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + return AccessDeniedView(); + + var profile = _importProfileService.GetImportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + var model = new ImportProfileModel(); + PrepareProfileModel(model, profile, true); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCollection form) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + return AccessDeniedView(); + + var profile = _importProfileService.GetImportProfileById(model.Id); + if (profile == null) + return RedirectToAction("List"); + + var map = new ColumnMap(); + var hasErrors = false; + var resetMappings = false; + + try + { + var propertyKeyPrefix = "ColumnMapping.Property."; + var allPropertyKeys = form.AllKeys.Where(x => x.HasValue() && x.StartsWith(propertyKeyPrefix)); + + if (allPropertyKeys.Any()) + { + var entityProperties = _importProfileService.GetImportableEntityProperties(profile.EntityType); + + foreach (var key in allPropertyKeys) + { + var index = key.Substring(propertyKeyPrefix.Length); + var property = form[key]; + var column = form["ColumnMapping.Column." + index]; + var defaultValue = form["ColumnMapping.Default." + index]; + + if (column.IsEmpty()) + { + // tell mapper to explicitly ignore the property + map.AddMapping(property, null, property, "[IGNOREPROPERTY]"); + } + else if (!column.IsCaseInsensitiveEqual(property) || defaultValue.HasValue()) + { + if (defaultValue.HasValue() && GetDisabledDefaultFieldNames(profile).Contains(property)) + defaultValue = null; + + map.AddMapping(property, null, column, defaultValue); + } + } + } + } + catch (Exception exception) + { + hasErrors = true; + NotifyError(exception, true, false); + } + + if (!ModelState.IsValid || hasErrors) + { + PrepareProfileModel(model, profile, true, map); + return View(model); + } + + profile.Name = model.Name; + profile.EntityType = model.EntityType; + profile.Enabled = model.Enabled; + profile.Skip = model.Skip ?? 0; + profile.Take = model.Take ?? 0; + profile.UpdateOnly = model.UpdateOnly; + profile.KeyFieldNames = (model.KeyFieldNames == null ? null : string.Join(",", model.KeyFieldNames)); + + try + { + if (profile.FileType == ImportFileType.CSV && model.CsvConfiguration != null) + { + var csvConverter = new CsvConfigurationConverter(); + + var oldCsvConfig = csvConverter.ConvertFrom(profile.FileTypeConfiguration); + var oldDelimiter = (oldCsvConfig != null ? oldCsvConfig.Delimiter.ToString() : null); + + // auto reset mappings cause they are invalid. note: delimiter can be whitespaced, so no oldDelimter.HasValue() etc. + resetMappings = (oldDelimiter != model.CsvConfiguration.Delimiter && profile.ColumnMapping.HasValue()); + + profile.FileTypeConfiguration = csvConverter.ConvertTo(model.CsvConfiguration.Clone()); + } + else + { + profile.FileTypeConfiguration = null; + } + + if (resetMappings) + { + profile.ColumnMapping = null; + } + else + { + var mapConverter = new ColumnMapConverter(); + profile.ColumnMapping = mapConverter.ConvertTo(map); + } + + if (model.ExtraData != null) + { + var extraData = new ImportExtraData + { + NumberOfPictures = model.ExtraData.NumberOfPictures + }; + + profile.ExtraData = XmlHelper.Serialize(extraData); + } + } + catch (Exception exception) + { + hasErrors = true; + NotifyError(exception, true, false); + } + + if (!hasErrors) + { + _importProfileService.UpdateImportProfile(profile); + + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + + if (resetMappings) + { + NotifyWarning(T("Admin.DataExchange.ColumnMapping.Validate.MappingsReset")); + } + } + + return (continueEditing ? RedirectToAction("Edit", new { id = profile.Id }) : RedirectToAction("List")); + } + + [HttpPost] + public ActionResult ResetColumnMappings(int id) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + return AccessDeniedView(); + + var profile = _importProfileService.GetImportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + profile.ColumnMapping = null; + _importProfileService.UpdateImportProfile(profile); + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + + return RedirectToAction("Edit", new { id = profile.Id }); + } + + [HttpPost, ActionName("Delete")] + public ActionResult DeleteConfirmed(int id) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + return AccessDeniedView(); + + var profile = _importProfileService.GetImportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + try + { + _importProfileService.DeleteImportProfile(profile); + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + + return RedirectToAction("List"); + } + catch (Exception exception) + { + NotifyError(exception); + } + + return RedirectToAction("Edit", new { id = profile.Id }); + } + + [HttpPost] + public JsonResult FileUpload(int id) + { + var success = false; + string error = null; + string tempFile = ""; + + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + { + var postedFile = Request.ToPostedFileResult(); + if (postedFile != null) + { + if (id == 0) + { + var path = Path.Combine(FileSystemHelper.TempDir(), postedFile.FileName); + FileSystemHelper.Delete(path); + + success = postedFile.Stream.ToFile(path); + if (success) + { + success = IsValidImportFile(path, out error); + if (success) + tempFile = postedFile.FileName; + } + } + else + { + var profile = _importProfileService.GetImportProfileById(id); + if (profile != null) + { + var files = profile.GetImportFiles(); + if (files.Any()) + { + var extension = Path.GetExtension(files.First()); + + if (!postedFile.FileExtension.IsCaseInsensitiveEqual(extension)) + error = T("Admin.Common.FileTypeMustEqual", extension.Substring(1).ToUpper()); + } + + if (!error.HasValue()) + { + var folder = profile.GetImportFolder(true, true); + var fileName = Path.GetFileName(postedFile.FileName); + var path = Path.Combine(folder, fileName); + + success = postedFile.Stream.ToFile(path); + if (success) + { + success = IsValidImportFile(path, out error); + if (success) + { + var fileType = (Path.GetExtension(fileName).IsCaseInsensitiveEqual(".xlsx") ? ImportFileType.XLSX : ImportFileType.CSV); + if (fileType != profile.FileType) + { + profile.FileType = fileType; + _importProfileService.UpdateImportProfile(profile); + } + } + } + } + } + } + } + } + else + { + error = T("Admin.AccessDenied.Description"); + } + + if (!success && error.IsEmpty()) + error = T("Admin.Common.UploadFileFailed"); + + if (error.HasValue()) + NotifyError(error); + + return Json(new { success = success, tempFile = tempFile, error = error }); + } + + [HttpPost] + public ActionResult Execute(int id) + { + // permissions checked internally by DataImporter + + var profile = _importProfileService.GetImportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + var taskParams = new Dictionary(); + taskParams.Add(TaskExecutor.CurrentCustomerIdParamName, Services.WorkContext.CurrentCustomer.Id.ToString()); + + _taskScheduler.RunSingleTask(profile.SchedulingTaskId, taskParams); + + NotifyInfo(T("Admin.System.ScheduleTasks.RunNow.Progress.DataImportTask")); + + var referrer = Services.WebHelper.GetUrlReferrer(); + if (referrer.HasValue()) + return Redirect(referrer); + + return RedirectToAction("List"); + } + + public ActionResult DownloadLogFile(int id) + { + if (!Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + return AccessDeniedView(); + + var profile = _importProfileService.GetImportProfileById(id); + if (profile != null) + { + var path = profile.GetImportLogPath(); + if (System.IO.File.Exists(path)) + { + try + { + var stream = new FileStream(path, FileMode.Open); + var result = new FileStreamResult(stream, MediaTypeNames.Text.Plain); + + return result; + } + catch (IOException) + { + NotifyWarning(T("Admin.Common.FileInUse")); + } + } + } + + return RedirectToAction("List"); + } + + public ActionResult DownloadImportFile(int id, string name) + { + string message = null; + + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + { + var profile = _importProfileService.GetImportProfileById(id); + if (profile != null) + { + var path = Path.Combine(profile.GetImportFolder(true), name); + + if (!System.IO.File.Exists(path)) + path = Path.Combine(profile.GetImportFolder(false), name); + + if (System.IO.File.Exists(path)) + { + try + { + var stream = new FileStream(path, FileMode.Open); + + var result = new FileStreamResult(stream, MimeTypes.MapNameToMimeType(path)); + result.FileDownloadName = Path.GetFileName(path); + + return result; + } + catch (IOException) + { + NotifyWarning(T("Admin.Common.FileInUse")); + } + } + } + } + else + { + message = T("Admin.AccessDenied.Description"); + } + + if (message.IsEmpty()) + { + message = T("Admin.Common.ResourceNotFound"); + } + + return File(Encoding.UTF8.GetBytes(message), MediaTypeNames.Text.Plain, "DownloadImportFile.txt"); + } + + [HttpPost] + public ActionResult DeleteImportFile(int id, string name) + { + if (Services.Permissions.Authorize(StandardPermissionProvider.ManageImports)) + { + var profile = _importProfileService.GetImportProfileById(id); + if (profile != null) + { + var importFiles = profile.GetImportFiles(); + var path = Path.Combine(profile.GetImportFolder(true), name); + FileSystemHelper.Delete(path); + } + } + return RedirectToAction("Edit", new { id = id }); + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs index ae6bdad839..dc777ae81e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs @@ -1,98 +1,127 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; -using System.Text; using System.Web.Mvc; using SmartStore.Admin.Models.Localization; -using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Plugins; +using SmartStore.Services; +using SmartStore.Services.Directory; using SmartStore.Services.Localization; using SmartStore.Services.Security; using SmartStore.Services.Stores; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public partial class LanguageController : AdminControllerBase { #region Fields private readonly ILanguageService _languageService; - private readonly ILocalizationService _localizationService; - private readonly IStoreService _storeService; private readonly IStoreMappingService _storeMappingService; - private readonly IPermissionService _permissionService; - private readonly IWebHelper _webHelper; private readonly AdminAreaSettings _adminAreaSettings; private readonly IPluginFinder _pluginFinder; + private readonly ICountryService _countryService; + private readonly ICommonServices _services; #endregion #region Constructors public LanguageController(ILanguageService languageService, - ILocalizationService localizationService, - IStoreService storeService, IStoreMappingService storeMappingService, - IPermissionService permissionService, - IWebHelper webHelper, AdminAreaSettings adminAreaSettings, - IPluginFinder pluginFinder) + IPluginFinder pluginFinder, + ICountryService countryService, + ICommonServices services) { - this._localizationService = localizationService; this._languageService = languageService; - this._storeService = storeService; this._storeMappingService = storeMappingService; - this._permissionService = permissionService; - this._webHelper = webHelper; this._adminAreaSettings = adminAreaSettings; this._pluginFinder = pluginFinder; + this._countryService = countryService; + this._services = services; } - #endregion - #region Utilities + #endregion - [NonAction] - private void PrepareFlagsModel(LanguageModel model) - { - if (model == null) - throw new ArgumentNullException("model"); - - model.FlagFileNames = System.IO.Directory - .EnumerateFiles(_webHelper.MapPath("~/Content/Images/flags/"), "*.png", SearchOption.TopDirectoryOnly) - .Select(System.IO.Path.GetFileName) - .ToList(); - } + #region Utilities - [NonAction] - private void PrepareStoresMappingModel(LanguageModel model, Language language, bool excludeProperties) + private void PrepareLanguageModel(LanguageModel model, Language language, bool excludeProperties) { - if (model == null) - throw new ArgumentNullException("model"); + var languageId = _services.WorkContext.WorkingLanguage.Id; - model.AvailableStores = _storeService - .GetAllStores() - .Select(s => s.ToModel()) + var allCultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures) + .OrderBy(x => x.DisplayName) .ToList(); - if (!excludeProperties) + + var allCountryNames = _countryService.GetAllCountries(true) + .ToDictionarySafe(x => x.TwoLetterIsoCode.EmptyNull().ToLower(), x => x.GetLocalized(y => y.Name, languageId, true, false)); + + model.AvailableCultures = allCultures + .Select(x => new SelectListItem { Text = "{0} [{1}]".FormatInvariant(x.DisplayName, x.IetfLanguageTag), Value = x.IetfLanguageTag }) + .ToList(); + + model.AvailableTwoLetterLanguageCodes = new List(); + model.AvailableFlags = new List(); + + foreach (var item in allCultures) { - if (language != null) + if (!model.AvailableTwoLetterLanguageCodes.Any(x => x.Value.IsCaseInsensitiveEqual(item.TwoLetterISOLanguageName))) { - model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(language); - } - else - { - model.SelectedStoreIds = new int[0]; + // display language name is not provided by net framework + var index = item.DisplayName.EmptyNull().IndexOf(" ("); + + if (index == -1) + index = item.DisplayName.EmptyNull().IndexOf(" ["); + + var displayName = "{0} [{1}]".FormatInvariant( + index == -1 ? item.DisplayName : item.DisplayName.Substring(0, index), + item.TwoLetterISOLanguageName); + + if (item.TwoLetterISOLanguageName.Length == 2) + { + model.AvailableTwoLetterLanguageCodes.Add(new SelectListItem { Text = displayName, Value = item.TwoLetterISOLanguageName }); + } } } + + foreach (var path in Directory.EnumerateFiles(_services.WebHelper.MapPath("~/Content/Images/flags/"), "*.png", SearchOption.TopDirectoryOnly)) + { + var name = Path.GetFileNameWithoutExtension(path).EmptyNull().ToLower(); + string countryDescription = null; + + if (allCountryNames.ContainsKey(name)) + countryDescription = "{0} [{1}]".FormatInvariant(allCountryNames[name], name); + + if (countryDescription.IsEmpty()) + countryDescription = name; + + model.AvailableFlags.Add(new SelectListItem { Text = countryDescription, Value = Path.GetFileName(path) }); + } + + model.AvailableFlags = model.AvailableFlags.OrderBy(x => x.Text).ToList(); + + model.AvailableStores = _services.StoreService.GetAllStores() + .Select(s => s.ToModel()) + .ToList(); + + if (!excludeProperties) + { + model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(language); + } } #endregion @@ -106,58 +135,61 @@ public ActionResult Index() public ActionResult List() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var languages = _languageService.GetAllLanguages(true); + var gridModel = new GridModel { Data = languages.Select(x => x.ToModel()), Total = languages.Count() }; + return View(gridModel); } [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) + { + var languages = _languageService.GetAllLanguages(true); + + model.Data = languages.Select(x => x.ToModel()); + model.Total = languages.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var languages = _languageService.GetAllLanguages(true); - var gridModel = new GridModel - { - Data = languages.Select(x => x.ToModel()), - Total = languages.Count() - }; return new JsonResult { - Data = gridModel + Data = model }; } public ActionResult Create() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var model = new LanguageModel(); - //flags - PrepareFlagsModel(model); - - //Stores - PrepareStoresMappingModel(model, null, false); + PrepareLanguageModel(model, null, false); - //default values - //model.Published = true; return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(LanguageModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); if (ModelState.IsValid) @@ -173,18 +205,14 @@ public ActionResult Create(LanguageModel model, bool continueEditing) foreach (var plugin in plugins) { - _localizationService.ImportPluginResourcesFromXml(plugin, null, false, filterLanguages); + _services.Localization.ImportPluginResourcesFromXml(plugin, null, false, filterLanguages); } - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Languages.Added")); + NotifySuccess(T("Admin.Configuration.Languages.Added")); return continueEditing ? RedirectToAction("Edit", new { id = language.Id }) : RedirectToAction("List"); } - //flags - PrepareFlagsModel(model); - - //Stores - PrepareStoresMappingModel(model, null, true); + PrepareLanguageModel(model, null, true); //If we got this far, something failed, redisplay form return View(model); @@ -192,7 +220,7 @@ public ActionResult Create(LanguageModel model, bool continueEditing) public ActionResult Edit(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var language = _languageService.GetLanguageById(id); @@ -204,19 +232,15 @@ public ActionResult Edit(int id) var model = language.ToModel(); - //flags - PrepareFlagsModel(model); - - //Stores - PrepareStoresMappingModel(model, language, false); + PrepareLanguageModel(model, language, false); return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(LanguageModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var language = _languageService.GetLanguageById(model.Id); @@ -242,15 +266,11 @@ public ActionResult Edit(LanguageModel model, bool continueEditing) _storeMappingService.SaveStoreMappings(language, model.SelectedStoreIds); //notification - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Languages.Updated")); + NotifySuccess(T("Admin.Configuration.Languages.Updated")); return continueEditing ? RedirectToAction("Edit", new { id = language.Id }) : RedirectToAction("List"); } - //flags - PrepareFlagsModel(model); - - //Stores - PrepareStoresMappingModel(model, language, true); + PrepareLanguageModel(model, language, true); //If we got this far, something failed, redisplay form return View(model); @@ -259,7 +279,7 @@ public ActionResult Edit(LanguageModel model, bool continueEditing) [HttpPost] public ActionResult Delete(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var language = _languageService.GetLanguageById(id); @@ -278,7 +298,7 @@ public ActionResult Delete(int id) _languageService.DeleteLanguage(language); //notification - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Languages.Deleted")); + NotifySuccess(T("Admin.Configuration.Languages.Deleted")); return RedirectToAction("List"); } @@ -288,7 +308,7 @@ public ActionResult Delete(int id) public ActionResult Resources(int languageId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); ViewBag.AllLanguages = _languageService.GetAllLanguages(true) @@ -302,7 +322,7 @@ public ActionResult Resources(int languageId) ViewBag.LanguageId = languageId; ViewBag.LanguageName = language.Name; - var resources = _localizationService + var resources = _services.Localization .GetResourceValues(languageId, true) .Where(x => x.Key != "!!___EOF___!!" && x.Value != null) .OrderBy(x => x.Key) @@ -327,31 +347,37 @@ public ActionResult Resources(int languageId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Resources(int languageId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) - return AccessDeniedView(); + var model = new GridModel(); - var language = _languageService.GetLanguageById(languageId); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) + { + var language = _languageService.GetLanguageById(languageId); + + var resources = _services.Localization + .GetResourceValues(languageId, true) + .OrderBy(x => x.Key) + .Where(x => x.Key != "!!___EOF___!!" && x.Value != null) + .Select(x => new LanguageResourceModel + { + LanguageId = languageId, + LanguageName = language.Name, + Id = x.Value.Item1, + Name = x.Key, + Value = x.Value.Item2.EmptyNull(), + }) + .ForCommand(command) + .ToList(); + + model.Data = resources.PagedForCommand(command); + model.Total = resources.Count; + } + else + { + model.Data = Enumerable.Empty(); - var resources = _localizationService - .GetResourceValues(languageId, true) - .OrderBy(x => x.Key) - .Where(x => x.Key != "!!___EOF___!!" && x.Value != null) - .Select(x => new LanguageResourceModel() - { - LanguageId = languageId, - LanguageName = language.Name, - Id = x.Value.Item1, - Name = x.Key, - Value = x.Value.Item2.EmptyNull(), - }) - .ForCommand(command) - .ToList(); + NotifyAccessDenied(); + } - var model = new GridModel - { - Data = resources.PagedForCommand(command), - Total = resources.Count - }; return new JsonResult { Data = model @@ -361,36 +387,36 @@ public ActionResult Resources(int languageId, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult ResourceUpdate(LanguageResourceModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) + { + if (model.Name != null) + model.Name = model.Name.Trim(); + if (model.Value != null) + model.Value = model.Value.Trim(); - if (model.Name != null) - model.Name = model.Name.Trim(); - if (model.Value != null) - model.Value = model.Value.Trim(); + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var resource = _services.Localization.GetLocaleStringResourceById(model.Id); + // if the resourceName changed, ensure it isn't being used by another resource + if (!resource.ResourceName.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase)) + { + var res = _services.Localization.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); + if (res != null && res.Id != resource.Id) + { + return Content(T("Admin.Configuration.Languages.Resources.NameAlreadyExists", res.ResourceName)); + } + } - var resource = _localizationService.GetLocaleStringResourceById(model.Id); - // if the resourceName changed, ensure it isn't being used by another resource - if (!resource.ResourceName.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase)) - { - var res = _localizationService.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); - if (res != null && res.Id != resource.Id) - { - return Content(string.Format(_localizationService.GetResource("Admin.Configuration.Languages.Resources.NameAlreadyExists"), res.ResourceName)); - } - } + resource.ResourceName = model.Name; + resource.ResourceValue = model.Value; + resource.IsTouched = true; - resource.ResourceName = model.Name; - resource.ResourceValue = model.Value; - resource.IsTouched = true; - _localizationService.UpdateLocaleStringResource(resource); + _services.Localization.UpdateLocaleStringResource(resource); + } return Resources(model.LanguageId, command); } @@ -398,47 +424,47 @@ public ActionResult ResourceUpdate(LanguageResourceModel model, GridCommand comm [GridAction(EnableCustomBinding = true)] public ActionResult ResourceAdd(int id, LanguageResourceModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) + { + if (model.Name != null) + model.Name = model.Name.Trim(); + if (model.Value != null) + model.Value = model.Value.Trim(); - if (model.Name != null) - model.Name = model.Name.Trim(); - if (model.Value != null) - model.Value = model.Value.Trim(); + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var res = _services.Localization.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); + if (res == null) + { + var resource = new LocaleStringResource { LanguageId = id }; + resource.ResourceName = model.Name; + resource.ResourceValue = model.Value; + resource.IsTouched = true; + + _services.Localization.InsertLocaleStringResource(resource); + } + else + { + return Content(T("Admin.Configuration.Languages.Resources.NameAlreadyExists", model.Name)); + } + } - var res = _localizationService.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); - if (res == null) - { - var resource = new LocaleStringResource { LanguageId = id }; - resource.ResourceName = model.Name; - resource.ResourceValue = model.Value; - resource.IsTouched = true; - _localizationService.InsertLocaleStringResource(resource); - } - else - { - return Content(string.Format(_localizationService.GetResource("Admin.Configuration.Languages.Resources.NameAlreadyExists"), model.Name)); - } return Resources(id, command); } [GridAction(EnableCustomBinding = true)] public ActionResult ResourceDelete(int id, int languageId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) + { + var resource = _services.Localization.GetLocaleStringResourceById(id); - var resource = _localizationService.GetLocaleStringResourceById(id); - if (resource == null) - throw new ArgumentException("No resource found with the specified id"); - _localizationService.DeleteLocaleStringResource(resource); + _services.Localization.DeleteLocaleStringResource(resource); + } return Resources(languageId, command); } @@ -449,7 +475,7 @@ public ActionResult ResourceDelete(int id, int languageId, GridCommand command) public ActionResult ExportXml(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var language = _languageService.GetLanguageById(id); @@ -458,7 +484,7 @@ public ActionResult ExportXml(int id) try { - var xml = _localizationService.ExportResourcesToXml(language); + var xml = _services.Localization.ExportResourcesToXml(language); return new XmlDownloadResult(xml, "language-pack-{0}.xml".FormatInvariant(language.UniqueSeoCode)); } catch (Exception exc) @@ -471,7 +497,7 @@ public ActionResult ExportXml(int id) [HttpPost] public ActionResult ImportXml(int id, FormCollection form, ImportModeFlags mode, bool updateTouched) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var language = _languageService.GetLanguageById(id); @@ -486,20 +512,15 @@ public ActionResult ImportXml(int id, FormCollection form, ImportModeFlags mode, var file = Request.Files["importxmlfile"]; if (file != null && file.ContentLength > 0) { - using (var sr = new StreamReader(file.InputStream, Encoding.UTF8)) - { - string content = sr.ReadToEnd(); - _localizationService.ImportResourcesFromXml(language, content, mode: mode, updateTouchedResources: updateTouched); - } - + _services.Localization.ImportResourcesFromXml(language, file.InputStream.AsString(), mode: mode, updateTouchedResources: updateTouched); } else { - NotifyError(_localizationService.GetResource("Admin.Common.UploadFile")); + NotifyError(T("Admin.Common.UploadFile")); return RedirectToAction("Edit", new { id = language.Id }); } - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Languages.Imported")); + NotifySuccess(T("Admin.Configuration.Languages.Imported")); return RedirectToAction("Edit", new { id = language.Id }); } catch (Exception exc) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/LogController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/LogController.cs index f8ea714e11..3d0129a164 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/LogController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/LogController.cs @@ -11,6 +11,8 @@ using SmartStore.Services.Security; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -53,8 +55,8 @@ public ActionResult List() return AccessDeniedView(); var model = new LogListModel(); - model.AvailableLogLevels = LogLevel.Debug.ToSelectList(false).ToList(); - model.AvailableLogLevels.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + model.AvailableLogLevels = LogLevel.Debug.ToSelectList(false).ToList(); return View(model); } @@ -62,49 +64,55 @@ public ActionResult List() [GridAction(EnableCustomBinding = true)] public ActionResult LogList(GridCommand command, LogListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSystemLog)) - return AccessDeniedView(); - - DateTime? createdOnFromValue = (model.CreatedOnFrom == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnFrom.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? createdToFromValue = (model.CreatedOnTo == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - LogLevel? logLevel = model.LogLevelId > 0 ? (LogLevel?)(model.LogLevelId) : null; - - - var logItems = Logger.GetAllLogs(createdOnFromValue, createdToFromValue, model.Message, - logLevel, command.Page - 1, command.PageSize, model.MinFrequency); - - var gridModel = new GridModel - { - Data = logItems.Select(x => - { - var logModel = new LogModel() - { - Id = x.Id, - LogLevelHint = s_logLevelHintMap[x.LogLevel], - LogLevel = x.LogLevel.GetLocalizedEnum(_localizationService, _workContext), - ShortMessage = x.ShortMessage, - FullMessage = x.FullMessage, - IpAddress = x.IpAddress, - CustomerId = x.CustomerId, - CustomerEmail = x.Customer != null ? x.Customer.Email : null, - PageUrl = x.PageUrl, - ReferrerUrl = x.ReferrerUrl, - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageSystemLog)) + { + DateTime? createdOnFromValue = (model.CreatedOnFrom == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnFrom.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? createdToFromValue = (model.CreatedOnTo == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + LogLevel? logLevel = model.LogLevelId > 0 ? (LogLevel?)(model.LogLevelId) : null; + + var logItems = Logger.GetAllLogs(createdOnFromValue, createdToFromValue, model.Message, + logLevel, command.Page - 1, command.PageSize, model.MinFrequency); + + gridModel.Data = logItems.Select(x => + { + var logModel = new LogModel + { + Id = x.Id, + LogLevelHint = s_logLevelHintMap[x.LogLevel], + LogLevel = x.LogLevel.GetLocalizedEnum(_localizationService, _workContext), + ShortMessage = x.ShortMessage, + FullMessage = x.FullMessage, + IpAddress = x.IpAddress, + CustomerId = x.CustomerId, + CustomerEmail = x.Customer != null ? x.Customer.Email : null, + PageUrl = x.PageUrl, + ReferrerUrl = x.ReferrerUrl, + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), Frequency = x.Frequency, ContentHash = x.ContentHash - }; + }; if (x.UpdatedOnUtc.HasValue) logModel.UpdatedOn = _dateTimeHelper.ConvertToUserTime(x.UpdatedOnUtc.Value, DateTimeKind.Utc); return logModel; - }), - Total = logItems.TotalCount - }; + }); + + gridModel.Total = logItems.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs index 12817a0890..62ea281f19 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs @@ -2,12 +2,14 @@ using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Catalog; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Logging; using SmartStore.Services.Catalog; -using SmartStore.Services.ExportImport; +using SmartStore.Services.Common; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; @@ -16,12 +18,13 @@ using SmartStore.Services.Stores; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public partial class ManufacturerController : AdminControllerBase { #region Fields @@ -37,7 +40,6 @@ public partial class ManufacturerController : AdminControllerBase private readonly ILanguageService _languageService; private readonly ILocalizationService _localizationService; private readonly ILocalizedEntityService _localizedEntityService; - private readonly IExportManager _exportManager; private readonly IWorkContext _workContext; private readonly ICustomerActivityService _customerActivityService; private readonly IPermissionService _permissionService; @@ -45,19 +47,20 @@ public partial class ManufacturerController : AdminControllerBase private readonly AdminAreaSettings _adminAreaSettings; private readonly CatalogSettings _catalogSettings; - #endregion - - #region Constructors + #endregion - public ManufacturerController(ICategoryService categoryService, IManufacturerService manufacturerService, + #region Constructors + + public ManufacturerController(ICategoryService categoryService, IManufacturerService manufacturerService, IManufacturerTemplateService manufacturerTemplateService, IProductService productService, IStoreService storeService, IStoreMappingService storeMappingService, IUrlRecordService urlRecordService, IPictureService pictureService, ILanguageService languageService, ILocalizationService localizationService, ILocalizedEntityService localizedEntityService, - IExportManager exportManager, IWorkContext workContext, + IWorkContext workContext, ICustomerActivityService customerActivityService, IPermissionService permissionService, IDateTimeHelper dateTimeHelper, - AdminAreaSettings adminAreaSettings, CatalogSettings catalogSettings) + AdminAreaSettings adminAreaSettings, + CatalogSettings catalogSettings) { this._categoryService = categoryService; this._manufacturerTemplateService = manufacturerTemplateService; @@ -70,14 +73,13 @@ public ManufacturerController(ICategoryService categoryService, IManufacturerSer this._languageService = languageService; this._localizationService = localizationService; this._localizedEntityService = localizedEntityService; - this._exportManager = exportManager; this._workContext = workContext; this._customerActivityService = customerActivityService; this._permissionService = permissionService; this._dateTimeHelper = dateTimeHelper; this._adminAreaSettings = adminAreaSettings; this._catalogSettings = catalogSettings; - } + } #endregion @@ -150,6 +152,8 @@ private void PrepareManufacturerModel(ManufacturerModel model, Manufacturer manu if (model == null) throw new ArgumentNullException("model"); + model.GridPageSize = _adminAreaSettings.GridPageSize; + model.AvailableStores = _storeService .GetAllStores() .Select(s => s.ToModel()) @@ -182,6 +186,7 @@ private void PrepareManufacturerModel(ManufacturerModel model, Manufacturer manu public ActionResult AllManufacturers(string label, int selectedId) { var manufacturers = _manufacturerService.GetAllManufacturers(true); + if (label.HasValue()) { manufacturers.Insert(0, new Manufacturer { Name = label, Id = 0 }); @@ -195,7 +200,39 @@ public ActionResult AllManufacturers(string label, int selectedId) selected = m.Id == selectedId }; - return new JsonResult { Data = list.ToList(), JsonRequestBehavior = JsonRequestBehavior.AllowGet }; + var data = list.ToList(); + + var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedManufacturers), + _catalogSettings.MostRecentlyUsedManufacturersMaxSize); + + // TODO: insert disabled option separator (select2 v.3.4.2 or higher required) + //if (mru.Count > 0) + //{ + // data.Insert(0, new + // { + // id = "", + // text = "----------------------", + // selected = false, + // disabled = true + // }); + //} + + for (int i = mru.Count - 1; i >= 0; --i) + { + string id = mru[i]; + var item = manufacturers.FirstOrDefault(x => x.Id.ToString() == id); + if (item != null) + { + data.Insert(0, new + { + id = id, + text = item.Name, + selected = false + }); + } + } + + return new JsonResult { Data = data, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; } public ActionResult Index() @@ -208,29 +245,38 @@ public ActionResult List() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - var model = new ManufacturerListModel(); - var manufacturers = _manufacturerService.GetAllManufacturers(null, 0, _adminAreaSettings.GridPageSize, true); - model.Manufacturers = new GridModel - { - Data = manufacturers.Select(x => x.ToModel()), - Total = manufacturers.TotalCount - }; - return View(model); + var model = new ManufacturerListModel + { + GridPageSize = _adminAreaSettings.GridPageSize + }; + + model.AvailableStores = _storeService.GetAllStores().ToSelectListItems(); + + return View(model); } [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, ManufacturerListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + model.AvailableStores = _storeService.GetAllStores().ToSelectListItems(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var manufacturers = _manufacturerService.GetAllManufacturers(model.SearchManufacturerName, command.Page - 1, command.PageSize, + model.SearchStoreId, true); + + gridModel.Data = manufacturers.Select(x => x.ToModel()); + gridModel.Total = manufacturers.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var manufacturers = _manufacturerService.GetAllManufacturers(model.SearchManufacturerName, - command.Page - 1, command.PageSize, true); - var gridModel = new GridModel - { - Data = manufacturers.Select(x => x.ToModel()), - Total = manufacturers.TotalCount - }; return new JsonResult { Data = gridModel @@ -264,7 +310,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(ManufacturerModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -275,6 +321,8 @@ public ActionResult Create(ManufacturerModel model, bool continueEditing) var manufacturer = model.ToEntity(); manufacturer.CreatedOnUtc = DateTime.UtcNow; manufacturer.UpdatedOnUtc = DateTime.UtcNow; + + MediaHelper.UpdatePictureTransientStateFor(manufacturer, m => m.PictureId); _manufacturerService.InsertManufacturer(manufacturer); @@ -333,7 +381,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(ManufacturerModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -345,10 +393,9 @@ public ActionResult Edit(ManufacturerModel model, bool continueEditing) if (ModelState.IsValid) { - int prevPictureId = manufacturer.PictureId.GetValueOrDefault(); manufacturer = model.ToEntity(manufacturer); + MediaHelper.UpdatePictureTransientStateFor(manufacturer, m => m.PictureId); manufacturer.UpdatedOnUtc = DateTime.UtcNow; - _manufacturerService.UpdateManufacturer(manufacturer); //search engine name @@ -358,14 +405,6 @@ public ActionResult Edit(ManufacturerModel model, bool continueEditing) //locales UpdateLocales(manufacturer, model); - //delete an old picture (if deleted or updated) - if (prevPictureId > 0 && prevPictureId != manufacturer.PictureId.GetValueOrDefault()) - { - var prevPicture = _pictureService.GetPictureById(prevPictureId); - if (prevPicture != null) - _pictureService.DeletePicture(prevPicture); - } - //update picture seo file name UpdatePictureSeoNames(manufacturer); @@ -409,62 +448,48 @@ public ActionResult Delete(int id) #endregion - #region Export / Import - - public ActionResult ExportXml() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - try - { - var manufacturers = _manufacturerService.GetAllManufacturers(true); - var xml = _exportManager.ExportManufacturersToXml(manufacturers); - return new XmlDownloadResult(xml, "manufacturers.xml"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } - } - - #endregion - #region Products [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductList(GridCommand command, int manufacturerId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var productManufacturers = _manufacturerService.GetProductManufacturersByManufacturerId(manufacturerId, - command.Page - 1, command.PageSize, true); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productManufacturers = _manufacturerService.GetProductManufacturersByManufacturerId(manufacturerId, command.Page - 1, command.PageSize, true); + + var productIds = productManufacturers.Select(x => x.ProductId).ToArray(); + var products = _productService.GetProductsByIds(productIds); + + model.Data = productManufacturers + .Select(x => + { + var product = products.FirstOrDefault(y => y.Id == x.ProductId); + + return new ManufacturerModel.ManufacturerProductModel + { + Id = x.Id, + ManufacturerId = x.ManufacturerId, + ProductId = x.ProductId, + ProductName = product.Name, + Sku = product.Sku, + ProductTypeName = product.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = product.ProductTypeLabelHint, + Published = product.Published, + IsFeaturedProduct = x.IsFeaturedProduct, + DisplayOrder1 = x.DisplayOrder + }; + }); + + model.Total = productManufacturers.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); - var model = new GridModel - { - Data = productManufacturers - .Select(x => - { - var product = _productService.GetProductById(x.ProductId); - - return new ManufacturerModel.ManufacturerProductModel() - { - Id = x.Id, - ManufacturerId = x.ManufacturerId, - ProductId = x.ProductId, - ProductName = product.Name, - Sku = product.Sku, - ProductTypeName = product.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = product.ProductTypeLabelHint, - Published = product.Published, - IsFeaturedProduct = x.IsFeaturedProduct, - DisplayOrder1 = x.DisplayOrder - }; - }), - Total = productManufacturers.TotalCount - }; + NotifyAccessDenied(); + } return new JsonResult { @@ -475,16 +500,15 @@ public ActionResult ProductList(GridCommand command, int manufacturerId) [GridAction(EnableCustomBinding = true)] public ActionResult ProductUpdate(GridCommand command, ManufacturerModel.ManufacturerProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var productManufacturer = _manufacturerService.GetProductManufacturerById(model.Id); - var productManufacturer = _manufacturerService.GetProductManufacturerById(model.Id); - if (productManufacturer == null) - throw new ArgumentException("No product manufacturer mapping found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + productManufacturer.IsFeaturedProduct = model.IsFeaturedProduct; + productManufacturer.DisplayOrder = model.DisplayOrder1; - productManufacturer.IsFeaturedProduct = model.IsFeaturedProduct; - productManufacturer.DisplayOrder = model.DisplayOrder1; - _manufacturerService.UpdateProductManufacturer(productManufacturer); + _manufacturerService.UpdateProductManufacturer(productManufacturer); + } return ProductList(command, productManufacturer.ManufacturerId); } @@ -492,141 +516,56 @@ public ActionResult ProductUpdate(GridCommand command, ManufacturerModel.Manufac [GridAction(EnableCustomBinding = true)] public ActionResult ProductDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productManufacturer = _manufacturerService.GetProductManufacturerById(id); - if (productManufacturer == null) - throw new ArgumentException("No product manufacturer mapping found with the specified id"); + var productManufacturer = _manufacturerService.GetProductManufacturerById(id); + var manufacturerId = productManufacturer.ManufacturerId; - var manufacturerId = productManufacturer.ManufacturerId; - _manufacturerService.DeleteProductManufacturer(productManufacturer); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _manufacturerService.DeleteProductManufacturer(productManufacturer); + } return ProductList(command, manufacturerId); - } - - public ActionResult ProductAddPopup(int manufacturerId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var ctx = new ProductSearchContext(); - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageSize = _adminAreaSettings.GridPageSize; - ctx.ShowHidden = true; + } - var products = _productService.SearchProducts(ctx); - - var model = new ManufacturerModel.AddManufacturerProductModel(); - model.Products = new GridModel + [HttpPost] + public ActionResult ProductAdd(int manufacturerId, string selectedProductIds) + { + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - Data = products.Select(x => - { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - - return productModel; - }), - Total = products.TotalCount - }; - - //categories - var allCategories = _categoryService.GetAllCategories(showHidden: true); - var mappedCategories = allCategories.ToDictionary(x => x.Id); - foreach (var c in allCategories) - { - model.AvailableCategories.Add(new SelectListItem() { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); - } - - //manufacturers - foreach (var m in _manufacturerService.GetAllManufacturers(true)) - { - model.AvailableManufacturers.Add(new SelectListItem() { Text = m.Name, Value = m.Id.ToString() }); - } - - //product types - model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - return View(model); - } - - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult ProductAddPopupList(GridCommand command, ManufacturerModel.AddManufacturerProductModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var gridModel = new GridModel(); - - var ctx = new ProductSearchContext(); - - if (model.SearchCategoryId > 0) - ctx.CategoryIds.Add(model.SearchCategoryId); - - ctx.ManufacturerId = model.SearchManufacturerId; - ctx.Keywords = model.SearchProductName; - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageIndex = command.Page - 1; - ctx.PageSize = command.PageSize; - ctx.ShowHidden = true; - ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; - - var products = _productService.SearchProducts(ctx); + var productIds = selectedProductIds.SplitSafe(",").Select(x => x.ToInt()).ToArray(); + var products = _productService.GetProductsByIds(productIds); + ProductManufacturer productManu = null; + var maxDisplayOrder = -1; - gridModel.Data = products.Select(x => + foreach (var product in products) + { + var existingProductManus = _manufacturerService.GetProductManufacturersByManufacturerId(manufacturerId, 0, int.MaxValue, true); + + if (!existingProductManus.Any(x => x.ProductId == product.Id && x.ManufacturerId == manufacturerId)) + { + if (maxDisplayOrder == -1 && (productManu = existingProductManus.OrderByDescending(x => x.DisplayOrder).FirstOrDefault()) != null) + { + maxDisplayOrder = productManu.DisplayOrder; + } + + _manufacturerService.InsertProductManufacturer(new ProductManufacturer + { + ManufacturerId = manufacturerId, + ProductId = product.Id, + IsFeaturedProduct = false, + DisplayOrder = ++maxDisplayOrder + }); + } + } + } + else { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - - return productModel; - }); - gridModel.Total = products.TotalCount; - return new JsonResult - { - Data = gridModel - }; - } - - [HttpPost] - [FormValueRequired("save")] - public ActionResult ProductAddPopup(string btnId, string formId, ManufacturerModel.AddManufacturerProductModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - if (model.SelectedProductIds != null) - { - foreach (int id in model.SelectedProductIds) - { - var product = _productService.GetProductById(id); - if (product != null) - { - var existingProductmanufacturers = _manufacturerService.GetProductManufacturersByManufacturerId(model.ManufacturerId, 0, int.MaxValue, true); - if (existingProductmanufacturers.FindProductManufacturer(id, model.ManufacturerId) == null) - { - _manufacturerService.InsertProductManufacturer( - new ProductManufacturer() - { - ManufacturerId = model.ManufacturerId, - ProductId = id, - IsFeaturedProduct = false, - DisplayOrder = 1 - }); - } - } - } - } + NotifyAccessDenied(); + } - ViewBag.RefreshPage = true; - ViewBag.btnId = btnId; - ViewBag.formId = formId; - model.Products = new GridModel(); - return View(model); - } + return new EmptyResult(); + } - #endregion - } + #endregion + } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/MeasureController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/MeasureController.cs index 5b897b0974..f8b33e6102 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/MeasureController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/MeasureController.cs @@ -9,6 +9,7 @@ using SmartStore.Services.Security; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -78,20 +79,29 @@ public ActionResult Weights(string id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Weights(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); + var model = new GridModel(); - var weightsModel = _measureService.GetAllMeasureWeights() - .Select(x => x.ToModel()) - .ForCommand(command) - .ToList(); - foreach (var wm in weightsModel) - wm.IsPrimaryWeight = wm.Id == _measureSettings.BaseWeightId; - var model = new GridModel - { - Data = weightsModel, - Total = weightsModel.Count - }; + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + var weightsModel = _measureService.GetAllMeasureWeights() + .Select(x => x.ToModel()) + .ForCommand(command) + .ToList(); + + foreach (var wm in weightsModel) + { + wm.IsPrimaryWeight = wm.Id == _measureSettings.BaseWeightId; + } + + model.Data = weightsModel; + model.Total = weightsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -99,22 +109,22 @@ public ActionResult Weights(GridCommand command) }; } - [GridAction(EnableCustomBinding=true)] + [GridAction(EnableCustomBinding = true)] public ActionResult WeightUpdate(MeasureWeightModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); - - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - var weight = _measureService.GetMeasureWeightById(model.Id); - weight = model.ToEntity(weight); - _measureService.UpdateMeasureWeight(weight); + var weight = _measureService.GetMeasureWeightById(model.Id); + weight = model.ToEntity(weight); + + _measureService.UpdateMeasureWeight(weight); + } return Weights(command); } @@ -122,19 +132,19 @@ public ActionResult WeightUpdate(MeasureWeightModel model, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult WeightAdd([Bind(Exclude="Id")] MeasureWeightModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var weight = new MeasureWeight(); + weight = model.ToEntity(weight); - var weight = new MeasureWeight(); - weight = model.ToEntity(weight); - _measureService.InsertMeasureWeight(weight); + _measureService.InsertMeasureWeight(weight); + } return Weights(command); } @@ -142,17 +152,17 @@ public ActionResult WeightAdd([Bind(Exclude="Id")] MeasureWeightModel model, Gri [GridAction(EnableCustomBinding = true)] public ActionResult WeightDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); - - var weight = _measureService.GetMeasureWeightById(id); - if (weight == null) - throw new ArgumentException("No weight found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + var weight = _measureService.GetMeasureWeightById(id); - if (weight.Id == _measureSettings.BaseWeightId) - return Content(_localizationService.GetResource("Admin.Configuration.Measures.Weights.CantDeletePrimary")); + if (weight.Id == _measureSettings.BaseWeightId) + { + return Content(T("Admin.Configuration.Measures.Weights.CantDeletePrimary")); + } - _measureService.DeleteMeasureWeight(weight); + _measureService.DeleteMeasureWeight(weight); + } return Weights(command); } @@ -194,20 +204,29 @@ public ActionResult Dimensions(string id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Dimensions(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); + var model = new GridModel(); - var dimensionsModel = _measureService.GetAllMeasureDimensions() - .Select(x => x.ToModel()) - .ForCommand(command) - .ToList(); - foreach (var wm in dimensionsModel) - wm.IsPrimaryDimension = wm.Id == _measureSettings.BaseDimensionId; - var model = new GridModel - { - Data = dimensionsModel, - Total = dimensionsModel.Count - }; + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + var dimensionsModel = _measureService.GetAllMeasureDimensions() + .Select(x => x.ToModel()) + .ForCommand(command) + .ToList(); + + foreach (var wm in dimensionsModel) + { + wm.IsPrimaryDimension = wm.Id == _measureSettings.BaseDimensionId; + } + + model.Data = dimensionsModel; + model.Total = dimensionsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -218,19 +237,19 @@ public ActionResult Dimensions(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult DimensionUpdate(MeasureDimensionModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var dimension = _measureService.GetMeasureDimensionById(model.Id); + dimension = model.ToEntity(dimension); - var dimension = _measureService.GetMeasureDimensionById(model.Id); - dimension = model.ToEntity(dimension); - _measureService.UpdateMeasureDimension(dimension); + _measureService.UpdateMeasureDimension(dimension); + } return Dimensions(command); } @@ -238,19 +257,19 @@ public ActionResult DimensionUpdate(MeasureDimensionModel model, GridCommand com [GridAction(EnableCustomBinding = true)] public ActionResult DimensionAdd([Bind(Exclude="Id")] MeasureDimensionModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var dimension = new MeasureDimension(); + dimension = model.ToEntity(dimension); - var dimension = new MeasureDimension(); - dimension = model.ToEntity(dimension); - _measureService.InsertMeasureDimension(dimension); + _measureService.InsertMeasureDimension(dimension); + } return Dimensions(command); } @@ -258,17 +277,17 @@ public ActionResult DimensionAdd([Bind(Exclude="Id")] MeasureDimensionModel mode [GridAction(EnableCustomBinding = true)] public ActionResult DimensionDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); - - var dimension = _measureService.GetMeasureDimensionById(id); - if (dimension == null) - throw new ArgumentException("No dimension found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + var dimension = _measureService.GetMeasureDimensionById(id); - if (dimension.Id == _measureSettings.BaseDimensionId) - return Content(_localizationService.GetResource("Admin.Configuration.Measures.Dimensions.CantDeletePrimary")); + if (dimension.Id == _measureSettings.BaseDimensionId) + { + return Content(T("Admin.Configuration.Measures.Dimensions.CantDeletePrimary")); + } - _measureService.DeleteMeasureDimension(dimension); + _measureService.DeleteMeasureDimension(dimension); + } return Dimensions(command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/MediaController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/MediaController.cs index ceac9c3ccc..b55656e0cc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/MediaController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/MediaController.cs @@ -12,7 +12,6 @@ namespace SmartStore.Admin.Controllers public partial class MediaController : AdminControllerBase { - private static readonly Regex s_allowedImageTypes = new Regex(@"(.*?)\.(gif|jpg|jpeg|png|bmp|ico|svg)", RegexOptions.Compiled | RegexOptions.IgnoreCase); private readonly IPermissionService _permissionService; private readonly IWebHelper _webHelper; @@ -51,46 +50,34 @@ public ActionResult UploadImageAjax() private UploadFileResult UploadImageInternal() { - if (Request.Files.Count == 0) + var postedFile = Request.ToPostedFileResult(); + if (postedFile == null) { - return new UploadFileResult { Message = "No file uploaded" }; + return new UploadFileResult { Message = T("Common.NoFileUploaded") }; } - var uploadFile = Request.Files[0]; - if (uploadFile == null) - { - return new UploadFileResult { Message = "No file name provided" }; - } - - var fileName = Path.GetFileName(uploadFile.FileName); - if (fileName.IsEmpty()) + if (postedFile.FileName.IsEmpty()) { return new UploadFileResult { Message = "No file name provided" }; } var directory = "~/Media/Uploaded/"; - var filePath = Path.Combine(_webHelper.MapPath(directory), fileName); + var filePath = Path.Combine(_webHelper.MapPath(directory), postedFile.FileName); - if (!IsAllowedImageType(fileName)) + if (!!postedFile.IsImage) { - return new UploadFileResult { Message = "Files with extension '{0}' cannot be uploaded".FormatInvariant(Path.GetExtension(filePath)) }; + return new UploadFileResult { Message = "Files with extension '{0}' cannot be uploaded".FormatInvariant(postedFile.FileExtension) }; } - uploadFile.SaveAs(filePath); + postedFile.File.SaveAs(filePath); return new UploadFileResult { Success = true, - Url = this.Url.Content(string.Format("{0}{1}", directory, fileName)) + Url = this.Url.Content(string.Format("{0}{1}", directory, postedFile.FileName)) }; } - [NonAction] - protected virtual bool IsAllowedImageType(string path) - { - return s_allowedImageTypes.IsMatch(path); - } - public class UploadFileResult { public bool Success { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/MessageTemplateController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/MessageTemplateController.cs index c2efaf8bc3..e3a63e295d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/MessageTemplateController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/MessageTemplateController.cs @@ -5,10 +5,13 @@ using SmartStore.Collections; using SmartStore.Core.Domain.Messages; using SmartStore.Services.Localization; +using SmartStore.Services.Media; using SmartStore.Services.Messages; using SmartStore.Services.Security; using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -91,29 +94,22 @@ public void UpdateLocales(MessageTemplate mt, MessageTemplateModel model) { foreach (var localized in model.Locales) { - _localizedEntityService.SaveLocalizedValue(mt, - x => x.BccEmailAddresses, - localized.BccEmailAddresses, - localized.LanguageId); - - _localizedEntityService.SaveLocalizedValue(mt, - x => x.Subject, - localized.Subject, - localized.LanguageId); - - _localizedEntityService.SaveLocalizedValue(mt, - x => x.Body, - localized.Body, - localized.LanguageId); - - _localizedEntityService.SaveLocalizedValue(mt, - x => x.EmailAccountId, - localized.EmailAccountId, - localized.LanguageId); + int lid = localized.LanguageId; + + MediaHelper.UpdateDownloadTransientState(mt.GetLocalized(x => x.Attachment1FileId, lid, false, false), localized.Attachment1FileId, true); + MediaHelper.UpdateDownloadTransientState(mt.GetLocalized(x => x.Attachment2FileId, lid, false, false), localized.Attachment2FileId, true); + MediaHelper.UpdateDownloadTransientState(mt.GetLocalized(x => x.Attachment3FileId, lid, false, false), localized.Attachment3FileId, true); + + _localizedEntityService.SaveLocalizedValue(mt, x => x.BccEmailAddresses, localized.BccEmailAddresses, lid); + _localizedEntityService.SaveLocalizedValue(mt, x => x.Subject, localized.Subject, lid); + _localizedEntityService.SaveLocalizedValue(mt, x => x.Body, localized.Body, lid); + _localizedEntityService.SaveLocalizedValue(mt, x => x.EmailAccountId, localized.EmailAccountId, lid); + _localizedEntityService.SaveLocalizedValue(mt, x => x.Attachment1FileId, localized.Attachment1FileId, lid); + _localizedEntityService.SaveLocalizedValue(mt, x => x.Attachment2FileId, localized.Attachment2FileId, lid); + _localizedEntityService.SaveLocalizedValue(mt, x => x.Attachment3FileId, localized.Attachment3FileId, lid); } } - [NonAction] private void PrepareStoresMappingModel(MessageTemplateModel model, MessageTemplate messageTemplate, bool excludeProperties) { @@ -153,10 +149,10 @@ public ActionResult List() var model = new MessageTemplateListModel(); - //stores - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); foreach (var s in _storeService.GetAllStores()) - model.AvailableStores.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString() }); + { + model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); + } return View(model); } @@ -164,15 +160,22 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, MessageTemplateListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMessageTemplates)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var messageTemplates = _messageTemplateService.GetAllMessageTemplates(model.SearchStoreId); - var gridModel = new GridModel + if (_permissionService.Authorize(StandardPermissionProvider.ManageMessageTemplates)) { - Data = messageTemplates.Select(x => x.ToModel()), - Total = messageTemplates.Count - }; + var messageTemplates = _messageTemplateService.GetAllMessageTemplates(model.SearchStoreId); + + gridModel.Data = messageTemplates.Select(x => x.ToModel()); + gridModel.Total = messageTemplates.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel @@ -192,19 +195,24 @@ public ActionResult Edit(int id) FillTokensTree(model.TokensTree, _messageTokenProvider.GetListOfAllowedTokens()); - //available email accounts - foreach (var ea in _emailAccountService.GetAllEmailAccounts()) - model.AvailableEmailAccounts.Add(ea.ToModel()); + // available email accounts + foreach (var ea in _emailAccountService.GetAllEmailAccounts()) + { + model.AvailableEmailAccounts.Add(ea.ToModel()); + } - //Store + // Store PrepareStoresMappingModel(model, messageTemplate, false); - //locales + // locales AddLocales(_languageService, model.Locales, (locale, languageId) => { locale.BccEmailAddresses = messageTemplate.GetLocalized(x => x.BccEmailAddresses, languageId, false, false); locale.Subject = messageTemplate.GetLocalized(x => x.Subject, languageId, false, false); locale.Body = messageTemplate.GetLocalized(x => x.Body, languageId, false, false); + locale.Attachment1FileId = messageTemplate.GetLocalized(x => x.Attachment1FileId, languageId, false, false); + locale.Attachment2FileId = messageTemplate.GetLocalized(x => x.Attachment2FileId, languageId, false, false); + locale.Attachment3FileId = messageTemplate.GetLocalized(x => x.Attachment3FileId, languageId, false, false); var emailAccountId = messageTemplate.GetLocalized(x => x.EmailAccountId, languageId, false, false); locale.EmailAccountId = emailAccountId > 0 ? emailAccountId : _emailAccountSettings.DefaultEmailAccountId; @@ -213,7 +221,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [FormValueRequired("save", "save-continue")] public ActionResult Edit(MessageTemplateModel model, bool continueEditing) { @@ -227,12 +235,17 @@ public ActionResult Edit(MessageTemplateModel model, bool continueEditing) if (ModelState.IsValid) { messageTemplate = model.ToEntity(messageTemplate); + + MediaHelper.UpdateDownloadTransientStateFor(messageTemplate, x => x.Attachment1FileId); + MediaHelper.UpdateDownloadTransientStateFor(messageTemplate, x => x.Attachment2FileId); + MediaHelper.UpdateDownloadTransientStateFor(messageTemplate, x => x.Attachment3FileId); + _messageTemplateService.UpdateMessageTemplate(messageTemplate); - //Stores + // Stores _storeMappingService.SaveStoreMappings(messageTemplate, model.SelectedStoreIds); - //locales + // locales UpdateLocales(messageTemplate, model); NotifySuccess(_localizationService.GetResource("Admin.ContentManagement.MessageTemplates.Updated")); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsController.cs index da32d65850..9cf0e59ae5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsController.cs @@ -14,6 +14,8 @@ using SmartStore.Services.Stores; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -102,10 +104,11 @@ public ActionResult List() return AccessDeniedView(); var model = new NewsItemListModel(); - //stores - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + foreach (var s in _storeService.GetAllStores()) - model.AvailableStores.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString() }); + { + model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); + } return View(model); } @@ -113,26 +116,38 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, NewsItemListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNews)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageNews)) + { + var news = _newsService.GetAllNews(0, model.SearchStoreId, command.Page - 1, command.PageSize, true); + + gridModel.Data = news.Select(x => + { + var m = x.ToModel(); + + if (x.StartDateUtc.HasValue) + m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); + + if (x.EndDateUtc.HasValue) + m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); + + m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + m.LanguageName = x.Language.Name; + m.Comments = x.ApprovedCommentCount + x.NotApprovedCommentCount; + + return m; + }); + + gridModel.Total = news.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var news = _newsService.GetAllNews(0, model.SearchStoreId, command.Page - 1, command.PageSize, true); - var gridModel = new GridModel - { - Data = news.Select(x => - { - var m = x.ToModel(); - if (x.StartDateUtc.HasValue) - m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); - if (x.EndDateUtc.HasValue) - m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); - m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - m.LanguageName = x.Language.Name; - m.Comments = x.ApprovedCommentCount + x.NotApprovedCommentCount; - return m; - }), - Total = news.TotalCount - }; return new JsonResult { Data = gridModel @@ -154,7 +169,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(NewsItemModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageNews)) @@ -209,7 +224,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(NewsItemModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageNews)) @@ -262,6 +277,22 @@ public ActionResult DeleteConfirmed(int id) return RedirectToAction("List"); } + [HttpPost] + public ActionResult DeleteSelected(ICollection selectedIds) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageNews)) + return AccessDeniedView(); + + if (selectedIds != null) + { + var news = _newsService.GetNewsByIds(selectedIds.ToArray()).ToList(); + + news.ForEach(x => _newsService.DeleteNews(x)); + } + + return Json(new { Result = true }); + } + #endregion #region Comments @@ -279,47 +310,54 @@ public ActionResult Comments(int? filterByNewsItemId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Comments(int? filterByNewsItemId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNews)) - return AccessDeniedView(); + var gridModel = new GridModel(); - IList comments; - if (filterByNewsItemId.HasValue) - { - //filter comments by news item - var newsItem = _newsService.GetNewsById(filterByNewsItemId.Value); - comments = newsItem.NewsComments.OrderBy(bc => bc.CreatedOnUtc).ToList(); - } - else - { - //load all news comments - comments = _customerContentService.GetAllCustomerContent(0, null); - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageNews)) + { + IList comments; + if (filterByNewsItemId.HasValue) + { + //filter comments by news item + var newsItem = _newsService.GetNewsById(filterByNewsItemId.Value); + comments = newsItem.NewsComments.OrderBy(bc => bc.CreatedOnUtc).ToList(); + } + else + { + //load all news comments + comments = _customerContentService.GetAllCustomerContent(0, null); + } - var gridModel = new GridModel - { - Data = comments.PagedForCommand(command).Select(newsComment => - { - var commentModel = new NewsCommentModel(); + gridModel.Data = comments.PagedForCommand(command).Select(newsComment => + { + var commentModel = new NewsCommentModel(); var customer = _customerService.GetCustomerById(newsComment.CustomerId); - commentModel.Id = newsComment.Id; - commentModel.NewsItemId = newsComment.NewsItemId; - commentModel.NewsItemTitle = newsComment.NewsItem.Title; - commentModel.CustomerId = newsComment.CustomerId; - commentModel.IpAddress = newsComment.IpAddress; - commentModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(newsComment.CreatedOnUtc, DateTimeKind.Utc); - commentModel.CommentTitle = newsComment.CommentTitle; - commentModel.CommentText = Core.Html.HtmlUtils.FormatText(newsComment.CommentText, false, true, false, false, false, false); + commentModel.Id = newsComment.Id; + commentModel.NewsItemId = newsComment.NewsItemId; + commentModel.NewsItemTitle = newsComment.NewsItem.Title; + commentModel.CustomerId = newsComment.CustomerId; + commentModel.IpAddress = newsComment.IpAddress; + commentModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(newsComment.CreatedOnUtc, DateTimeKind.Utc); + commentModel.CommentTitle = newsComment.CommentTitle; + commentModel.CommentText = Core.Html.HtmlUtils.FormatText(newsComment.CommentText, false, true, false, false, false, false); if (customer == null) commentModel.CustomerName = "".NaIfEmpty(); else commentModel.CustomerName = customer.GetFullName(); - return commentModel; - }), - Total = comments.Count, - }; + return commentModel; + }); + + gridModel.Total = comments.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel @@ -329,17 +367,16 @@ public ActionResult Comments(int? filterByNewsItemId, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult CommentDelete(int? filterByNewsItemId, int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNews)) - return AccessDeniedView(); - - var comment = _customerContentService.GetCustomerContentById(id) as NewsComment; - if (comment == null) - throw new ArgumentException("No comment found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageNews)) + { + var comment = _customerContentService.GetCustomerContentById(id) as NewsComment; - var newsItem = comment.NewsItem; - _customerContentService.DeleteCustomerContent(comment); - //update totals - _newsService.UpdateCommentTotals(newsItem); + var newsItem = comment.NewsItem; + _customerContentService.DeleteCustomerContent(comment); + + //update totals + _newsService.UpdateCommentTotals(newsItem); + } return Comments(filterByNewsItemId, command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs index 732995c8a8..ac4bfd4929 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs @@ -1,18 +1,16 @@ using System; using System.Linq; -using System.Text; using System.Web.Mvc; -using Telerik.Web.Mvc; using SmartStore.Admin.Models.Messages; -using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Controllers; using SmartStore.Core.Domain.Common; using SmartStore.Services.Helpers; -using SmartStore.Services.Localization; using SmartStore.Services.Messages; using SmartStore.Services.Security; using SmartStore.Services.Stores; -using SmartStore.Utilities; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; +using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { @@ -21,19 +19,18 @@ public class NewsLetterSubscriptionController : AdminControllerBase { private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; private readonly IDateTimeHelper _dateTimeHelper; - private readonly ILocalizationService _localizationService; private readonly IPermissionService _permissionService; private readonly AdminAreaSettings _adminAreaSettings; private readonly IStoreService _storeService; public NewsLetterSubscriptionController(INewsLetterSubscriptionService newsLetterSubscriptionService, - IDateTimeHelper dateTimeHelper,ILocalizationService localizationService, - IPermissionService permissionService, AdminAreaSettings adminAreaSettings, + IDateTimeHelper dateTimeHelper, + IPermissionService permissionService, + AdminAreaSettings adminAreaSettings, IStoreService storeService) { this._newsLetterSubscriptionService = newsLetterSubscriptionService; this._dateTimeHelper = dateTimeHelper; - this._localizationService = localizationService; this._permissionService = permissionService; this._adminAreaSettings = adminAreaSettings; this._storeService = storeService; @@ -45,9 +42,7 @@ private void PrepareNewsLetterSubscriptionListModel(NewsLetterSubscriptionListMo model.GridPageSize = _adminAreaSettings.GridPageSize; - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - model.AvailableStores.AddRange(stores.ToSelectListItems()); - + model.AvailableStores = stores.ToSelectListItems(); } public ActionResult Index() @@ -72,7 +67,7 @@ public ActionResult List() var store = _storeService.GetStoreById(x.StoreId); m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - m.StoreName = store != null ? store.Name : "Unknown"; + m.StoreName = store != null ? store.Name : "".NaIfEmpty(); return m; }), @@ -84,26 +79,33 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult SubscriptionList(GridCommand command, NewsLetterSubscriptionListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var newsletterSubscriptions = _newsLetterSubscriptionService.GetAllNewsLetterSubscriptions( - model.SearchEmail, command.Page - 1, command.PageSize, true, model.StoreId); + if (_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) + { + var newsletterSubscriptions = _newsLetterSubscriptionService.GetAllNewsLetterSubscriptions( + model.SearchEmail, command.Page - 1, command.PageSize, true, model.StoreId); - var gridModel = new GridModel - { - Data = newsletterSubscriptions.Select(x => + gridModel.Data = newsletterSubscriptions.Select(x => { var m = x.ToModel(); var store = _storeService.GetStoreById(x.StoreId); m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - m.StoreName = store != null ? store.Name : "Unknown"; + m.StoreName = store != null ? store.Name : "".NaIfEmpty(); return m; - }), - Total = newsletterSubscriptions.TotalCount - }; + }); + + gridModel.Total = newsletterSubscriptions.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel @@ -113,104 +115,41 @@ public ActionResult SubscriptionList(GridCommand command, NewsLetterSubscription [GridAction(EnableCustomBinding = true)] public ActionResult SubscriptionUpdate(NewsLetterSubscriptionModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) - return AccessDeniedView(); - - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionById(model.Id); - subscription.Email = model.Email; - subscription.Active = model.Active; + var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionById(model.Id); + subscription.Email = model.Email; + subscription.Active = model.Active; - _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscription); + _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscription); + } var listModel = new NewsLetterSubscriptionListModel(); PrepareNewsLetterSubscriptionListModel(listModel); - return SubscriptionList(command, listModel); + return SubscriptionList(command, listModel); } [GridAction(EnableCustomBinding = true)] public ActionResult SubscriptionDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) - return AccessDeniedView(); - - var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionById(id); - if (subscription == null) - throw new ArgumentException("No subscription found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) + { + var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionById(id); - _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); + _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); + } var listModel = new NewsLetterSubscriptionListModel(); PrepareNewsLetterSubscriptionListModel(listModel); return SubscriptionList(command, listModel); } - - public ActionResult ExportCsv(NewsLetterSubscriptionListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) - return AccessDeniedView(); - - string fileName = String.Format("newsletter_emails_{0}_{1}.txt", DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss"), CommonHelper.GenerateRandomDigitCode(4)); - - var sb = new StringBuilder(); - var newsLetterSubscriptions = _newsLetterSubscriptionService.GetAllNewsLetterSubscriptions(model.SearchEmail, 0, int.MaxValue, true, model.StoreId); - - if (newsLetterSubscriptions.Count == 0) - { - NotifyInfo(_localizationService.GetResource("Admin.Common.ExportNoData")); - return RedirectToAction("List"); - } - - foreach (var subscription in newsLetterSubscriptions) - { - sb.Append(subscription.Email); - sb.Append(","); - sb.Append(subscription.Active); - sb.Append(","); - sb.Append(subscription.StoreId); - sb.Append(Environment.NewLine); - } - string result = sb.ToString(); - - return File(Encoding.UTF8.GetBytes(result), "text/csv", fileName); - } - - [HttpPost] - public ActionResult ImportCsv(FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) - return AccessDeniedView(); - - try - { - var file = Request.Files["importcsvfile"]; - if (file != null && file.ContentLength > 0) - { - var result = _newsLetterSubscriptionService.ImportSubscribers(file.InputStream); - - NotifySuccess( - _localizationService.GetResource("Admin.Promotions.NewsLetterSubscriptions.ImportEmailsSuccess").FormatWith(result.NewRecords, result.ModifiedRecords) - ); - - return RedirectToAction("List"); - } - - NotifyError(_localizationService.GetResource("Admin.Common.UploadFile")); - return RedirectToAction("List"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } - } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OnlineCustomerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OnlineCustomerController.cs index a03a657938..3aca0ddd54 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OnlineCustomerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OnlineCustomerController.cs @@ -11,6 +11,7 @@ using SmartStore.Services.Localization; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -80,27 +81,35 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + { + var lastActivityFrom = DateTime.UtcNow.AddMinutes(-_customerSettings.OnlineCustomerMinutes); + var customers = _customerService.GetOnlineCustomers(lastActivityFrom, null, command.Page - 1, command.PageSize); + + model.Data = customers.Select(x => + { + return new OnlineCustomerModel + { + Id = x.Id, + CustomerInfo = x.IsRegistered() ? x.Email : T("Admin.Customers.Guest").Text, + LastIpAddress = x.LastIpAddress, + Location = _geoCountryLookup.LookupCountryName(x.LastIpAddress), + LastActivityDate = _dateTimeHelper.ConvertToUserTime(x.LastActivityDateUtc, DateTimeKind.Utc), + LastVisitedPage = x.GetAttribute(SystemCustomerAttributeNames.LastVisitedPage) + }; + }); + + model.Total = customers.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var customers = _customerService.GetOnlineCustomers(DateTime.UtcNow.AddMinutes(-_customerSettings.OnlineCustomerMinutes), - null, command.Page - 1, command.PageSize); - var model = new GridModel - { - Data = customers.Select(x => - { - return new OnlineCustomerModel() - { - Id = x.Id, - CustomerInfo = x.IsRegistered() ? x.Email : _localizationService.GetResource("Admin.Customers.Guest"), - LastIpAddress = x.LastIpAddress, - Location = _geoCountryLookup.LookupCountryName(x.LastIpAddress), - LastActivityDate = _dateTimeHelper.ConvertToUserTime(x.LastActivityDateUtc, DateTimeKind.Utc), - LastVisitedPage = x.GetAttribute(SystemCustomerAttributeNames.LastVisitedPage) - }; - }), - Total = customers.TotalCount - }; return new JsonResult { Data = model diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index 32f43e4aa3..e73b37d270 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Net.Mime; using System.Web.Mvc; using System.Web.Routing; using SmartStore.Admin.Models.Orders; @@ -17,13 +15,13 @@ using SmartStore.Core.Domain.Tax; using SmartStore.Core.Events; using SmartStore.Core.Html; +using SmartStore.Core.Logging; using SmartStore.Services; using SmartStore.Services.Affiliates; using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Directory; -using SmartStore.Services.ExportImport; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; @@ -37,9 +35,10 @@ using SmartStore.Services.Tax; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Pdf; using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -64,7 +63,6 @@ public partial class OrderController : AdminControllerBase private readonly ICountryService _countryService; private readonly IStateProvinceService _stateProvinceService; private readonly IProductService _productService; - private readonly IExportManager _exportManager; private readonly IPermissionService _permissionService; private readonly IWorkflowMessageService _workflowMessageService; private readonly ICategoryService _categoryService; @@ -83,15 +81,17 @@ public partial class OrderController : AdminControllerBase private readonly ICustomerService _customerService; private readonly PluginMediator _pluginMediator; private readonly IAffiliateService _affiliateService; + private readonly ICustomerActivityService _customerActivityService; - private readonly CatalogSettings _catalogSettings; + private readonly CatalogSettings _catalogSettings; private readonly CurrencySettings _currencySettings; private readonly TaxSettings _taxSettings; private readonly MeasureSettings _measureSettings; private readonly PdfSettings _pdfSettings; private readonly AddressSettings _addressSettings; + private readonly AdminAreaSettings _adminAreaSettings; - private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter; + private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter; private readonly IPdfConverter _pdfConverter; private readonly ICommonServices _services; private readonly Lazy _pictureService; @@ -108,7 +108,7 @@ public OrderController(IOrderService orderService, IMeasureService measureService, IAddressService addressService, ICountryService countryService, IStateProvinceService stateProvinceService, IProductService productService, - IExportManager exportManager, IPermissionService permissionService, + IPermissionService permissionService, IWorkflowMessageService workflowMessageService, ICategoryService categoryService, IManufacturerService manufacturerService, IProductAttributeService productAttributeService, IProductAttributeParser productAttributeParser, @@ -122,9 +122,11 @@ public OrderController(IOrderService orderService, ICustomerService customerService, PluginMediator pluginMediator, IAffiliateService affiliateService, - CatalogSettings catalogSettings, CurrencySettings currencySettings, TaxSettings taxSettings, + ICustomerActivityService customerActivityService, + CatalogSettings catalogSettings, CurrencySettings currencySettings, TaxSettings taxSettings, MeasureSettings measureSettings, PdfSettings pdfSettings, AddressSettings addressSettings, - IPdfConverter pdfConverter, ICommonServices services, Lazy pictureService) + AdminAreaSettings adminAreaSettings, + IPdfConverter pdfConverter, ICommonServices services, Lazy pictureService) { this._orderService = orderService; this._orderReportService = orderReportService; @@ -141,7 +143,6 @@ public OrderController(IOrderService orderService, this._countryService = countryService; this._stateProvinceService = stateProvinceService; this._productService = productService; - this._exportManager = exportManager; this._permissionService = permissionService; this._workflowMessageService = workflowMessageService; this._categoryService = categoryService; @@ -160,13 +161,15 @@ public OrderController(IOrderService orderService, this._customerService = customerService; this._pluginMediator = pluginMediator; this._affiliateService = affiliateService; + this._customerActivityService = customerActivityService; - this._catalogSettings = catalogSettings; + this._catalogSettings = catalogSettings; this._currencySettings = currencySettings; this._taxSettings = taxSettings; this._measureSettings = measureSettings; this._pdfSettings = pdfSettings; this._addressSettings = addressSettings; + this._adminAreaSettings = adminAreaSettings; this._checkoutAttributeFormatter = checkoutAttributeFormatter; _pdfConverter = pdfConverter; @@ -188,7 +191,7 @@ protected void PrepareOrderDetailsModel(OrderModel model, Order order) throw new ArgumentNullException("model"); var store = _storeService.GetStoreById(order.StoreId); - var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); + var primaryStoreCurrency = store.PrimaryStoreCurrency; model.Id = order.Id; model.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext); @@ -206,6 +209,8 @@ protected void PrepareOrderDetailsModel(OrderModel model, Order order) model.TaxDisplayType = _taxSettings.TaxDisplayType; model.AffiliateId = order.AffiliateId; model.CustomerComment = order.CustomerOrderComment; + model.HasNewPaymentNotification = order.HasNewPaymentNotification; + model.AcceptThirdPartyEmailHandOver = order.AcceptThirdPartyEmailHandOver; if (order.AffiliateId != 0) { @@ -346,7 +351,7 @@ protected void PrepareOrderDetailsModel(OrderModel model, Order order) var pm = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); if (pm != null) { - if (pm.Metadata.SystemName.Equals("Payments.PurchaseOrder", StringComparison.InvariantCultureIgnoreCase)) + if (pm.Metadata.SystemName.Equals("SmartStore.PurchaseOrderNumber", StringComparison.InvariantCultureIgnoreCase)) { model.DisplayPurchaseOrderNumber = true; model.PurchaseOrderNumber = order.PurchaseOrderNumber; @@ -470,7 +475,7 @@ protected void PrepareOrderDetailsModel(OrderModel model, Order order) hasDownloadableItems = true; orderItem.Product.MergeWithCombination(orderItem.AttributesXml); - var orderItemModel = new OrderModel.OrderItemModel() + var orderItemModel = new OrderModel.OrderItemModel { Id = orderItem.Id, ProductId = orderItem.ProductId, @@ -496,7 +501,7 @@ protected void PrepareOrderDetailsModel(OrderModel model, Order order) foreach (var bundleItem in bundleData) { - var bundleItemModel = new OrderModel.BundleItemModel() + var bundleItemModel = new OrderModel.BundleItemModel { ProductId = bundleItem.ProductId, Sku = bundleItem.Sku, @@ -580,7 +585,7 @@ protected OrderModel.AddOrderProductModel.ProductDetailsModel PrepareAddProductT { var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); var customer = _workContext.CurrentCustomer; // TODO: we need a customer representing entity instance for backend work var order = _orderService.GetOrderById(orderId); @@ -786,69 +791,83 @@ public ActionResult List(OrderListModel model) if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); + var allStores = _storeService.GetAllStores(); + + model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); model.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); - //stores - model.AvailableStores.AddRange(_storeService.GetAllStores().ToList().ToSelectListItems()); - model.AvailableStores.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + model.AvailableStores = allStores + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); - return View(model); + model.GridPageSize = _adminAreaSettings.GridPageSize; + + return View(model); } [GridAction(EnableCustomBinding = true)] public ActionResult OrderList(GridCommand command, OrderListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); - DateTime? startDateValue = (model.StartDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - DateTime? endDateValue = (model.EndDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + DateTime? startDateValue = (model.StartDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + DateTime? endDateValue = (model.EndDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - var orderStatusIds = model.OrderStatusIds.ToIntArray(); - var paymentStatusIds = model.PaymentStatusIds.ToIntArray(); - var shippingStatusIds = model.ShippingStatusIds.ToIntArray(); + var orderStatusIds = model.OrderStatusIds.ToIntArray(); + var paymentStatusIds = model.PaymentStatusIds.ToIntArray(); + var shippingStatusIds = model.ShippingStatusIds.ToIntArray(); - var orders = _orderService.SearchOrders(model.StoreId, 0, startDateValue, endDateValue, orderStatusIds, paymentStatusIds, shippingStatusIds, - model.CustomerEmail, model.OrderGuid, model.OrderNumber, command.Page - 1, command.PageSize, model.CustomerName); + var orders = _orderService.SearchOrders(model.StoreId, 0, startDateValue, endDateValue, orderStatusIds, paymentStatusIds, shippingStatusIds, + model.CustomerEmail, model.OrderGuid, model.OrderNumber, command.Page - 1, command.PageSize, model.CustomerName); - var gridModel = new GridModel - { - Data = orders.Select(x => - { + gridModel.Data = orders.Select(x => + { var store = _storeService.GetStoreById(x.StoreId); - return new OrderModel() - { - Id = x.Id, - OrderNumber = x.GetOrderNumber(), - StoreName = store != null ? store.Name : "Unknown", - OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), - OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), - PaymentStatus = x.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), - ShippingStatus = x.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), - CustomerEmail = x.BillingAddress.Email, - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) - }; - }), - Total = orders.TotalCount - }; + return new OrderModel + { + Id = x.Id, + OrderNumber = x.GetOrderNumber(), + StoreName = (store != null ? store.Name : "".NaIfEmpty()), + OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), + OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), + PaymentStatus = x.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), + ShippingStatus = x.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), + CustomerName = x.BillingAddress.GetFullName(), + CustomerEmail = x.BillingAddress.Email, + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), + HasNewPaymentNotification = x.HasNewPaymentNotification + }; + }); + + gridModel.Total = orders.TotalCount; + + //summary report + //implemented as a workaround described here: http://www.telerik.com/community/forums/aspnet-mvc/grid/gridmodel-aggregates-how-to-use.aspx + var reportSummary = _orderReportService.GetOrderAverageReportLine(model.StoreId, orderStatusIds, + paymentStatusIds, shippingStatusIds, startDateValue, endDateValue, model.CustomerEmail); + + var profit = _orderReportService.ProfitReport(model.StoreId, orderStatusIds, + paymentStatusIds, shippingStatusIds, startDateValue, endDateValue, model.CustomerEmail); + + var aggregator = new OrderModel + { + aggregatorprofit = _priceFormatter.FormatPrice(profit, true, false), + aggregatortax = _priceFormatter.FormatPrice(reportSummary.SumTax, true, false), + aggregatortotal = _priceFormatter.FormatPrice(reportSummary.SumOrders, true, false) + }; + + gridModel.Aggregates = aggregator; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - //summary report - //implemented as a workaround described here: http://www.telerik.com/community/forums/aspnet-mvc/grid/gridmodel-aggregates-how-to-use.aspx - var reportSummary = _orderReportService.GetOrderAverageReportLine(model.StoreId, orderStatusIds, - paymentStatusIds, shippingStatusIds, startDateValue, endDateValue, model.CustomerEmail); - - var profit = _orderReportService.ProfitReport(model.StoreId, orderStatusIds, - paymentStatusIds, shippingStatusIds, startDateValue, endDateValue, model.CustomerEmail); - - var aggregator = new OrderModel() - { - aggregatorprofit = _priceFormatter.FormatPrice(profit, true, false), - aggregatortax = _priceFormatter.FormatPrice(reportSummary.SumTax, true, false), - aggregatortotal = _priceFormatter.FormatPrice(reportSummary.SumOrders, true, false) - }; - gridModel.Aggregates = aggregator; return new JsonResult { Data = gridModel @@ -869,100 +888,11 @@ public ActionResult GoToOrderId(OrderListModel model) return RedirectToAction("List", "Order"); } - #endregion - - #region Export / Import - - public ActionResult ExportXmlAll() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - try - { - var orders = _orderService.SearchOrders(0, 0, null, null, null, - null, null, null, null, null, 0, int.MaxValue); - - var xml = _exportManager.ExportOrdersToXml(orders); - return new XmlDownloadResult(xml, "orders.xml"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } - } - - [HttpPost] - public ActionResult ExportXmlSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var orders = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - orders.AddRange(_orderService.GetOrdersByIds(ids)); - } - - var xml = _exportManager.ExportOrdersToXml(orders); - return new XmlDownloadResult(xml, "orders.xml"); - } - - public ActionResult ExportExcelAll() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - try - { - var orders = _orderService.SearchOrders(0, 0, null, null, null, - null, null, null, null, null, 0, int.MaxValue); - - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportOrdersToXlsx(stream, orders); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "orders.xlsx"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } - } - - [HttpPost] - public ActionResult ExportExcelSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var orders = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - orders.AddRange(_orderService.GetOrdersByIds(ids)); - } + #endregion - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportOrdersToXlsx(stream, orders); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "orders.xlsx"); - } + #region Export / Import + [HttpPost, Compress] public ActionResult ExportPdf(bool all, string selectedIds = null) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) @@ -971,6 +901,7 @@ public ActionResult ExportPdf(bool all, string selectedIds = null) if (!all && selectedIds.IsEmpty()) { NotifyInfo(_localizationService.GetResource("Admin.Common.ExportNoData")); + return RedirectToAction("List"); } @@ -1320,15 +1251,22 @@ public ActionResult Delete(int id) return RedirectToAction("List"); _orderProcessingService.DeleteOrder(order); - return RedirectToAction("List"); + + var msg = T("ActivityLog.DeleteOrder", order.GetOrderNumber()); + + _customerActivityService.InsertActivity("DeleteOrder", msg); + + NotifySuccess(msg); + + return RedirectToAction("List"); } - public ActionResult PdfInvoice(int orderId) + public ActionResult Print(int orderId, bool pdf = false) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - return RedirectToAction("Print", "Order", new { id = orderId, pdf = true, area = "" }); + return RedirectToAction("Print", "Order", new { id = orderId, pdf = pdf, area = "" }); } [HttpPost, ActionName("Edit")] @@ -1641,7 +1579,7 @@ public ActionResult UploadLicenseFilePopup(int id, int orderItemId) if (!orderItem.Product.IsDownload) throw new ArgumentException("Product is not downloadable"); - var model = new OrderModel.UploadLicenseModel() + var model = new OrderModel.UploadLicenseModel { LicenseDownloadId = orderItem.LicenseDownloadId.HasValue ? orderItem.LicenseDownloadId.Value : 0, OrderId = order.Id, @@ -1672,6 +1610,9 @@ public ActionResult UploadLicenseFilePopup(string btnId, string formId, OrderMod orderItem.LicenseDownloadId = model.LicenseDownloadId; else orderItem.LicenseDownloadId = null; + + MediaHelper.UpdateDownloadTransientStateFor(orderItem, x => x.LicenseDownloadId); + _orderService.UpdateOrder(order); //success @@ -1700,6 +1641,7 @@ public ActionResult DeleteLicenseFilePopup(string btnId, string formId, OrderMod //attach license orderItem.LicenseDownloadId = null; + MediaHelper.UpdateDownloadTransientStateFor(orderItem, x => x.LicenseDownloadId); _orderService.UpdateOrder(order); //success @@ -1718,7 +1660,6 @@ public ActionResult AddProductToOrder(int orderId) var model = new OrderModel.AddOrderProductModel(); model.OrderId = orderId; - //categories var allCategories = _categoryService.GetAllCategories(showHidden: true); var mappedCategories = allCategories.ToDictionary(x => x.Id); foreach (var c in allCategories) @@ -1726,15 +1667,12 @@ public ActionResult AddProductToOrder(int orderId) model.AvailableCategories.Add(new SelectListItem() { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); } - //manufacturers foreach (var m in _manufacturerService.GetAllManufacturers(true)) { model.AvailableManufacturers.Add(new SelectListItem() { Text = m.Name, Value = m.Id.ToString() }); } - //product types model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); return View(model); } @@ -1742,36 +1680,46 @@ public ActionResult AddProductToOrder(int orderId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult AddProductToOrder(GridCommand command, OrderModel.AddOrderProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var gridModel = new GridModel(); - var searchContext = new ProductSearchContext() + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) { - CategoryIds = new List() { model.SearchCategoryId }, - ManufacturerId = model.SearchManufacturerId, - Keywords = model.SearchProductName, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - ShowHidden = true, - ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null - }; + var searchContext = new ProductSearchContext + { + CategoryIds = new List { model.SearchCategoryId }, + ManufacturerId = model.SearchManufacturerId, + Keywords = model.SearchProductName, + PageIndex = command.Page - 1, + PageSize = command.PageSize, + ShowHidden = true, + ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null + }; - var products = _productService.SearchProducts(searchContext); - gridModel.Data = products.Select(x => - { - var productModel = new OrderModel.AddOrderProductModel.ProductModel() - { - Id = x.Id, - Name = x.Name, - Sku = x.Sku, - ProductTypeName = x.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = x.ProductTypeLabelHint - }; + var products = _productService.SearchProducts(searchContext); + + gridModel.Data = products.Select(x => + { + var productModel = new OrderModel.AddOrderProductModel.ProductModel + { + Id = x.Id, + Name = x.Name, + Sku = x.Sku, + ProductTypeName = x.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = x.ProductTypeLabelHint + }; + + return productModel; + }); + + gridModel.Total = products.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - return productModel; - }); - gridModel.Total = products.TotalCount; return new JsonResult { Data = gridModel @@ -1876,7 +1824,7 @@ public ActionResult AddProductToOrderDetails(int orderId, int productId, bool ad string attributeDescription = _productAttributeFormatter.FormatAttributes(product, attributes, order.Customer); //save item - var orderItem = new OrderItem() + var orderItem = new OrderItem { OrderItemGuid = Guid.NewGuid(), Order = order, @@ -2049,23 +1997,29 @@ public ActionResult ShipmentList() [GridAction(EnableCustomBinding = true)] public ActionResult ShipmentListSelect(GridCommand command, ShipmentListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - DateTime? endDateValue = (model.EndDate == null) ? null - :(DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + //load shipments + var shipments = _shipmentService.GetAllShipments(model.TrackingNumber, startDateValue, endDateValue, command.Page - 1, command.PageSize); + + gridModel.Data = shipments.Select(shipment => PrepareShipmentModel(shipment, false, false)); + gridModel.Total = shipments.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - //load shipments - var shipments = _shipmentService.GetAllShipments(model.TrackingNumber, startDateValue, endDateValue, - command.Page - 1, command.PageSize); - var gridModel = new GridModel - { - Data = shipments.Select(shipment => PrepareShipmentModel(shipment, false, false)), - Total = shipments.TotalCount - }; return new JsonResult { Data = gridModel @@ -2075,24 +2029,29 @@ public ActionResult ShipmentListSelect(GridCommand command, ShipmentListModel mo [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ShipmentsSelect(int orderId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var model = new GridModel(); - var order = _orderService.GetOrderById(orderId); - if (order == null) - throw new ArgumentException("No order found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var order = _orderService.GetOrderById(orderId); - //shipments - var shipmentModels = new List(); - var shipments = order.Shipments.OrderBy(s => s.CreatedOnUtc).ToList(); - foreach (var shipment in shipments) - shipmentModels.Add(PrepareShipmentModel(shipment, false, false)); + var shipmentModels = new List(); + var shipments = order.Shipments.OrderBy(s => s.CreatedOnUtc).ToList(); - var model = new GridModel - { - Data = shipmentModels, - Total = shipmentModels.Count - }; + foreach (var shipment in shipments) + { + shipmentModels.Add(PrepareShipmentModel(shipment, false, false)); + } + + model.Data = shipmentModels; + model.Total = shipmentModels.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -2162,7 +2121,7 @@ public ActionResult AddShipment(int orderId) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [FormValueRequired("save", "save-continue")] public ActionResult AddShipment(int orderId, FormCollection form, bool continueEditing) { @@ -2171,84 +2130,39 @@ public ActionResult AddShipment(int orderId, FormCollection form, bool continueE var order = _orderService.GetOrderById(orderId); if (order == null) - //No order found with the specified id return RedirectToAction("List"); - Shipment shipment = null; - - decimal? totalWeight = null; - foreach (var orderItem in order.OrderItems) - { - //is shippable - if (!orderItem.Product.IsShipEnabled) - continue; - - //ensure that this product can be shipped (have at least one item to ship) - var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); - if (maxQtyToAdd <= 0) - continue; - - int qtyToAdd = 0; //parse quantity - foreach (string formKey in form.AllKeys) - if (formKey.Equals(string.Format("qtyToAdd{0}", orderItem.Id), StringComparison.InvariantCultureIgnoreCase)) - { - int.TryParse(form[formKey], out qtyToAdd); - break; - } - - //validate quantity - if (qtyToAdd <= 0) - continue; - if (qtyToAdd > maxQtyToAdd) - qtyToAdd = maxQtyToAdd; - - //ok. we have at least one item. let's create a shipment (if it does not exist) + var quantities = new Dictionary(); + var trackingNumber = form["TrackingNumber"]; - var orderItemTotalWeight = orderItem.ItemWeight.HasValue ? orderItem.ItemWeight * qtyToAdd : null; - if (orderItemTotalWeight.HasValue) - { - if (!totalWeight.HasValue) - totalWeight = 0; - totalWeight += orderItemTotalWeight.Value; - } + foreach (var orderItem in order.OrderItems) + { + foreach (string formKey in form.AllKeys) + { + if (formKey.Equals(string.Format("qtyToAdd{0}", orderItem.Id), StringComparison.InvariantCultureIgnoreCase)) + { + quantities.Add(orderItem.Id, form[formKey].ToInt()); + break; + } + } + } - if (shipment == null) - { - shipment = new Shipment() - { - OrderId = order.Id, - TrackingNumber = form["TrackingNumber"], - TotalWeight = null, - ShippedDateUtc = null, - DeliveryDateUtc = null, - CreatedOnUtc = DateTime.UtcNow, - }; - } - //create a shipment item - var shipmentItem = new ShipmentItem() - { - OrderItemId = orderItem.Id, - Quantity = qtyToAdd, - }; - shipment.ShipmentItems.Add(shipmentItem); - } + var shipment = _orderProcessingService.AddShipment(order, trackingNumber, quantities); - //if we have at least one item in the shipment, then save it - if (shipment != null && shipment.ShipmentItems.Count > 0) - { - shipment.TotalWeight = totalWeight; - _shipmentService.InsertShipment(shipment); + if (shipment != null) + { + NotifySuccess(_localizationService.GetResource("Admin.Orders.Shipments.Added")); - NotifySuccess(_localizationService.GetResource("Admin.Orders.Shipments.Added")); - return continueEditing - ? RedirectToAction("ShipmentDetails", new {id = shipment.Id}) - : RedirectToAction("Edit", new { id = orderId }); - } - else - { + return continueEditing + ? RedirectToAction("ShipmentDetails", new { id = shipment.Id }) + : RedirectToAction("Edit", new { id = orderId }); + } + else + { NotifyError(_localizationService.GetResource("Admin.Orders.Shipments.NoProductsSelected")); + return RedirectToAction("AddShipment", new { orderId = orderId }); - } + } } public ActionResult ShipmentDetails(int id) @@ -2422,33 +2336,41 @@ public ActionResult PdfPackagingSlips(bool all, string selectedIds = null) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult OrderNotesSelect(int orderId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var model = new GridModel(); - var order = _orderService.GetOrderById(orderId); - if (order == null) - throw new ArgumentException("No order found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var order = _orderService.GetOrderById(orderId); - //order notes - var orderNoteModels = new List(); - foreach (var orderNote in order.OrderNotes - .OrderByDescending(on => on.CreatedOnUtc)) - { - orderNoteModels.Add(new OrderModel.OrderNote() - { - Id = orderNote.Id, - OrderId = orderNote.OrderId, - DisplayToCustomer = orderNote.DisplayToCustomer, - Note = orderNote.FormatOrderNoteText(), - CreatedOn = _dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc) - }); - } + var orderNoteModels = new List(); - var model = new GridModel - { - Data = orderNoteModels, - Total = orderNoteModels.Count - }; + foreach (var orderNote in order.OrderNotes.OrderByDescending(on => on.CreatedOnUtc)) + { + orderNoteModels.Add(new OrderModel.OrderNote + { + Id = orderNote.Id, + OrderId = orderNote.OrderId, + DisplayToCustomer = orderNote.DisplayToCustomer, + Note = orderNote.FormatOrderNoteText(), + CreatedOn = _dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc) + }); + } + + model.Data = orderNoteModels; + model.Total = orderNoteModels.Count; + + if (order.HasNewPaymentNotification) + { + order.HasNewPaymentNotification = false; + _orderService.UpdateOrder(order); + } + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -2491,17 +2413,13 @@ public ActionResult OrderNoteAdd(int orderId, bool displayToCustomer, string mes [GridAction(EnableCustomBinding = true)] public ActionResult OrderNoteDelete(int orderId, int orderNoteId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(orderId); - if (order == null) - throw new ArgumentException("No order found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var order = _orderService.GetOrderById(orderId); + var orderNote = order.OrderNotes.Where(on => on.Id == orderNoteId).FirstOrDefault(); - var orderNote = order.OrderNotes.Where(on => on.Id == orderNoteId).FirstOrDefault(); - if (orderNote == null) - throw new ArgumentException("No order note found with the specified id"); - _orderService.DeleteOrderNote(orderNote); + _orderService.DeleteOrderNote(orderNote); + } return OrderNotesSelect(orderId, command); } @@ -2539,12 +2457,14 @@ protected IList GetBestsellersBriefReportModel(int r return model; } - public ActionResult BestsellersBriefReportByQuantity() + + public ActionResult BestsellersBriefReportByQuantity() { var model = GetBestsellersBriefReportModel(5, 1); return PartialView(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult BestsellersBriefReportByQuantityList(GridCommand command) { var model = GetBestsellersBriefReportModel(5, 1); @@ -2558,12 +2478,14 @@ public ActionResult BestsellersBriefReportByQuantityList(GridCommand command) Data = gridModel }; } - public ActionResult BestsellersBriefReportByAmount() + + public ActionResult BestsellersBriefReportByAmount() { var model = GetBestsellersBriefReportModel(5, 2); return PartialView(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult BestsellersBriefReportByAmountList(GridCommand command) { var model = GetBestsellersBriefReportModel(5, 2); @@ -2578,51 +2500,46 @@ public ActionResult BestsellersBriefReportByAmountList(GridCommand command) }; } - - - public ActionResult BestsellersReport() { var model = new BestsellersReportModel(); - //order statuses model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); - model.AvailableOrderStatuses.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - //payment statuses model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.AvailablePaymentStatuses.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - //billing countries foreach (var c in _countryService.GetAllCountriesForBilling()) { - model.AvailableCountries.Add(new SelectListItem() { Text = c.Name, Value = c.Id.ToString() }); + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString() }); } - model.AvailableCountries.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + + model.AvailableCountries.Insert(0, new SelectListItem { Text = T("Admin.Address.SelectCountry"), Value = "0" }); return View(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult BestsellersReportList(GridCommand command, BestsellersReportModel model) { DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); OrderStatus? orderStatus = model.OrderStatusId > 0 ? (OrderStatus?)(model.OrderStatusId) : null; PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; var items = _orderReportService.BestSellersReport(0, startDateValue, endDateValue, orderStatus, paymentStatus, null, model.BillingCountryId, 100, 2, true); + var gridModel = new GridModel { Data = items.Select(x => { var product = _productService.GetProductById(x.ProductId); - var m = new BestsellersReportLineModel() + var m = new BestsellersReportLineModel { ProductId = x.ProductId, TotalAmount = _priceFormatter.FormatPrice(x.TotalAmount, true, false), @@ -2639,6 +2556,7 @@ public ActionResult BestsellersReportList(GridCommand command, BestsellersReport }), Total = items.Count }; + return new JsonResult { Data = gridModel @@ -2652,17 +2570,18 @@ public ActionResult NeverSoldReport() var model = new NeverSoldReportModel(); return View(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult NeverSoldReportList(GridCommand command, NeverSoldReportModel model) { DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - var items = _orderReportService.ProductsNeverSold(startDateValue, endDateValue, - command.Page - 1, command.PageSize, true); + var items = _orderReportService.ProductsNeverSold(startDateValue, endDateValue, command.Page - 1, command.PageSize, true); + var gridModel = new GridModel { Data = items.Select(x => @@ -2678,6 +2597,7 @@ public ActionResult NeverSoldReportList(GridCommand command, NeverSoldReportMode }), Total = items.TotalCount }; + return new JsonResult { Data = gridModel @@ -2786,13 +2706,15 @@ protected virtual IList GetOrderIncompleteReport return model; } - [ChildActionOnly] + + [ChildActionOnly] public ActionResult OrderIncompleteReport() { var model = GetOrderIncompleteReportModel(); return PartialView(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult OrderIncompleteReportList(GridCommand command) { var model = GetOrderIncompleteReportModel(); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs index 65a0c4b1a8..12b1cb92dd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs @@ -11,7 +11,9 @@ using SmartStore.Services; using SmartStore.Services.Security; using System.Dynamic; +using SmartStore.Core.Logging; using SmartStore.Core.Themes; +using SmartStore.Web.Framework.Security; namespace SmartStore.Admin.Controllers { @@ -30,8 +32,6 @@ public PackagingController(ICommonServices services, IPackageManager packageMana this._themeRegistry = themeRegistry; } - public Localizer T { get; set; } - [ChildActionOnly] public ActionResult UploadPackage(bool isTheme) { @@ -45,17 +45,12 @@ public ActionResult UploadPackage(bool isTheme) [HttpPost] public ActionResult UploadPackage(FormCollection form, string returnUrl = "") { - if (returnUrl.IsEmpty()) - { - returnUrl = _services.WebHelper.GetUrlReferrer(); - } - bool isTheme = false; try { - var file = Request.Files["packagefile"]; - if (file != null && file.ContentLength > 0) + var file = Request.Files["packagefile"].ToPostedFileResult(); + if (file != null) { var requiredPermission = (isTheme = PackagingUtils.IsTheme(file.FileName)) ? StandardPermissionProvider.ManageThemes @@ -66,10 +61,10 @@ public ActionResult UploadPackage(FormCollection form, string returnUrl = "") return AccessDeniedView(); } - if (!Path.GetExtension(file.FileName).IsCaseInsensitiveEqual(".nupkg")) + if (!file.FileExtension.IsCaseInsensitiveEqual(".nupkg")) { NotifyError(T("Admin.Packaging.NotAPackage")); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } var location = CommonHelper.MapPath("~/App_Data"); @@ -81,7 +76,7 @@ public ActionResult UploadPackage(FormCollection form, string returnUrl = "") _themeRegistry.Value.StopMonitoring(); } - var packageInfo = _packageManager.Install(file.InputStream, location, appPath); + var packageInfo = _packageManager.Install(file.Stream, location, appPath); if (isTheme) { @@ -102,7 +97,7 @@ public ActionResult UploadPackage(FormCollection form, string returnUrl = "") else { NotifyError(T("Admin.Common.UploadFile")); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } if (!isTheme) @@ -110,12 +105,13 @@ public ActionResult UploadPackage(FormCollection form, string returnUrl = "") _services.WebHelper.RestartAppDomain(); } NotifySuccess(T("Admin.Packaging.InstallSuccess")); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } catch (Exception exc) { NotifyError(exc); - return Redirect(returnUrl); + Logger.Error(exc); + return RedirectToReferrer(returnUrl); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs index 2d2b631d3d..35c73eecc0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs @@ -1,19 +1,21 @@ using System.Collections.Generic; using System.Linq; using System.Web.Mvc; -using System.Web.Routing; using SmartStore.Admin.Models.Payments; -using SmartStore.Core; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Plugins; -using SmartStore.Services.Configuration; +using SmartStore.Services; +using SmartStore.Services.Customers; +using SmartStore.Services.Directory; +using SmartStore.Services.Localization; using SmartStore.Services.Payments; using SmartStore.Services.Security; -using SmartStore.Services.Localization; -using SmartStore.Web.Framework; +using SmartStore.Services.Shipping; using SmartStore.Web.Framework.Controllers; -using Telerik.Web.Mvc; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; namespace SmartStore.Admin.Controllers { @@ -22,43 +24,72 @@ public partial class PaymentController : AdminControllerBase { #region Fields + private readonly ICommonServices _services; private readonly IPaymentService _paymentService; private readonly PaymentSettings _paymentSettings; - private readonly ISettingService _settingService; - private readonly IPermissionService _permissionService; private readonly IPluginFinder _pluginFinder; - private readonly ILocalizationService _localizationService; private readonly PluginMediator _pluginMediator; + private readonly ILanguageService _languageService; + private readonly ICustomerService _customerService; + private readonly IShippingService _shippingService; + private readonly ICountryService _countryService; + private readonly ILocalizedEntityService _localizedEntityService; #endregion #region Constructors public PaymentController( + ICommonServices services, IPaymentService paymentService, PaymentSettings paymentSettings, - ISettingService settingService, - IPermissionService permissionService, IPluginFinder pluginFinder, - ILocalizationService localizationService, - PluginMediator pluginMediator) + PluginMediator pluginMediator, + ILanguageService languageService, + ICustomerService customerService, + IShippingService shippingService, + ICountryService countryService, + ILocalizedEntityService localizedEntityService) { + this._services = services; this._paymentService = paymentService; this._paymentSettings = paymentSettings; - this._settingService = settingService; - this._permissionService = permissionService; this._pluginFinder = pluginFinder; - this._localizationService = localizationService; this._pluginMediator = pluginMediator; + this._languageService = languageService; + this._customerService = customerService; + this._shippingService = shippingService; + this._countryService = countryService; + this._localizedEntityService = localizedEntityService; } - #endregion  + #endregion + + #region Utilities + + private void PreparePaymentMethodEditModel(PaymentMethodEditModel model, PaymentMethod paymentMethod) + { + var allFilters = _paymentService.GetAllPaymentMethodFilters(); + + model.FilterConfigurationUrls = allFilters + .Select(x => "'" + x.GetConfigurationUrl(model.SystemName) + "'") + .OrderBy(x => x) + .ToList(); + + if (paymentMethod != null) + { + model.Id = paymentMethod.Id; + model.FullDescription = paymentMethod.FullDescription; + } + } - #region Methods + #endregion - public ActionResult Providers() + #region Methods + + public ActionResult Providers() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePaymentMethods)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManagePaymentMethods)) return AccessDeniedView(); var paymentMethodsModel = new List(); @@ -72,7 +103,7 @@ public ActionResult Providers() model.SupportPartiallyRefund = instance.SupportPartiallyRefund; model.SupportRefund = instance.SupportRefund; model.SupportVoid = instance.SupportVoid; - model.RecurringPaymentType = instance.RecurringPaymentType.GetLocalizedEnum(_localizationService); + model.RecurringPaymentType = instance.RecurringPaymentType.GetLocalizedEnum(_services.Localization); paymentMethodsModel.Add(model); } @@ -81,25 +112,103 @@ public ActionResult Providers() public ActionResult ActivateProvider(string systemName, bool activate) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePaymentMethods)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManagePaymentMethods)) return AccessDeniedView(); var pm = _paymentService.LoadPaymentMethodBySystemName(systemName); - bool dirty = pm.IsPaymentMethodActive(_paymentSettings) != activate; - if (dirty) + + if (activate && !pm.Value.IsActive) + { + NotifyWarning(_services.Localization.GetResource("Admin.Configuration.Payment.CannotActivatePaymentMethod")); + } + else { if (!activate) _paymentSettings.ActivePaymentMethodSystemNames.Remove(pm.Metadata.SystemName); else _paymentSettings.ActivePaymentMethodSystemNames.Add(pm.Metadata.SystemName); - _settingService.SaveSetting(_paymentSettings); + _services.Settings.SaveSetting(_paymentSettings); _pluginMediator.ActivateDependentWidgets(pm.Metadata, activate); } return RedirectToAction("Providers"); } + public ActionResult Edit(string systemName) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManagePaymentMethods)) + return AccessDeniedView(); + + var provider = _paymentService.LoadPaymentMethodBySystemName(systemName); + var paymentMethod = _paymentService.GetPaymentMethodBySystemName(systemName); + + var model = new PaymentMethodEditModel(); + var providerModel = _pluginMediator.ToProviderModel(provider, true); + + model.SystemName = providerModel.SystemName; + model.IconUrl = providerModel.IconUrl; + model.FriendlyName = providerModel.FriendlyName; + model.Description = providerModel.Description; + + AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.FriendlyName = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata, languageId, false); + locale.Description = _pluginMediator.GetLocalizedDescription(provider.Metadata, languageId, false); + + if (paymentMethod != null) + { + locale.FullDescription = paymentMethod.GetLocalized(x => x.FullDescription, languageId, false, false); + } + }); + + PreparePaymentMethodEditModel(model, paymentMethod); + + return View(model); + } + + [HttpPost, ValidateInput(false), ParameterBasedOnFormName("save-continue", "continueEditing")] + public ActionResult Edit(string systemName, bool continueEditing, PaymentMethodEditModel model, FormCollection form) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManagePaymentMethods)) + return AccessDeniedView(); + + var provider = _paymentService.LoadPaymentMethodBySystemName(systemName); + if (provider == null) + return HttpNotFound(); + + _pluginMediator.SetSetting(provider.Metadata, "FriendlyName", model.FriendlyName); + _pluginMediator.SetSetting(provider.Metadata, "Description", model.Description); + + var paymentMethod = _paymentService.GetPaymentMethodBySystemName(systemName); + + if (paymentMethod == null) + paymentMethod = new PaymentMethod { PaymentMethodSystemName = systemName }; + + paymentMethod.FullDescription = model.FullDescription; + + if (paymentMethod.Id == 0) + _paymentService.InsertPaymentMethod(paymentMethod); + else + _paymentService.UpdatePaymentMethod(paymentMethod); + + foreach (var localized in model.Locales) + { + _pluginMediator.SaveLocalizedValue(provider.Metadata, localized.LanguageId, "FriendlyName", localized.FriendlyName); + _pluginMediator.SaveLocalizedValue(provider.Metadata, localized.LanguageId, "Description", localized.Description); + + _localizedEntityService.SaveLocalizedValue(paymentMethod, x => x.FullDescription, localized.FullDescription, localized.LanguageId); + } + + _services.EventPublisher.Publish(new ModelBoundEvent(model, paymentMethod, form)); + + NotifySuccess(T("Admin.Common.DataEditSuccess")); + + return (continueEditing ? + RedirectToAction("Edit", "Payment", new { systemName = systemName }) : + RedirectToAction("Providers", "Payment")); + } + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PictureController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PictureController.cs index 6eac36619a..6a401eb4f7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PictureController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PictureController.cs @@ -5,6 +5,7 @@ using SmartStore.Services.Media; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; namespace SmartStore.Admin.Controllers { @@ -22,82 +23,21 @@ public PictureController(IPictureService pictureService, } [HttpPost] - public ActionResult AsyncUpload() + public ActionResult AsyncUpload(bool isTransient = false) { if (!_permissionService.Authorize(StandardPermissionProvider.UploadPictures)) - return Json(new { success = false, error = "You do not have required permissions" }, "text/plain"); + return Json(new { success = false, error = "You do not have the required permissions" }); - //we process it distinct ways based on a browser - //find more info here http://stackoverflow.com/questions/4884920/mvc3-valums-ajax-file-upload - Stream stream = null; - var fileName = ""; - var contentType = ""; - if (String.IsNullOrEmpty(Request["qqfile"])) - { - // IE - HttpPostedFileBase httpPostedFile = Request.Files[0]; - if (httpPostedFile == null) - throw new ArgumentException("No file uploaded"); - stream = httpPostedFile.InputStream; - fileName = Path.GetFileName(httpPostedFile.FileName); - contentType = httpPostedFile.ContentType; - } - else - { - //Webkit, Mozilla - stream = Request.InputStream; - fileName = Request["qqfile"]; - } + var postedFile = Request.ToPostedFileResult(); - var fileBinary = new byte[stream.Length]; - stream.Read(fileBinary, 0, fileBinary.Length); + var picture = _pictureService.InsertPicture(postedFile.Buffer, postedFile.ContentType, null, true, isTransient); - var fileExtension = Path.GetExtension(fileName); - if (!String.IsNullOrEmpty(fileExtension)) - fileExtension = fileExtension.ToLowerInvariant(); - //contentType is not always available - //that's why we manually update it here - //http://www.sfsu.edu/training/mimetype.htm - if (String.IsNullOrEmpty(contentType)) - { - switch (fileExtension) - { - case ".bmp": - contentType = "image/bmp"; - break; - case ".gif": - contentType = "image/gif"; - break; - case ".jpeg": - case ".jpg": - case ".jpe": - case ".jfif": - case ".pjpeg": - case ".pjp": - contentType = "image/jpeg"; - break; - case ".png": - contentType = "image/png"; - break; - case ".tiff": - case ".tif": - contentType = "image/tiff"; - break; - default: - break; - } - } - - var picture = _pictureService.InsertPicture(fileBinary, contentType, null, true); - //when returning JSON the mime-type must be set to text/plain - //otherwise some browsers will pop-up a "Save As" dialog. return Json( new { success = true, pictureId = picture.Id, imageUrl = _pictureService.GetPictureUrl(picture, 100) - }, - "text/plain"); + }); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs index 2665bc51ed..971c96eaa9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs @@ -4,7 +4,6 @@ using System.Web.Mvc; using System.Web.Routing; using SmartStore.Admin.Models.Plugins; -using SmartStore.Core; using SmartStore.Core.Domain.Cms; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Payments; @@ -13,18 +12,18 @@ using SmartStore.Core.Domain.Tax; using SmartStore.Core.Localization; using SmartStore.Core.Plugins; +using SmartStore.Licensing; +using SmartStore.Services; using SmartStore.Services.Authentication.External; using SmartStore.Services.Cms; -using SmartStore.Services.Configuration; using SmartStore.Services.Localization; using SmartStore.Services.Payments; using SmartStore.Services.Security; using SmartStore.Services.Shipping; -using SmartStore.Services.Stores; using SmartStore.Services.Tax; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Mvc; using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; namespace SmartStore.Admin.Controllers { @@ -34,12 +33,8 @@ public partial class PluginController : AdminControllerBase #region Fields private readonly IPluginFinder _pluginFinder; - private readonly ILocalizationService _localizationService; - private readonly IWebHelper _webHelper; private readonly IPermissionService _permissionService; private readonly ILanguageService _languageService; - private readonly ISettingService _settingService; - private readonly IStoreService _storeService; private readonly PaymentSettings _paymentSettings; private readonly ShippingSettings _shippingSettings; private readonly TaxSettings _taxSettings; @@ -47,33 +42,27 @@ public partial class PluginController : AdminControllerBase private readonly WidgetSettings _widgetSettings; private readonly IProviderManager _providerManager; private readonly PluginMediator _pluginMediator; + private readonly ICommonServices _services; #endregion #region Constructors public PluginController(IPluginFinder pluginFinder, - ILocalizationService localizationService, - IWebHelper webHelper, IPermissionService permissionService, ILanguageService languageService, - ISettingService settingService, - IStoreService storeService, PaymentSettings paymentSettings, ShippingSettings shippingSettings, TaxSettings taxSettings, ExternalAuthenticationSettings externalAuthenticationSettings, WidgetSettings widgetSettings, IProviderManager providerManager, - PluginMediator pluginMediator) + PluginMediator pluginMediator, + ICommonServices services) { this._pluginFinder = pluginFinder; - this._localizationService = localizationService; - this._webHelper = webHelper; this._permissionService = permissionService; this._languageService = languageService; - this._settingService = settingService; - this._storeService = storeService; this._paymentSettings = paymentSettings; this._shippingSettings = shippingSettings; this._taxSettings = taxSettings; @@ -81,37 +70,35 @@ public PluginController(IPluginFinder pluginFinder, this._widgetSettings = widgetSettings; this._providerManager = providerManager; this._pluginMediator = pluginMediator; - T = NullLocalizer.Instance; + this._services = services; } #endregion  #region Utilities - public Localizer T { get; set; } - [NonAction] protected PluginModel PreparePluginModel(PluginDescriptor pluginDescriptor, bool forList = true) { var model = pluginDescriptor.ToModel(); - model.Group = _localizationService.GetResource("Admin.Plugins.KnownGroup." + pluginDescriptor.Group); + model.Group = T("Admin.Plugins.KnownGroup." + pluginDescriptor.Group); if (forList) { - model.FriendlyName = pluginDescriptor.GetLocalizedValue(_localizationService, "FriendlyName"); - model.Description = pluginDescriptor.GetLocalizedValue(_localizationService, "Description"); + model.FriendlyName = pluginDescriptor.GetLocalizedValue(_services.Localization, "FriendlyName"); + model.Description = pluginDescriptor.GetLocalizedValue(_services.Localization, "Description"); } //locales AddLocales(_languageService, model.Locales, (locale, languageId) => { - locale.FriendlyName = pluginDescriptor.GetLocalizedValue(_localizationService, "FriendlyName", languageId, false); - locale.Description = pluginDescriptor.GetLocalizedValue(_localizationService, "Description", languageId, false); + locale.FriendlyName = pluginDescriptor.GetLocalizedValue(_services.Localization, "FriendlyName", languageId, false); + locale.Description = pluginDescriptor.GetLocalizedValue(_services.Localization, "Description", languageId, false); }); // Stores - model.SelectedStoreIds = _settingService.GetSettingByKey(pluginDescriptor.GetSettingKey("LimitedToStores")).ToIntArray(); + model.SelectedStoreIds = _services.Settings.GetSettingByKey(pluginDescriptor.GetSettingKey("LimitedToStores")).ToIntArray(); // Icon model.IconUrl = _pluginMediator.GetIconUrl(pluginDescriptor); @@ -138,6 +125,22 @@ protected PluginModel PreparePluginModel(PluginDescriptor pluginDescriptor, bool } } } + + if (LicenseChecker.IsLicensablePlugin(pluginDescriptor)) + { + // we always show license button to serve ability to delete a key + model.IsLicensable = true; + model.LicenseUrl = Url.Action("LicensePlugin", new { systemName = pluginDescriptor.SystemName }); + + var license = LicenseChecker.GetLicense(pluginDescriptor.SystemName); + + if (license != null) // license\plugin has been used + { + model.LicenseState = license.State; + model.TruncatedLicenseKey = license.TruncatedLicenseKey; + model.RemainingDemoUsageDays = license.RemainingDemoDays; + } + } } return model; } @@ -150,9 +153,13 @@ protected LocalPluginsModel PrepareLocalPluginsModel() .ThenBy(p => p.DisplayOrder) .Select(x => PreparePluginModel(x)); - var model = new LocalPluginsModel(); + var model = new LocalPluginsModel + { + IsSandbox = LicenseChecker.IsSandbox, + IsLocalhost = LicenseChecker.IsLocalhost + }; - model.AvailableStores = _storeService + model.AvailableStores = _services.StoreService .GetAllStores() .Select(s => s.ToModel()) .ToList(); @@ -232,7 +239,7 @@ public ActionResult ExecuteTasks(IEnumerable pluginsToInstall, IEnumerab // restart application if (tasksCount > 0) { - _webHelper.RestartAppDomain(); + _services.WebHelper.RestartAppDomain(aggressive: true); } } catch (Exception exc) @@ -248,8 +255,9 @@ public ActionResult ReloadList() if (!_permissionService.Authorize(StandardPermissionProvider.ManagePlugins)) return AccessDeniedView(); - //restart application - _webHelper.RestartAppDomain(); + // restart application + _services.WebHelper.RestartAppDomain(aggressive: true); + return RedirectToAction("List"); } @@ -263,7 +271,7 @@ public ActionResult ConfigurePlugin(string systemName) return HttpNotFound(); var model = PreparePluginModel(descriptor, false); - model.FriendlyName = descriptor.GetLocalizedValue(_localizationService, "FriendlyName"); + model.FriendlyName = descriptor.GetLocalizedValue(_services.Localization, "FriendlyName"); return View(model); } @@ -319,6 +327,128 @@ public ActionResult ConfigureProvider(string systemName, string listUrl) return View(model); } + public ActionResult LicensePlugin(string systemName, string licenseKey) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManagePlugins)) + return AccessDeniedPartialView(); + + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(systemName); + + if (descriptor == null || !descriptor.Installed) + return Content(T("Admin.Common.ResourceNotFound")); + + bool singleLicenseForAllStores = false; + bool isLicensable = LicenseChecker.IsLicensablePlugin(descriptor, out singleLicenseForAllStores); + + if (!isLicensable) + return Content(T("Admin.Common.ResourceNotFound")); + + var stores = _services.StoreService.GetAllStores(); + var model = new LicensePluginModel + { + SystemName = systemName, + Licenses = new List() + }; + + // validate store url + foreach (var store in stores) + { + if (!_services.StoreService.IsStoreDataValid(store)) + { + model.InvalidDataStoreId = store.Id; + return View(model); + } + } + + if (singleLicenseForAllStores) + { + var licenseModel = new LicensePluginModel.LicenseModel(); + var license = LicenseChecker.GetLicense(systemName, ""); + + if (license != null) + licenseModel.LicenseKey = license.TruncatedLicenseKey; + + model.Licenses.Add(licenseModel); + } + else + { + foreach (var store in stores) + { + var licenseModel = new LicensePluginModel.LicenseModel + { + StoreId = store.Id, + StoreName = store.Name, + StoreUrl = store.Url + }; + + var license = LicenseChecker.GetLicense(systemName, store.Url); + + if (license != null) + licenseModel.LicenseKey = license.TruncatedLicenseKey; + + model.Licenses.Add(licenseModel); + } + } + + return View(model); + } + + [HttpPost] + public ActionResult LicensePlugin(string systemName, LicensePluginModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManagePlugins)) + return AccessDeniedView(); + + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(systemName); + if (descriptor == null || !descriptor.Installed) + return HttpNotFound(); + + var isLicensable = LicenseChecker.IsLicensablePlugin(descriptor); + if (!isLicensable) + return HttpNotFound(); + + if (model.Licenses != null) + { + foreach (var item in model.Licenses) + { + var result = LicenseChecker.Activate(item.LicenseKey, descriptor.SystemName, item.StoreUrl); + + if (result == null) + { + // do nothing, skiped + } + else if (result.Success) + { + NotifySuccess(T("Admin.Configuration.Plugins.LicenseActivated")); + } + else + { + if (result.IsFailureWarning) + NotifyWarning(result.ToString()); + else + NotifyError(result.ToString()); + + return RedirectToAction("List"); + } + } + } + + return RedirectToAction("List"); + } + + [HttpPost] + public ActionResult LicenseResetStatusCheck(string systemName) + { + var result = LicenseChecker.ResetStatusCheckDate(systemName); + + if (result.Success) + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + else + NotifyError(result.ToString()); + + return Content(""); + } + public ActionResult EditProviderPopup(string systemName) { if (!_permissionService.Authorize(StandardPermissionProvider.ManagePlugins)) @@ -381,11 +511,11 @@ public ActionResult SetSelectedStores(string pk /* SystemName */, string name, F var storeIds = (form["value[]"] ?? "0").Split(',').Select(x => x.ToInt()).Where(x => x > 0).ToList(); if (storeIds.Count > 0) { - _settingService.SetSetting(settingKey, string.Join(",", storeIds)); + _services.Settings.SetSetting(settingKey, string.Join(",", storeIds)); } else { - _settingService.DeleteSetting(settingKey); + _services.Settings.DeleteSetting(settingKey); } } catch (Exception ex) @@ -431,25 +561,20 @@ public ActionResult UpdateStringResources(string systemName, string returnUrl = return AccessDeniedView(); var pluginDescriptor = _pluginFinder.GetPluginDescriptors() - .Where(x => x.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase)) - .FirstOrDefault(); + .FirstOrDefault(x => x.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase)); if (pluginDescriptor == null) { - NotifyError(_localizationService.GetResource("Admin.Configuration.Plugins.Resources.UpdateFailure")); + NotifyError(T("Admin.Configuration.Plugins.Resources.UpdateFailure")); } else { - _localizationService.ImportPluginResourcesFromXml(pluginDescriptor, null, false); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Plugins.Resources.UpdateSuccess")); - } + _services.Localization.ImportPluginResourcesFromXml(pluginDescriptor, null, false); - if (returnUrl.IsEmpty()) - { - return RedirectToAction("List"); + NotifySuccess(T("Admin.Configuration.Plugins.Resources.UpdateSuccess")); } - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl, () => RedirectToAction("List")); } public ActionResult UpdateAllStringResources() @@ -463,15 +588,16 @@ public ActionResult UpdateAllStringResources() { if (plugin.Installed) { - _localizationService.ImportPluginResourcesFromXml(plugin, null, false); + _services.Localization.ImportPluginResourcesFromXml(plugin, null, false); } else { - _localizationService.DeleteLocaleStringResources(plugin.ResourceRootKey); + _services.Localization.DeleteLocaleStringResources(plugin.ResourceRootKey); } } - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Plugins.Resources.UpdateSuccess")); + NotifySuccess(T("Admin.Configuration.Plugins.Resources.UpdateSuccess")); + return RedirectToAction("List"); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs index 4a729069c5..efbf8ad4ad 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs @@ -10,6 +10,8 @@ using SmartStore.Services.Security; using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -101,24 +103,36 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) + { + var polls = _pollService.GetPolls(0, false, command.Page - 1, command.PageSize, true); + + gridModel.Data = polls.Select(x => + { + var m = x.ToModel(); + + if (x.StartDateUtc.HasValue) + m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); + + if (x.EndDateUtc.HasValue) + m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); + + m.LanguageName = x.Language.Name; + + return m; + }); + + gridModel.Total = polls.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var polls = _pollService.GetPolls(0, false, command.Page - 1, command.PageSize, true); - var gridModel = new GridModel - { - Data = polls.Select(x => - { - var m = x.ToModel(); - if (x.StartDateUtc.HasValue) - m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); - if (x.EndDateUtc.HasValue) - m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); - m.LanguageName = x.Language.Name; - return m; - }), - Total = polls.TotalCount - }; return new JsonResult { Data = gridModel @@ -141,7 +155,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(PollModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) @@ -188,7 +202,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(PollModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) @@ -243,30 +257,35 @@ public ActionResult DeleteConfirmed(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult PollAnswers(int pollId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) - return AccessDeniedView(); + var model = new GridModel(); - var poll = _pollService.GetPollById(pollId); - if (poll == null) - throw new ArgumentException("No poll found with the specified id", "pollId"); + if (_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) + { + var poll = _pollService.GetPollById(pollId); + + var answers = poll.PollAnswers.OrderBy(x => x.DisplayOrder).ToList(); + + model.Data = answers.Select(x => + { + return new PollAnswerModel + { + Id = x.Id, + PollId = x.PollId, + Name = x.Name, + NumberOfVotes = x.NumberOfVotes, + DisplayOrder1 = x.DisplayOrder + }; + }); + + model.Total = answers.Count; + } + else + { + model.Data = Enumerable.Empty(); - var answers = poll.PollAnswers.OrderBy(x=>x.DisplayOrder).ToList(); + NotifyAccessDenied(); + } - var model = new GridModel - { - Data = answers.Select(x => - { - return new PollAnswerModel() - { - Id = x.Id, - PollId = x.PollId, - Name = x.Name, - NumberOfVotes = x.NumberOfVotes, - DisplayOrder1 = x.DisplayOrder - }; - }), - Total = answers.Count - }; return new JsonResult { Data = model @@ -277,23 +296,21 @@ public ActionResult PollAnswers(int pollId, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult PollAnswerUpdate(PollAnswerModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) - return AccessDeniedView(); - - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var pollAnswer = _pollService.GetPollAnswerById(model.Id); - var pollAnswer = _pollService.GetPollAnswerById(model.Id); - if (pollAnswer == null) - throw new ArgumentException("No poll answer found with the specified id", "id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } + + pollAnswer.Name = model.Name; + pollAnswer.DisplayOrder = model.DisplayOrder1; - pollAnswer.Name = model.Name; - pollAnswer.DisplayOrder = model.DisplayOrder1; - _pollService.UpdatePoll(pollAnswer.Poll); + _pollService.UpdatePoll(pollAnswer.Poll); + } return PollAnswers(pollAnswer.PollId, command); } @@ -301,44 +318,39 @@ public ActionResult PollAnswerUpdate(PollAnswerModel model, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult PollAnswerAdd(int pollId, PollAnswerModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) - return AccessDeniedView(); - - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + if (_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - var poll = _pollService.GetPollById(pollId); - if (poll == null) - throw new ArgumentException("No poll found with the specified id", "pollId"); + var poll = _pollService.GetPollById(pollId); - poll.PollAnswers.Add(new PollAnswer - { - Name = model.Name, - DisplayOrder = model.DisplayOrder1 - }); - _pollService.UpdatePoll(poll); + poll.PollAnswers.Add(new PollAnswer + { + Name = model.Name, + DisplayOrder = model.DisplayOrder1 + }); + + _pollService.UpdatePoll(poll); + } - return PollAnswers(poll.Id, command); + return PollAnswers(pollId, command); } [GridAction(EnableCustomBinding = true)] public ActionResult PollAnswerDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) - return AccessDeniedView(); - - var pollAnswer = _pollService.GetPollAnswerById(id); - if (pollAnswer == null) - throw new ArgumentException("No poll answer found with the specified id", "id"); - - int pollId = pollAnswer.PollId; - _pollService.DeletePollAnswer(pollAnswer); + var pollAnswer = _pollService.GetPollAnswerById(id); + var pollId = pollAnswer.PollId; + if (_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) + { + _pollService.DeletePollAnswer(pollAnswer); + } return PollAnswers(pollId, command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductAttributeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductAttributeController.cs index 98dc785b93..ea2a6ae606 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductAttributeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductAttributeController.cs @@ -7,6 +7,8 @@ using SmartStore.Core.Logging; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -89,34 +91,40 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productAttributes = _productAttributeService.GetAllProductAttributes(); + + gridModel.Data = productAttributes.Select(x => x.ToModel()); + gridModel.Total = productAttributes.Count(); + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var productAttributes = _productAttributeService.GetAllProductAttributes(); - var gridModel = new GridModel - { - Data = productAttributes.Select(x => x.ToModel()), - Total = productAttributes.Count() - }; return new JsonResult { Data = gridModel }; } - //create public ActionResult Create() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); var model = new ProductAttributeModel(); - //locales + AddLocales(_languageService, model.Locales); return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(ProductAttributeModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -161,7 +169,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(ProductAttributeModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 93585351f9..752a19c41c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -1,17 +1,15 @@ using System; using System.Collections.Generic; -using System.IO; +using System.ComponentModel; +using System.Data; using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; +using System.Web; using System.Web.Mvc; -using System.Web.Routing; using Autofac; using Newtonsoft.Json; using SmartStore.Admin.Models.Catalog; +using SmartStore.Collections; using SmartStore.Core; -using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; @@ -19,9 +17,8 @@ using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Seo; using SmartStore.Core.Events; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Services; using SmartStore.Services.Catalog; @@ -29,25 +26,24 @@ using SmartStore.Services.Customers; using SmartStore.Services.Directory; using SmartStore.Services.Discounts; -using SmartStore.Services.ExportImport; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Orders; -using SmartStore.Services.Pdf; using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Stores; using SmartStore.Services.Tax; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Mvc; -using SmartStore.Web.Framework.Pdf; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public partial class ProductController : AdminControllerBase { #region Fields @@ -67,17 +63,16 @@ public partial class ProductController : AdminControllerBase private readonly ITaxCategoryService _taxCategoryService; private readonly IProductTagService _productTagService; private readonly ICopyProductService _copyProductService; - private readonly IExportManager _exportManager; private readonly ICustomerActivityService _customerActivityService; private readonly IPermissionService _permissionService; private readonly IAclService _aclService; private readonly IStoreService _storeService; private readonly IStoreMappingService _storeMappingService; - private readonly PdfSettings _pdfSettings; private readonly AdminAreaSettings _adminAreaSettings; private readonly IDateTimeHelper _dateTimeHelper; private readonly IDiscountService _discountService; private readonly IProductAttributeService _productAttributeService; + private readonly IRepository _pvacRepository; private readonly IBackInStockSubscriptionService _backInStockSubscriptionService; private readonly IShoppingCartService _shoppingCartService; private readonly IProductAttributeFormatter _productAttributeFormatter; @@ -86,22 +81,20 @@ public partial class ProductController : AdminControllerBase private readonly IDownloadService _downloadService; private readonly IDeliveryTimeService _deliveryTimesService; private readonly IQuantityUnitService _quantityUnitService; - private readonly ICurrencyService _currencyService; - private readonly CurrencySettings _currencySettings; private readonly IMeasureService _measureService; private readonly MeasureSettings _measureSettings; private readonly IPriceFormatter _priceFormatter; private readonly IDbContext _dbContext; private readonly IEventPublisher _eventPublisher; private readonly IGenericAttributeService _genericAttributeService; - private readonly IPdfConverter _pdfConverter; private readonly ICommonServices _services; + private readonly SeoSettings _seoSettings; - #endregion + #endregion #region Constructors - public ProductController( + public ProductController( IProductService productService, IProductTemplateService productTemplateService, ICategoryService categoryService, @@ -117,19 +110,17 @@ public ProductController( ITaxCategoryService taxCategoryService, IProductTagService productTagService, ICopyProductService copyProductService, - IExportManager exportManager, - IImportManager importManager, ICustomerActivityService customerActivityService, IPermissionService permissionService, IAclService aclService, IStoreService storeService, IStoreMappingService storeMappingService, - PdfSettings pdfSettings, AdminAreaSettings adminAreaSettings, IDateTimeHelper dateTimeHelper, IDiscountService discountService, IProductAttributeService productAttributeService, - IBackInStockSubscriptionService backInStockSubscriptionService, + IRepository pvacRepository, + IBackInStockSubscriptionService backInStockSubscriptionService, IShoppingCartService shoppingCartService, IProductAttributeFormatter productAttributeFormatter, IProductAttributeParser productAttributeParser, @@ -137,17 +128,15 @@ public ProductController( IDownloadService downloadService, IDeliveryTimeService deliveryTimesService, IQuantityUnitService quantityUnitService, - ICurrencyService currencyService, - CurrencySettings currencySettings, IMeasureService measureService, MeasureSettings measureSettings, IPriceFormatter priceFormatter, IDbContext dbContext, IEventPublisher eventPublisher, IGenericAttributeService genericAttributeService, - IPdfConverter pdfConverter, - ICommonServices services) - { + ICommonServices services, + SeoSettings seoSettings) + { this._productService = productService; this._productTemplateService = productTemplateService; this._categoryService = categoryService; @@ -163,17 +152,16 @@ public ProductController( this._taxCategoryService = taxCategoryService; this._productTagService = productTagService; this._copyProductService = copyProductService; - this._exportManager = exportManager; this._customerActivityService = customerActivityService; this._permissionService = permissionService; this._aclService = aclService; this._storeService = storeService; this._storeMappingService = storeMappingService; - this._pdfSettings = pdfSettings; this._adminAreaSettings = adminAreaSettings; this._dateTimeHelper = dateTimeHelper; this._discountService = discountService; this._productAttributeService = productAttributeService; + this._pvacRepository = pvacRepository; this._backInStockSubscriptionService = backInStockSubscriptionService; this._shoppingCartService = shoppingCartService; this._productAttributeFormatter = productAttributeFormatter; @@ -182,24 +170,18 @@ public ProductController( this._downloadService = downloadService; this._deliveryTimesService = deliveryTimesService; this._quantityUnitService = quantityUnitService; - this._currencyService = currencyService; - this._currencySettings = currencySettings; this._measureService = measureService; this._measureSettings = measureSettings; this._priceFormatter = priceFormatter; this._dbContext = dbContext; this._eventPublisher = eventPublisher; this._genericAttributeService = genericAttributeService; - _pdfConverter = pdfConverter; _services = services; - - T = NullLocalizer.Instance; - } + _seoSettings = seoSettings; + } #endregion - public Localizer T { get; set; } - #region Update[...] [NonAction] @@ -224,6 +206,7 @@ protected void UpdateProductGeneralInfo(Product product, ProductModel model) p.AllowCustomerReviews = m.AllowCustomerReviews; p.ShowOnHomePage = m.ShowOnHomePage; + p.HomePageDisplayOrder = m.HomePageDisplayOrder; p.Published = m.Published; p.RequireOtherProducts = m.RequireOtherProducts; p.RequiredProductIds = m.RequiredProductIds; @@ -231,9 +214,9 @@ protected void UpdateProductGeneralInfo(Product product, ProductModel model) p.IsGiftCard = m.IsGiftCard; p.GiftCardTypeId = m.GiftCardTypeId; - + p.IsDownload = m.IsDownload; - p.DownloadId = m.DownloadId; + p.DownloadId = m.DownloadId ?? 0; p.UnlimitedDownloads = m.UnlimitedDownloads; p.MaxNumberOfDownloads = m.MaxNumberOfDownloads; p.DownloadExpirationDays = m.DownloadExpirationDays; @@ -546,13 +529,22 @@ private void UpdatePictureSeoNames(Product product) } [NonAction] - private void UpdateDataOfExistingProduct(Product product, ProductModel model) + private void UpdateDataOfExistingProduct(Product product, ProductModel model, bool editMode) { var p = product; var m = model; + var modifiedProperties = editMode ? _dbContext.GetModifiedProperties(p): new Dictionary(); + + var nameChanged = modifiedProperties.ContainsKey("Name"); + var seoTabLoaded = m.LoadedTabs.Contains("SEO", StringComparer.OrdinalIgnoreCase); + + // Handle Download transiency + MediaHelper.UpdateDownloadTransientStateFor(p, x => x.DownloadId); + MediaHelper.UpdateDownloadTransientStateFor(p, x => x.SampleDownloadId); + // SEO - m.SeName = p.ValidateSeName(m.SeName, p.Name, true); + m.SeName = p.ValidateSeName(m.SeName, p.Name, true, _urlRecordService, _seoSettings); _urlRecordService.SaveSlug(p, m.SeName, 0); foreach (var localized in model.Locales) @@ -562,13 +554,16 @@ private void UpdateDataOfExistingProduct(Product product, ProductModel model) _localizedEntityService.SaveLocalizedValue(product, x => x.FullDescription, localized.FullDescription, localized.LanguageId); // search engine name - var seName = product.ValidateSeName(localized.SeName, localized.Name, false, localized.LanguageId); - _urlRecordService.SaveSlug(product, seName, localized.LanguageId); + var localizedSeName = p.ValidateSeName(localized.SeName, localized.Name, false, _urlRecordService, _seoSettings, localized.LanguageId); + _urlRecordService.SaveSlug(p, localizedSeName, localized.LanguageId); } // picture seo names - UpdatePictureSeoNames(product); - + if (nameChanged) + { + UpdatePictureSeoNames(p); + } + // product tags UpdateProductTags(p, m.ProductTags); } @@ -641,10 +636,28 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set model.CreatedOn = _dateTimeHelper.ConvertToUserTime(product.CreatedOnUtc, DateTimeKind.Utc); model.UpdatedOn = _dateTimeHelper.ConvertToUserTime(product.UpdatedOnUtc, DateTimeKind.Utc); - model.ProductUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }, Request.Url.Scheme); + if (product.LimitedToStores) + { + var storeMappings = _storeMappingService.GetStoreMappings(product); + if (storeMappings.FirstOrDefault(x => x.StoreId == _services.StoreContext.CurrentStore.Id) == null) + { + var storeMapping = storeMappings.FirstOrDefault(); + if (storeMapping != null) + { + var store = _services.StoreService.GetStoreById(storeMapping.StoreId); + if (store != null) + model.ProductUrl = store.Url.EnsureEndsWith("/") + product.GetSeName(); + } + } + } + + if (model.ProductUrl.IsEmpty()) + { + model.ProductUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }, Request.Url.Scheme); + } } - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; model.BaseWeightIn = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name; model.BaseDimensionIn = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId).Name; @@ -656,7 +669,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set if (product != null) { model.CopyProductModel.Id = product.Id; - model.CopyProductModel.Name = "Copy of " + product.Name; + model.CopyProductModel.Name = "{0} {1}".FormatInvariant(T("Admin.Common.CopyOf"), product.Name); model.CopyProductModel.Published = true; model.CopyProductModel.CopyImages = true; } @@ -665,7 +678,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set var templates = _productTemplateService.GetAllProductTemplates(); foreach (var template in templates) { - model.AvailableProductTemplates.Add(new SelectListItem() + model.AvailableProductTemplates.Add(new SelectListItem { Text = template.Name, Value = template.Id.ToString() @@ -676,28 +689,20 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set var allTags = _productTagService.GetAllProductTagNames(); foreach (var tag in allTags) { - model.AvailableProductTags.Add(new SelectListItem() { Text = tag, Value = tag }); + model.AvailableProductTags.Add(new SelectListItem { Text = tag, Value = tag }); } if (product != null) { - var result = new StringBuilder(); - var tags = product.ProductTags.ToList(); - for (int i = 0; i < product.ProductTags.Count; i++) - { - var pt = tags[i]; - result.Append(pt.Name); - if (i != product.ProductTags.Count - 1) - result.Append(", "); - } - model.ProductTags = result.ToString(); + //var tags = product.ProductTags; + model.ProductTags = string.Join(", ", product.ProductTags.Select(x => x.Name)); } //tax categories var taxCategories = _taxCategoryService.GetAllTaxCategories(); foreach (var tc in taxCategories) { - model.AvailableTaxCategories.Add(new SelectListItem() + model.AvailableTaxCategories.Add(new SelectListItem { Text = tc.Name, Value = tc.Id.ToString(), @@ -709,7 +714,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set var deliveryTimes = _deliveryTimesService.GetAllDeliveryTimes(); foreach (var dt in deliveryTimes) { - model.AvailableDeliveryTimes.Add(new SelectListItem() + model.AvailableDeliveryTimes.Add(new SelectListItem { Text = dt.Name, Value = dt.Id.ToString(), @@ -721,7 +726,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set var quantityUnits = _quantityUnitService.GetAllQuantityUnits(); foreach (var mu in quantityUnits) { - model.AvailableQuantityUnits.Add(new SelectListItem() + model.AvailableQuantityUnits.Add(new SelectListItem { Text = mu.Name, Value = mu.Id.ToString(), @@ -741,7 +746,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set foreach (var mu in measureUnits) { - model.AvailableMeasureUnits.Add(new SelectListItem() + model.AvailableMeasureUnits.Add(new SelectListItem { Text = mu, Value = mu, @@ -754,12 +759,14 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set for (int i = 0; i < specificationAttributes.Count; i++) { var sa = specificationAttributes[i]; - model.AddSpecificationAttributeModel.AvailableAttributes.Add(new SelectListItem() { Text = sa.Name, Value = sa.Id.ToString() }); + model.AddSpecificationAttributeModel.AvailableAttributes.Add(new SelectListItem { Text = sa.Name, Value = sa.Id.ToString() }); if (i == 0) { //attribute options foreach (var sao in _specificationAttributeService.GetSpecificationAttributeOptionsBySpecificationAttribute(sa.Id)) - model.AddSpecificationAttributeModel.AvailableOptions.Add(new SelectListItem() { Text = sao.Name, Value = sao.Id.ToString() }); + { + model.AddSpecificationAttributeModel.AvailableOptions.Add(new SelectListItem { Text = sao.Name, Value = sao.Id.ToString() }); + } } } //default specs values @@ -779,7 +786,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set foreach (var inventoryMethod in inventoryMethods) { - model.AvailableManageInventoryMethods.Add(new SelectListItem() + model.AvailableManageInventoryMethods.Add(new SelectListItem { Value = ((int)inventoryMethod).ToString(), Text = inventoryMethod.GetLocalizedEnum(_localizationService, _workContext), @@ -820,20 +827,6 @@ private void PrepareProductPictureThumbnailModel(ProductModel model, Product pro } } - [NonAction] - private string[] ParseProductTags(string productTags) - { - var result = new List(); - if (!String.IsNullOrWhiteSpace(productTags)) - { - string[] values = productTags.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - foreach (string val1 in values) - if (!String.IsNullOrEmpty(val1.Trim())) - result.Add(val1.Trim()); - } - return result.ToArray(); - } - #endregion Utitilies #region Methods @@ -870,106 +863,121 @@ public ActionResult Index() return RedirectToAction("List"); } - public ActionResult List() + public ActionResult List(ProductListModel model) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - var ctx = new ProductSearchContext(); - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageSize = _adminAreaSettings.GridPageSize; - ctx.ShowHidden = true; - - var products = _productService.SearchProducts(ctx); + var allStores = _storeService.GetAllStores(); - var model = new ProductListModel(); model.DisplayProductPictures = _adminAreaSettings.DisplayProductPictures; - model.DisplayPdfExport = _pdfSettings.Enabled; model.GridPageSize = _adminAreaSettings.GridPageSize; - model.Products = new GridModel - { - Data = products.Select(x => - { - var productModel = x.ToModel(); - PrepareProductPictureThumbnailModel(productModel, x); - - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - - return productModel; - }), - Total = products.TotalCount - }; - - //categories var allCategories = _categoryService.GetAllCategories(showHidden: true); var mappedCategories = allCategories.ToDictionary(x => x.Id); foreach (var c in allCategories) { - model.AvailableCategories.Add(new SelectListItem() { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); + model.AvailableCategories.Add(new SelectListItem { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); } - //manufacturers foreach (var m in _manufacturerService.GetAllManufacturers(true)) { - model.AvailableManufacturers.Add(new SelectListItem() { Text = m.Name, Value = m.Id.ToString() }); + model.AvailableManufacturers.Add(new SelectListItem { Text = m.Name, Value = m.Id.ToString() }); } - //stores - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var s in _storeService.GetAllStores()) + foreach (var s in allStores) { - model.AvailableStores.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString() }); + model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); } - //product types model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); return View(model); } - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult ProductList(GridCommand command, ProductListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + [HttpPost, GridAction(EnableCustomBinding = true)] + public ActionResult ProductList(GridCommand command, ProductListModel model) + { + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var searchContext = new ProductSearchContext + { + ManufacturerId = model.SearchManufacturerId, + StoreId = model.SearchStoreId, + Keywords = model.SearchProductName, + SearchSku = !_catalogSettings.SuppressSkuSearch, + LanguageId = _workContext.WorkingLanguage.Id, + OrderBy = ProductSortingEnum.Position, + PageIndex = command.Page - 1, + PageSize = command.PageSize, + ShowHidden = true, + ProductType = (model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null), + WithoutCategories = model.SearchWithoutCategories, + WithoutManufacturers = model.SearchWithoutManufacturers, + IsPublished = model.SearchIsPublished, + HomePageProducts = model.SearchHomePageProducts + }; + + if (model.SearchCategoryId > 0) + searchContext.CategoryIds.Add(model.SearchCategoryId); + + if (command.SortDescriptors != null && command.SortDescriptors.Count > 0) + { + var sort = command.SortDescriptors.First(); + if (sort.Member == "Name") + { + searchContext.OrderBy = (sort.SortDirection == ListSortDirection.Ascending ? ProductSortingEnum.NameAsc : ProductSortingEnum.NameDesc); + } + else if (sort.Member == "Price") + { + searchContext.OrderBy = (sort.SortDirection == ListSortDirection.Ascending ? ProductSortingEnum.PriceAsc : ProductSortingEnum.PriceDesc); + } + else if (sort.Member == "CreatedOn") + { + searchContext.OrderBy = (sort.SortDirection == ListSortDirection.Ascending ? ProductSortingEnum.CreatedOnAsc : ProductSortingEnum.CreatedOn); + } + } - var gridModel = new GridModel(); + var products = _productService.SearchProducts(searchContext); + + gridModel.Data = products.Select(x => + { + var productModel = new ProductModel + { + Sku = x.Sku, + Published = x.Published, + ProductTypeLabelHint = x.ProductTypeLabelHint, + Name = x.Name, + Id = x.Id, + StockQuantity = x.StockQuantity, + Price = x.Price, + LimitedToStores = x.LimitedToStores + }; - var ctx = new ProductSearchContext(); + PrepareProductPictureThumbnailModel(productModel, x); - if (model.SearchCategoryId > 0) - ctx.CategoryIds.Add(model.SearchCategoryId); + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + productModel.UpdatedOn = _dateTimeHelper.ConvertToUserTime(x.UpdatedOnUtc, DateTimeKind.Utc); + productModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - ctx.ManufacturerId = model.SearchManufacturerId; - ctx.StoreId = model.SearchStoreId; - ctx.Keywords = model.SearchProductName; - ctx.SearchSku = !_catalogSettings.SuppressSkuSearch; - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageIndex = command.Page - 1; - ctx.PageSize = command.PageSize; - ctx.ShowHidden = true; - ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; + return productModel; + }); - var products = _productService.SearchProducts(ctx); - gridModel.Data = products.Select(x => - { - var productModel = x.ToModel(); - productModel.FullDescription = ""; // Perf - PrepareProductPictureThumbnailModel(productModel, x); + gridModel.Total = products.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + NotifyAccessDenied(); + } - return productModel; - }); - gridModel.Total = products.TotalCount; - return new JsonResult - { - Data = gridModel - }; + return new JsonResult + { + Data = gridModel + }; } [HttpPost, ActionName("List")] @@ -977,12 +985,20 @@ public ActionResult ProductList(GridCommand command, ProductListModel model) public ActionResult GoToSku(ProductListModel model) { string sku = model.GoDirectlyToSku; - var product = _productService.GetProductBySku(sku); - if (product != null) - return RedirectToAction("Edit", "Product", new { id = product.Id }); + + if (sku.HasValue()) + { + var product = _productService.GetProductBySku(sku); + if (product != null) + return RedirectToAction("Edit", "Product", new { id = product.Id }); + + var combination = _productAttributeService.GetProductVariantAttributeCombinationBySku(sku); + if (combination != null && combination.Product != null && !combination.Product.Deleted) + return RedirectToAction("Edit", "Product", new { id = combination.Product.Id }); + } //not found - return List(); + return List(model); } //create product @@ -1002,7 +1018,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [ValidateInput(false)] public ActionResult Create(ProductModel model, bool continueEditing, FormCollection form) { @@ -1032,7 +1048,7 @@ public ActionResult Create(ProductModel model, bool continueEditing, FormCollect _productService.InsertProduct(product); - UpdateDataOfExistingProduct(product, model); + UpdateDataOfExistingProduct(product, model, false); //activity log _customerActivityService.InsertActivity("AddNewProduct", _localizationService.GetResource("ActivityLog.AddNewProduct"), product.Name); @@ -1092,7 +1108,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [ValidateInput(false)] public ActionResult Edit(ProductModel model, bool continueEditing, FormCollection form) { @@ -1110,7 +1126,7 @@ public ActionResult Edit(ProductModel model, bool continueEditing, FormCollectio if (ModelState.IsValid) { MapModelToProduct(model, product, form); - UpdateDataOfExistingProduct(product, model); + UpdateDataOfExistingProduct(product, model, true); // activity log _customerActivityService.InsertActivity("EditProduct", _localizationService.GetResource("ActivityLog.EditProduct"), product.Name); @@ -1138,7 +1154,7 @@ protected void MapModelToProduct(ProductModel model, Product product, FormCollec foreach (var tab in model.LoadedTabs) { - switch (tab.ToLower()) + switch (tab.ToLowerInvariant()) { case "info": UpdateProductGeneralInfo(product, model); @@ -1270,12 +1286,14 @@ public ActionResult CopyProduct(ProductModel model) { var product = _productService.GetProductById(copyModel.Id); var newProduct = _copyProductService.CopyProduct(product, copyModel.Name, copyModel.Published, copyModel.CopyImages); - NotifySuccess("The product is copied"); + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + return RedirectToAction("Edit", new { id = newProduct.Id }); } catch (Exception exc) { - NotifyError(exc.Message); + NotifyError(exc.ToAllMessages()); return RedirectToAction("Edit", new { id = copyModel.Id }); } } @@ -1287,30 +1305,35 @@ public ActionResult CopyProduct(ProductModel model) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductCategoryList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var productCategories = _categoryService.GetProductCategoriesByProductId(productId, true); - var productCategoriesModel = productCategories - .Select(x => - { - return new ProductModel.ProductCategoryModel() - { - Id = x.Id, - Category = _categoryService.GetCategoryById(x.CategoryId).GetCategoryBreadCrumb(_categoryService), - ProductId = x.ProductId, - CategoryId = x.CategoryId, - IsFeaturedProduct = x.IsFeaturedProduct, - DisplayOrder = x.DisplayOrder - }; - }) - .ToList(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productCategories = _categoryService.GetProductCategoriesByProductId(productId, true); + var productCategoriesModel = productCategories + .Select(x => + { + return new ProductModel.ProductCategoryModel() + { + Id = x.Id, + Category = _categoryService.GetCategoryById(x.CategoryId).GetCategoryBreadCrumb(_categoryService), + ProductId = x.ProductId, + CategoryId = x.CategoryId, + IsFeaturedProduct = x.IsFeaturedProduct, + DisplayOrder = x.DisplayOrder + }; + }) + .ToList(); - var model = new GridModel - { - Data = productCategoriesModel, - Total = productCategoriesModel.Count - }; + model.Data = productCategoriesModel; + model.Total = productCategoriesModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1321,23 +1344,23 @@ public ActionResult ProductCategoryList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult ProductCategoryInsert(GridCommand command, ProductModel.ProductCategoryModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productCategory = new ProductCategory + { + ProductId = model.ProductId, + CategoryId = Int32.Parse(model.Category), //use Category property (not CategoryId) because appropriate property is stored in it + IsFeaturedProduct = model.IsFeaturedProduct, + DisplayOrder = model.DisplayOrder + }; - var productCategory = new ProductCategory() - { - ProductId = model.ProductId, - CategoryId = Int32.Parse(model.Category), //use Category property (not CategoryId) because appropriate property is stored in it - IsFeaturedProduct = model.IsFeaturedProduct, - DisplayOrder = model.DisplayOrder - }; - _categoryService.InsertProductCategory(productCategory); + _categoryService.InsertProductCategory(productCategory); - - var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedCategories), - model.Category, _catalogSettings.MostRecentlyUsedCategoriesMaxSize); + var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedCategories), + model.Category, _catalogSettings.MostRecentlyUsedCategoriesMaxSize); - _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedCategories, mru.ToString()); + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedCategories, mru.ToString()); + } return ProductCategoryList(command, model.ProductId); } @@ -1345,27 +1368,25 @@ public ActionResult ProductCategoryInsert(GridCommand command, ProductModel.Prod [GridAction(EnableCustomBinding = true)] public ActionResult ProductCategoryUpdate(GridCommand command, ProductModel.ProductCategoryModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productCategory = _categoryService.GetProductCategoryById(model.Id); - if (productCategory == null) - throw new ArgumentException("No product category mapping found with the specified id"); + var productCategory = _categoryService.GetProductCategoryById(model.Id); - bool categoryChanged = (Int32.Parse(model.Category) != productCategory.CategoryId); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var categoryChanged = (Int32.Parse(model.Category) != productCategory.CategoryId); - //use Category property (not CategoryId) because appropriate property is stored in it - productCategory.CategoryId = Int32.Parse(model.Category); - productCategory.IsFeaturedProduct = model.IsFeaturedProduct; - productCategory.DisplayOrder = model.DisplayOrder; - _categoryService.UpdateProductCategory(productCategory); + //use Category property (not CategoryId) because appropriate property is stored in it + productCategory.CategoryId = Int32.Parse(model.Category); + productCategory.IsFeaturedProduct = model.IsFeaturedProduct; + productCategory.DisplayOrder = model.DisplayOrder; + _categoryService.UpdateProductCategory(productCategory); - if (categoryChanged) - { - var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedCategories), - model.Category, _catalogSettings.MostRecentlyUsedCategoriesMaxSize); + if (categoryChanged) + { + var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedCategories), + model.Category, _catalogSettings.MostRecentlyUsedCategoriesMaxSize); - _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedCategories, mru.ToString()); + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedCategories, mru.ToString()); + } } return ProductCategoryList(command, productCategory.ProductId); @@ -1374,15 +1395,13 @@ public ActionResult ProductCategoryUpdate(GridCommand command, ProductModel.Prod [GridAction(EnableCustomBinding = true)] public ActionResult ProductCategoryDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productCategory = _categoryService.GetProductCategoryById(id); - if (productCategory == null) - throw new ArgumentException("No product category mapping found with the specified id"); + var productCategory = _categoryService.GetProductCategoryById(id); + var productId = productCategory.ProductId; - var productId = productCategory.ProductId; - _categoryService.DeleteProductCategory(productCategory); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _categoryService.DeleteProductCategory(productCategory); + } return ProductCategoryList(command, productId); } @@ -1394,30 +1413,35 @@ public ActionResult ProductCategoryDelete(int id, GridCommand command) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductManufacturerList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var productManufacturers = _manufacturerService.GetProductManufacturersByProductId(productId, true); - var productManufacturersModel = productManufacturers - .Select(x => - { - return new ProductModel.ProductManufacturerModel() - { - Id = x.Id, - Manufacturer = _manufacturerService.GetManufacturerById(x.ManufacturerId).Name, - ProductId = x.ProductId, - ManufacturerId = x.ManufacturerId, - IsFeaturedProduct = x.IsFeaturedProduct, - DisplayOrder = x.DisplayOrder - }; - }) - .ToList(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productManufacturers = _manufacturerService.GetProductManufacturersByProductId(productId, true); + var productManufacturersModel = productManufacturers + .Select(x => + { + return new ProductModel.ProductManufacturerModel + { + Id = x.Id, + Manufacturer = _manufacturerService.GetManufacturerById(x.ManufacturerId).Name, + ProductId = x.ProductId, + ManufacturerId = x.ManufacturerId, + IsFeaturedProduct = x.IsFeaturedProduct, + DisplayOrder = x.DisplayOrder + }; + }) + .ToList(); - var model = new GridModel - { - Data = productManufacturersModel, - Total = productManufacturersModel.Count - }; + model.Data = productManufacturersModel; + model.Total = productManufacturersModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1428,17 +1452,23 @@ public ActionResult ProductManufacturerList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult ProductManufacturerInsert(GridCommand command, ProductModel.ProductManufacturerModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productManufacturer = new ProductManufacturer + { + ProductId = model.ProductId, + ManufacturerId = Int32.Parse(model.Manufacturer), //use Manufacturer property (not ManufacturerId) because appropriate property is stored in it + IsFeaturedProduct = model.IsFeaturedProduct, + DisplayOrder = model.DisplayOrder + }; - var productManufacturer = new ProductManufacturer() - { - ProductId = model.ProductId, - ManufacturerId = Int32.Parse(model.Manufacturer), //use Manufacturer property (not ManufacturerId) because appropriate property is stored in it - IsFeaturedProduct = model.IsFeaturedProduct, - DisplayOrder = model.DisplayOrder - }; - _manufacturerService.InsertProductManufacturer(productManufacturer); + _manufacturerService.InsertProductManufacturer(productManufacturer); + + var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedManufacturers), + model.Manufacturer, _catalogSettings.MostRecentlyUsedManufacturersMaxSize); + + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedManufacturers, mru.ToString()); + } return ProductManufacturerList(command, model.ProductId); } @@ -1446,18 +1476,27 @@ public ActionResult ProductManufacturerInsert(GridCommand command, ProductModel. [GridAction(EnableCustomBinding = true)] public ActionResult ProductManufacturerUpdate(GridCommand command, ProductModel.ProductManufacturerModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var productManufacturer = _manufacturerService.GetProductManufacturerById(model.Id); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var manufacturerChanged = (Int32.Parse(model.Manufacturer) != productManufacturer.ManufacturerId); - var productManufacturer = _manufacturerService.GetProductManufacturerById(model.Id); - if (productManufacturer == null) - throw new ArgumentException("No product manufacturer mapping found with the specified id"); + //use Manufacturer property (not ManufacturerId) because appropriate property is stored in it + productManufacturer.ManufacturerId = Int32.Parse(model.Manufacturer); + productManufacturer.IsFeaturedProduct = model.IsFeaturedProduct; + productManufacturer.DisplayOrder = model.DisplayOrder; - //use Manufacturer property (not ManufacturerId) because appropriate property is stored in it - productManufacturer.ManufacturerId = Int32.Parse(model.Manufacturer); - productManufacturer.IsFeaturedProduct = model.IsFeaturedProduct; - productManufacturer.DisplayOrder = model.DisplayOrder; - _manufacturerService.UpdateProductManufacturer(productManufacturer); + _manufacturerService.UpdateProductManufacturer(productManufacturer); + + if (manufacturerChanged) + { + var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedManufacturers), + model.Manufacturer, _catalogSettings.MostRecentlyUsedManufacturersMaxSize); + + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedManufacturers, mru.ToString()); + } + } return ProductManufacturerList(command, productManufacturer.ProductId); } @@ -1465,15 +1504,13 @@ public ActionResult ProductManufacturerUpdate(GridCommand command, ProductModel. [GridAction(EnableCustomBinding = true)] public ActionResult ProductManufacturerDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productManufacturer = _manufacturerService.GetProductManufacturerById(id); - if (productManufacturer == null) - throw new ArgumentException("No product manufacturer mapping found with the specified id"); + var productManufacturer = _manufacturerService.GetProductManufacturerById(id); + var productId = productManufacturer.ProductId; - var productId = productManufacturer.ProductId; - _manufacturerService.DeleteProductManufacturer(productManufacturer); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _manufacturerService.DeleteProductManufacturer(productManufacturer); + } return ProductManufacturerList(command, productId); } @@ -1485,34 +1522,39 @@ public ActionResult ProductManufacturerDelete(int id, GridCommand command) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult RelatedProductList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var relatedProducts = _productService.GetRelatedProductsByProductId1(productId, true); - var relatedProductsModel = relatedProducts - .Select(x => - { - var product2 = _productService.GetProductById(x.ProductId2); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var relatedProducts = _productService.GetRelatedProductsByProductId1(productId, true); + var relatedProductsModel = relatedProducts + .Select(x => + { + var product2 = _productService.GetProductById(x.ProductId2); - return new ProductModel.RelatedProductModel() - { - Id = x.Id, - ProductId2 = x.ProductId2, - Product2Name = product2.Name, - ProductTypeName = product2.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = product2.ProductTypeLabelHint, - DisplayOrder = x.DisplayOrder, - Product2Sku = product2.Sku, - Product2Published = product2.Published - }; - }) - .ToList(); + return new ProductModel.RelatedProductModel() + { + Id = x.Id, + ProductId2 = x.ProductId2, + Product2Name = product2.Name, + ProductTypeName = product2.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = product2.ProductTypeLabelHint, + DisplayOrder = x.DisplayOrder, + Product2Sku = product2.Sku, + Product2Published = product2.Published + }; + }) + .ToList(); - var model = new GridModel - { - Data = relatedProductsModel, - Total = relatedProductsModel.Count - }; + model.Data = relatedProductsModel; + model.Total = relatedProductsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1523,15 +1565,13 @@ public ActionResult RelatedProductList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult RelatedProductUpdate(GridCommand command, ProductModel.RelatedProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var relatedProduct = _productService.GetRelatedProductById(model.Id); - var relatedProduct = _productService.GetRelatedProductById(model.Id); - if (relatedProduct == null) - throw new ArgumentException("No related product found with the specified id"); - - relatedProduct.DisplayOrder = model.DisplayOrder; - _productService.UpdateRelatedProduct(relatedProduct); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + relatedProduct.DisplayOrder = model.DisplayOrder; + _productService.UpdateRelatedProduct(relatedProduct); + } return RelatedProductList(command, relatedProduct.ProductId1); } @@ -1539,145 +1579,79 @@ public ActionResult RelatedProductUpdate(GridCommand command, ProductModel.Relat [GridAction(EnableCustomBinding = true)] public ActionResult RelatedProductDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var relatedProduct = _productService.GetRelatedProductById(id); - if (relatedProduct == null) - throw new ArgumentException("No related product found with the specified id"); + var relatedProduct = _productService.GetRelatedProductById(id); + var productId = relatedProduct.ProductId1; - var productId = relatedProduct.ProductId1; - _productService.DeleteRelatedProduct(relatedProduct); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productService.DeleteRelatedProduct(relatedProduct); + } return RelatedProductList(command, productId); } - public ActionResult RelatedProductAddPopup(int productId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var ctx = new ProductSearchContext(); - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageSize = _adminAreaSettings.GridPageSize; - ctx.ShowHidden = true; - - var products = _productService.SearchProducts(ctx); + [HttpPost] + public ActionResult RelatedProductAdd(int productId, string selectedProductIds) + { + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productIds = selectedProductIds.SplitSafe(",").Select(x => x.ToInt()).ToArray(); + var products = _productService.GetProductsByIds(productIds); + RelatedProduct relation = null; + var maxDisplayOrder = -1; - var model = new ProductModel.AddRelatedProductModel(); - model.Products = new GridModel - { - Data = products.Select(x => + foreach (var product in products) { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - - return productModel; - }), - Total = products.TotalCount - }; - - //categories - var allCategories = _categoryService.GetAllCategories(showHidden: true); - var mappedCategories = allCategories.ToDictionary(x => x.Id); - foreach (var c in allCategories) - { - model.AvailableCategories.Add(new SelectListItem() { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); - } - - //manufacturers - foreach (var m in _manufacturerService.GetAllManufacturers(true)) - { - model.AvailableManufacturers.Add(new SelectListItem() { Text = m.Name, Value = m.Id.ToString() }); - } - - //stores - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var s in _storeService.GetAllStores()) - model.AvailableStores.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString() }); - - //product types - model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - return View(model); - } - - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult RelatedProductAddPopupList(GridCommand command, ProductModel.AddRelatedProductModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var existingRelations = _productService.GetRelatedProductsByProductId1(productId); - var gridModel = new GridModel(); + if (existingRelations.FindRelatedProduct(productId, product.Id) == null) + { + if (maxDisplayOrder == -1 && (relation = existingRelations.OrderByDescending(x => x.DisplayOrder).FirstOrDefault()) != null) + { + maxDisplayOrder = relation.DisplayOrder; + } - var ctx = new ProductSearchContext(); + _productService.InsertRelatedProduct(new RelatedProduct + { + ProductId1 = productId, + ProductId2 = product.Id, + DisplayOrder = ++maxDisplayOrder + }); + } + } + } + else + { + NotifyAccessDenied(); + } - if (model.SearchCategoryId > 0) - ctx.CategoryIds.Add(model.SearchCategoryId); + return new EmptyResult(); + } - ctx.ManufacturerId = model.SearchManufacturerId; - ctx.StoreId = model.SearchStoreId; - ctx.Keywords = model.SearchProductName; - ctx.SearchSku = !_catalogSettings.SuppressSkuSearch; - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageIndex = command.Page - 1; - ctx.PageSize = command.PageSize; - ctx.ShowHidden = true; - ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; + [HttpPost, ValidateInput(false)] + public ActionResult CreateAllMutuallyRelatedProducts(int productId) + { + string message = null; - var products = _productService.SearchProducts(ctx); - gridModel.Data = products.Select(x => + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - - return productModel; - }); - gridModel.Total = products.TotalCount; - return new JsonResult - { - Data = gridModel - }; - } - - [HttpPost] - [FormValueRequired("save")] - public ActionResult RelatedProductAddPopup(string btnId, string formId, ProductModel.AddRelatedProductModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - if (model.SelectedProductIds != null) - { - foreach (int id in model.SelectedProductIds) - { - var product = _productService.GetProductById(id); - if (product != null) - { - var existingRelatedProducts = _productService.GetRelatedProductsByProductId1(model.ProductId); - if (existingRelatedProducts.FindRelatedProduct(model.ProductId, id) == null) - { - _productService.InsertRelatedProduct( - new RelatedProduct() - { - ProductId1 = model.ProductId, - ProductId2 = id, - DisplayOrder = 1 - }); - } - } - } - } + var product = _productService.GetProductById(productId); + if (product != null) + { + var count = _productService.EnsureMutuallyRelatedProducts(productId); + message = T("Admin.Common.CreateMutuallyAssociationsResult", count); + } + } + else + { + message = T("Admin.AccessDenied.Title"); + } - ViewBag.RefreshPage = true; - ViewBag.btnId = btnId; - ViewBag.formId = formId; - model.Products = new GridModel(); - return View(model); - } + return new JsonResult + { + Data = new { Message = message } + }; + } #endregion @@ -1686,33 +1660,38 @@ public ActionResult RelatedProductAddPopup(string btnId, string formId, ProductM [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CrossSellProductList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var crossSellProducts = _productService.GetCrossSellProductsByProductId1(productId, true); - var crossSellProductsModel = crossSellProducts - .Select(x => - { - var product2 = _productService.GetProductById(x.ProductId2); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var crossSellProducts = _productService.GetCrossSellProductsByProductId1(productId, true); + var crossSellProductsModel = crossSellProducts + .Select(x => + { + var product2 = _productService.GetProductById(x.ProductId2); - return new ProductModel.CrossSellProductModel() - { - Id = x.Id, - ProductId2 = x.ProductId2, - Product2Name = product2.Name, - ProductTypeName = product2.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = product2.ProductTypeLabelHint, - Product2Sku = product2.Sku, - Product2Published = product2.Published - }; - }) - .ToList(); + return new ProductModel.CrossSellProductModel + { + Id = x.Id, + ProductId2 = x.ProductId2, + Product2Name = product2.Name, + ProductTypeName = product2.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = product2.ProductTypeLabelHint, + Product2Sku = product2.Sku, + Product2Published = product2.Published + }; + }) + .ToList(); - var model = new GridModel - { - Data = crossSellProductsModel, - Total = crossSellProductsModel.Count - }; + model.Data = crossSellProductsModel; + model.Total = crossSellProductsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1723,145 +1702,71 @@ public ActionResult CrossSellProductList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult CrossSellProductDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var crossSellProduct = _productService.GetCrossSellProductById(id); - if (crossSellProduct == null) - throw new ArgumentException("No cross-sell product found with the specified id"); + var crossSellProduct = _productService.GetCrossSellProductById(id); + var productId = crossSellProduct.ProductId1; - var productId = crossSellProduct.ProductId1; - _productService.DeleteCrossSellProduct(crossSellProduct); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productService.DeleteCrossSellProduct(crossSellProduct); + } return CrossSellProductList(command, productId); } - public ActionResult CrossSellProductAddPopup(int productId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var ctx = new ProductSearchContext(); - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageSize = _adminAreaSettings.GridPageSize; - ctx.ShowHidden = true; - - var products = _productService.SearchProducts(ctx); + [HttpPost] + public ActionResult CrossSellProductAdd(int productId, string selectedProductIds) + { + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productIds = selectedProductIds.SplitSafe(",").Select(x => x.ToInt()).ToArray(); + var products = _productService.GetProductsByIds(productIds); - var model = new ProductModel.AddCrossSellProductModel(); - model.Products = new GridModel - { - Data = products.Select(x => + foreach (var product in products) { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - - return productModel; - }), - Total = products.TotalCount - }; - - //categories - var allCategories = _categoryService.GetAllCategories(showHidden: true); - var mappedCategories = allCategories.ToDictionary(x => x.Id); - foreach (var c in allCategories) - { - model.AvailableCategories.Add(new SelectListItem() { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); - } - - //manufacturers - foreach (var m in _manufacturerService.GetAllManufacturers(true)) - { - model.AvailableManufacturers.Add(new SelectListItem() { Text = m.Name, Value = m.Id.ToString() }); - } - - //stores - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var s in _storeService.GetAllStores()) - model.AvailableStores.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString() }); - - //product types - model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - return View(model); - } - - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult CrossSellProductAddPopupList(GridCommand command, ProductModel.AddCrossSellProductModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var gridModel = new GridModel(); + var existingRelations = _productService.GetCrossSellProductsByProductId1(productId); - var ctx = new ProductSearchContext(); - - if (model.SearchCategoryId > 0) - ctx.CategoryIds.Add(model.SearchCategoryId); - - ctx.ManufacturerId = model.SearchManufacturerId; - ctx.StoreId = model.SearchStoreId; - ctx.Keywords = model.SearchProductName; - ctx.SearchSku = !_catalogSettings.SuppressSkuSearch; - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageIndex = command.Page - 1; - ctx.PageSize = command.PageSize; - ctx.ShowHidden = true; - ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; - - var products = _productService.SearchProducts(ctx); - - gridModel.Data = products.Select(x => + if (existingRelations.FindCrossSellProduct(productId, product.Id) == null) + { + _productService.InsertCrossSellProduct(new CrossSellProduct + { + ProductId1 = productId, + ProductId2 = product.Id + }); + } + } + } + else { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + NotifyAccessDenied(); + } - return productModel; - }); - gridModel.Total = products.TotalCount; - return new JsonResult - { - Data = gridModel - }; - } + return new EmptyResult(); + } - [HttpPost] - [FormValueRequired("save")] - public ActionResult CrossSellProductAddPopup(string btnId, string formId, ProductModel.AddCrossSellProductModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + [HttpPost, ValidateInput(false)] + public ActionResult CreateAllMutuallyCrossSellProducts(int productId) + { + string message = null; - if (model.SelectedProductIds != null) - { - foreach (int id in model.SelectedProductIds) - { - var product = _productService.GetProductById(id); - if (product != null) - { - var existingCrossSellProducts = _productService.GetCrossSellProductsByProductId1(model.ProductId); - if (existingCrossSellProducts.FindCrossSellProduct(model.ProductId, id) == null) - { - _productService.InsertCrossSellProduct( - new CrossSellProduct() - { - ProductId1 = model.ProductId, - ProductId2 = id, - }); - } - } - } - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var product = _productService.GetProductById(productId); + if (product != null) + { + var count = _productService.EnsureMutuallyCrossSellProducts(productId); + message = T("Admin.Common.CreateMutuallyAssociationsResult", count); + } + } + else + { + message = T("Admin.AccessDenied.Title"); + } - ViewBag.RefreshPage = true; - ViewBag.btnId = btnId; - ViewBag.formId = formId; - model.Products = new GridModel(); - return View(model); - } + return new JsonResult + { + Data = new { Message = message } + }; + } #endregion @@ -1870,38 +1775,44 @@ public ActionResult CrossSellProductAddPopup(string btnId, string formId, Produc [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult AssociatedProductList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var searchContext = new ProductSearchContext() + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - ParentGroupedProductId = productId, - PageSize = int.MaxValue, - ShowHidden = true - }; - - var associatedProducts = _productService.SearchProducts(searchContext); - var associatedProductsModel = associatedProducts - .Select(x => + var searchContext = new ProductSearchContext { - return new ProductModel.AssociatedProductModel() + OrderBy = ProductSortingEnum.Position, + ParentGroupedProductId = productId, + PageSize = int.MaxValue, + ShowHidden = true + }; + + var associatedProducts = _productService.SearchProducts(searchContext); + var associatedProductsModel = associatedProducts + .Select(x => { - Id = x.Id, - ProductName = x.Name, - ProductTypeName = x.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = x.ProductTypeLabelHint, - DisplayOrder = x.DisplayOrder, - Sku = x.Sku, - Published = x.Published - }; - }) - .ToList(); + return new ProductModel.AssociatedProductModel + { + Id = x.Id, + ProductName = x.Name, + ProductTypeName = x.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = x.ProductTypeLabelHint, + DisplayOrder = x.DisplayOrder, + Sku = x.Sku, + Published = x.Published + }; + }) + .ToList(); - var model = new GridModel + model.Data = associatedProductsModel; + model.Total = associatedProductsModel.Count; + } + else { - Data = associatedProductsModel, - Total = associatedProductsModel.Count - }; + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1912,15 +1823,13 @@ public ActionResult AssociatedProductList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult AssociatedProductUpdate(GridCommand command, ProductModel.AssociatedProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var associatedProduct = _productService.GetProductById(model.Id); - if (associatedProduct == null) - throw new ArgumentException("No associated product found with the specified id"); - associatedProduct.DisplayOrder = model.DisplayOrder; - _productService.UpdateProduct(associatedProduct); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + associatedProduct.DisplayOrder = model.DisplayOrder; + _productService.UpdateProduct(associatedProduct); + } return AssociatedProductList(command, associatedProduct.ParentGroupedProductId); } @@ -1928,117 +1837,54 @@ public ActionResult AssociatedProductUpdate(GridCommand command, ProductModel.As [GridAction(EnableCustomBinding = true)] public ActionResult AssociatedProductDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var product = _productService.GetProductById(id); - if (product == null) - throw new ArgumentException("No associated product found with the specified id"); - var originalParentGroupedProductId = product.ParentGroupedProductId; - product.ParentGroupedProductId = 0; - _productService.UpdateProduct(product); - - return AssociatedProductList(command, originalParentGroupedProductId); - } - - public ActionResult AssociatedProductAddPopup(int productId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var model = new ProductModel.AddAssociatedProductModel(); - - //categories - var allCategories = _categoryService.GetAllCategories(showHidden: true); - var mappedCategories = allCategories.ToDictionary(x => x.Id); - foreach (var c in allCategories) - { - model.AvailableCategories.Add(new SelectListItem() { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); - } - - //manufacturers - foreach (var m in _manufacturerService.GetAllManufacturers(true)) - { - model.AvailableManufacturers.Add(new SelectListItem() { Text = m.Name, Value = m.Id.ToString() }); - } - - //stores - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var s in _storeService.GetAllStores()) + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - model.AvailableStores.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString() }); + product.ParentGroupedProductId = 0; + _productService.UpdateProduct(product); } - //product types - model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - return View(model); + return AssociatedProductList(command, originalParentGroupedProductId); } - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult AssociatedProductAddPopupList(GridCommand command, ProductModel.AddAssociatedProductModel model) + [HttpPost] + public ActionResult AssociatedProductAdd(int productId, string selectedProductIds) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var searchContext = new ProductSearchContext() - { - CategoryIds = new List() { model.SearchCategoryId }, - ManufacturerId = model.SearchManufacturerId, - StoreId = model.SearchStoreId, - Keywords = model.SearchProductName, - SearchSku = !_catalogSettings.SuppressSkuSearch, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - ShowHidden = true, - ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null - }; - - var products = _productService.SearchProducts(searchContext); - - var gridModel = new GridModel(); - gridModel.Data = products.Select(x => + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - - return productModel; - }); - gridModel.Total = products.TotalCount; + var searchContext = new ProductSearchContext + { + ParentGroupedProductId = productId, + PageSize = 1, + ShowHidden = true + }; - return new JsonResult - { - Data = gridModel - }; - } + var maxDisplayOrder = _productService.PrepareProductSearchQuery(searchContext, x => x.DisplayOrder) + .OrderByDescending(x => x) + .FirstOrDefault(); - [HttpPost] - [FormValueRequired("save")] - public ActionResult AssociatedProductAddPopup(string btnId, string formId, ProductModel.AddAssociatedProductModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var productIds = selectedProductIds.SplitSafe(",").Select(x => x.ToInt()).ToArray(); + var products = _productService.GetProductsByIds(productIds); - if (model.SelectedProductIds != null) - { - foreach (int id in model.SelectedProductIds) + foreach (var product in products) { - var product = _productService.GetProductById(id); - if (product != null) + if (product.ParentGroupedProductId != productId) { - product.ParentGroupedProductId = model.ProductId; + product.ParentGroupedProductId = productId; + product.DisplayOrder = ++maxDisplayOrder; + _productService.UpdateProduct(product); } } } + else + { + NotifyAccessDenied(); + } - ViewBag.RefreshPage = true; - ViewBag.btnId = btnId; - ViewBag.formId = formId; - return View(model); + return new EmptyResult(); } #endregion @@ -2146,34 +1992,39 @@ private void SaveFilteredAttributes(ProductBundleItem bundleItem, FormCollection [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult BundleItemList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var bundleItems = _productService.GetBundleItems(productId, true).Select(x => x.Item); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var bundleItems = _productService.GetBundleItems(productId, true).Select(x => x.Item); - var bundleItemsModel = bundleItems.Select(x => - { - return new ProductModel.BundleItemModel() + var bundleItemsModel = bundleItems.Select(x => { - Id = x.Id, - ProductId = x.Product.Id, - ProductName = x.Product.Name, - ProductTypeName = x.Product.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = x.Product.ProductTypeLabelHint, - Sku = x.Product.Sku, - Quantity = x.Quantity, - Discount = x.Discount, - DisplayOrder = x.DisplayOrder, - Visible = x.Visible, - Published = x.Published && x.Product.Published - }; - }).ToList(); + return new ProductModel.BundleItemModel + { + Id = x.Id, + ProductId = x.Product.Id, + ProductName = x.Product.Name, + ProductTypeName = x.Product.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = x.Product.ProductTypeLabelHint, + Sku = x.Product.Sku, + Quantity = x.Quantity, + Discount = x.Discount, + DisplayOrder = x.DisplayOrder, + Visible = x.Visible, + Published = x.Published + }; + }).ToList(); - var model = new GridModel() + model.Data = bundleItemsModel; + model.Total = bundleItemsModel.Count; + } + else { - Data = bundleItemsModel, - Total = bundleItemsModel.Count - }; + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { Data = model }; } @@ -2181,79 +2032,29 @@ public ActionResult BundleItemList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult BundleItemDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var bundleItem = _productService.GetBundleItemById(id); - if (bundleItem == null) - throw new ArgumentException("No product bundle item found with the specified id"); - - _productService.DeleteBundleItem(bundleItem); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productService.DeleteBundleItem(bundleItem); + } return BundleItemList(command, bundleItem.BundleProductId); } - public ActionResult BundleItemAddPopup(int productId) + [HttpPost] + public ActionResult BundleItemAdd(int productId, string selectedProductIds) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var product = _productService.GetProductById(productId); - - if (product.ProductType != ProductType.BundledProduct) - throw new ArgumentException("Bundle items can only be added to bundles."); - - var model = new ProductModel.AddBundleItemModel() - { - IsPerItemPricing = product.BundlePerItemPricing, - IsPerItemShipping = product.BundlePerItemShipping - }; - - //categories - var allCategories = _categoryService.GetAllCategories(showHidden: true); - var mappedCategories = allCategories.ToDictionary(x => x.Id); - foreach (var c in allCategories) + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - model.AvailableCategories.Add(new SelectListItem() { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); - } + var utcNow = DateTime.UtcNow; + var productIds = selectedProductIds.SplitSafe(",").Select(x => x.ToInt()).ToArray(); + var products = _productService.GetProductsByIds(productIds); - //manufacturers - foreach (var m in _manufacturerService.GetAllManufacturers(true)) - { - model.AvailableManufacturers.Add(new SelectListItem() { Text = m.Name, Value = m.Id.ToString() }); - } - - //stores - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var s in _storeService.GetAllStores()) - { - model.AvailableStores.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString() }); - } - - //product types - model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - ViewBag.RefreshPage = false; - ViewBag.CloseWindow = false; - - return View(model); - } - - [HttpPost] - [FormValueRequired("save")] - public ActionResult BundleItemAddPopup(string btnId, string formId, ProductModel.AddBundleItemModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - bool closeWindow = true; - - if (model.SelectedProductIds != null) - { - var products = _productService.GetProductsByIds(model.SelectedProductIds); - var utcNow = DateTime.UtcNow; + var maxDisplayOrder = _productService.GetBundleItems(productId, true) + .OrderByDescending(x => x.Item.DisplayOrder) + .Select(x => x.Item.DisplayOrder) + .FirstOrDefault(); foreach (var product in products.Where(x => x.CanBeBundleItem())) { @@ -2261,19 +2062,18 @@ public ActionResult BundleItemAddPopup(string btnId, string formId, ProductModel if (attributes.Count > 0 && attributes.Any(a => a.ProductVariantAttributeValues.Any(v => v.ValueType == ProductVariantAttributeValueType.ProductLinkage))) { - NotifyError(_localizationService.GetResource("Admin.Catalog.Products.BundleItems.NoAttributeWithProductLinkage")); - closeWindow = false; + NotifyError(T("Admin.Catalog.Products.BundleItems.NoAttributeWithProductLinkage")); } else { - var bundleItem = new ProductBundleItem() + var bundleItem = new ProductBundleItem { ProductId = product.Id, - BundleProductId = model.ProductId, + BundleProductId = productId, Quantity = 1, Visible = true, Published = true, - DisplayOrder = products.IndexOf(product) + 1, + DisplayOrder = ++maxDisplayOrder, CreatedOnUtc = utcNow, UpdatedOnUtc = utcNow }; @@ -2282,50 +2082,12 @@ public ActionResult BundleItemAddPopup(string btnId, string formId, ProductModel } } } - - ViewBag.RefreshPage = true; - ViewBag.CloseWindow = closeWindow; - ViewBag.btnId = btnId; - ViewBag.formId = formId; - return View(model); - } - - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult BundleItemAddPopupList(GridCommand command, ProductModel.AddBundleItemModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var searchContext = new ProductSearchContext() - { - CategoryIds = new List() { model.SearchCategoryId }, - ManufacturerId = model.SearchManufacturerId, - StoreId = model.SearchStoreId, - Keywords = model.SearchProductName, - SearchSku = !_catalogSettings.SuppressSkuSearch, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - ShowHidden = true, - ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null - }; - - var products = _productService.SearchProducts(searchContext); - - var gridModel = new GridModel(); - gridModel.Data = products.Select(x => + else { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - productModel.ProductSelectCheckboxClass = (!x.CanBeBundleItem() ? " hide" : ""); - - return productModel; - }); - gridModel.Total = products.TotalCount; + NotifyAccessDenied(); + } - return new JsonResult - { - Data = gridModel - }; + return new EmptyResult(); } public ActionResult BundleItemEditPopup(int id, string btnId, string formId) @@ -2346,7 +2108,7 @@ public ActionResult BundleItemEditPopup(int id, string btnId, string formId) } [ValidateInput(false)] - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult BundleItemEditPopup(string btnId, string formId, bool continueEditing, ProductBundleItemModel model, FormCollection form) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -2401,14 +2163,18 @@ public ActionResult ProductPictureAdd(int pictureId, int displayOrder, int produ var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); - _productService.InsertProductPicture(new ProductPicture() + var productPicture = new ProductPicture { PictureId = pictureId, ProductId = productId, DisplayOrder = displayOrder, - }); + }; + + MediaHelper.UpdatePictureTransientStateFor(productPicture, pp => pp.PictureId); + + _productService.InsertProductPicture(productPicture); _pictureService.SetSeoFilename(pictureId, _pictureService.GetPictureSeName(product.Name)); @@ -2418,29 +2184,34 @@ public ActionResult ProductPictureAdd(int pictureId, int displayOrder, int produ [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductPictureList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var productPictures = _productService.GetProductPicturesByProductId(productId); - var productPicturesModel = productPictures - .Select(x => - { - return new ProductModel.ProductPictureModel() - { - Id = x.Id, - ProductId = x.ProductId, - PictureId = x.PictureId, - PictureUrl = _pictureService.GetPictureUrl(x.PictureId), - DisplayOrder = x.DisplayOrder - }; - }) - .ToList(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productPictures = _productService.GetProductPicturesByProductId(productId); + var productPicturesModel = productPictures + .Select(x => + { + return new ProductModel.ProductPictureModel + { + Id = x.Id, + ProductId = x.ProductId, + PictureId = x.PictureId, + PictureUrl = _pictureService.GetPictureUrl(x.PictureId), + DisplayOrder = x.DisplayOrder + }; + }) + .ToList(); - var model = new GridModel - { - Data = productPicturesModel, - Total = productPicturesModel.Count - }; + model.Data = productPicturesModel; + model.Total = productPicturesModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -2451,15 +2222,13 @@ public ActionResult ProductPictureList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult ProductPictureUpdate(ProductModel.ProductPictureModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productPicture = _productService.GetProductPictureById(model.Id); - if (productPicture == null) - throw new ArgumentException("No product picture found with the specified id"); + var productPicture = _productService.GetProductPictureById(model.Id); - productPicture.DisplayOrder = model.DisplayOrder; - _productService.UpdateProductPicture(productPicture); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + productPicture.DisplayOrder = model.DisplayOrder; + _productService.UpdateProductPicture(productPicture); + } return ProductPictureList(command, productPicture.ProductId); } @@ -2467,18 +2236,16 @@ public ActionResult ProductPictureUpdate(ProductModel.ProductPictureModel model, [GridAction(EnableCustomBinding = true)] public ActionResult ProductPictureDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productPicture = _productService.GetProductPictureById(id); - if (productPicture == null) - throw new ArgumentException("No product picture found with the specified id"); + var productPicture = _productService.GetProductPictureById(id); + var productId = productPicture.ProductId; - var productId = productPicture.ProductId; - _productService.DeleteProductPicture(productPicture); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productService.DeleteProductPicture(productPicture); - var picture = _pictureService.GetPictureById(productPicture.PictureId); - _pictureService.DeletePicture(picture); + var picture = _pictureService.GetPictureById(productPicture.PictureId); + _pictureService.DeletePicture(picture); + } return ProductPictureList(command, productId); } @@ -2509,51 +2276,56 @@ public ActionResult ProductSpecificationAttributeAdd(int specificationAttributeO [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductSpecAttrList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productrSpecs = _specificationAttributeService.GetProductSpecificationAttributesByProductId(productId); + var model = new GridModel(); - var productrSpecsModel = productrSpecs - .Select(x => - { - var psaModel = new ProductSpecificationAttributeModel() - { - Id = x.Id, - SpecificationAttributeName = x.SpecificationAttributeOption.SpecificationAttribute.Name, - SpecificationAttributeOptionName = x.SpecificationAttributeOption.Name, - SpecificationAttributeOptionAttributeId = x.SpecificationAttributeOption.SpecificationAttributeId, - SpecificationAttributeOptionId = x.SpecificationAttributeOptionId, - AllowFiltering = x.AllowFiltering, - ShowOnProductPage = x.ShowOnProductPage, - DisplayOrder = x.DisplayOrder - }; - return psaModel; - }) - .ToList(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productrSpecs = _specificationAttributeService.GetProductSpecificationAttributesByProductId(productId); - foreach(var attr in productrSpecsModel) { + var productrSpecsModel = productrSpecs + .Select(x => + { + var psaModel = new ProductSpecificationAttributeModel + { + Id = x.Id, + SpecificationAttributeName = x.SpecificationAttributeOption.SpecificationAttribute.Name, + SpecificationAttributeOptionName = x.SpecificationAttributeOption.Name, + SpecificationAttributeOptionAttributeId = x.SpecificationAttributeOption.SpecificationAttributeId, + SpecificationAttributeOptionId = x.SpecificationAttributeOptionId, + AllowFiltering = x.AllowFiltering, + ShowOnProductPage = x.ShowOnProductPage, + DisplayOrder = x.DisplayOrder + }; + return psaModel; + }) + .ToList(); - var options = _specificationAttributeService.GetSpecificationAttributeOptionsBySpecificationAttribute(attr.SpecificationAttributeOptionAttributeId); + foreach (var attr in productrSpecsModel) + { + var options = _specificationAttributeService.GetSpecificationAttributeOptionsBySpecificationAttribute(attr.SpecificationAttributeOptionAttributeId); - foreach(var option in options) { + foreach (var option in options) + { + attr.SpecificationAttributeOptions.Add(new ProductSpecificationAttributeModel.SpecificationAttributeOption + { + id = option.Id, + name = option.Name, + text = option.Name + }); + } - attr.SpecificationAttributeOptions.Add(new ProductSpecificationAttributeModel.SpecificationAttributeOption() - { - id = option.Id, - name = option.Name, - text = option.Name - }); - } + attr.SpecificationAttributeOptionsJsonString = HttpUtility.HtmlEncode(JsonConvert.SerializeObject(attr.SpecificationAttributeOptions)); + } - attr.SpecificationAttributeOptionsJsonString = JsonConvert.SerializeObject(attr.SpecificationAttributeOptions); - } + model.Data = productrSpecsModel; + model.Total = productrSpecsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); - var model = new GridModel - { - Data = productrSpecsModel, - Total = productrSpecsModel.Count - }; + NotifyAccessDenied(); + } return new JsonResult { @@ -2562,17 +2334,17 @@ public ActionResult ProductSpecAttrList(GridCommand command, int productId) } [GridAction(EnableCustomBinding = true)] - public ActionResult ProductSpecAttrUpdate(int psaId, ProductSpecificationAttributeModel model, - GridCommand command) + public ActionResult ProductSpecAttrUpdate(int psaId, ProductSpecificationAttributeModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var psa = _specificationAttributeService.GetProductSpecificationAttributeById(psaId); - var psa = _specificationAttributeService.GetProductSpecificationAttributeById(psaId); - psa.AllowFiltering = model.AllowFiltering; - psa.ShowOnProductPage = model.ShowOnProductPage; - psa.DisplayOrder = model.DisplayOrder; - _specificationAttributeService.UpdateProductSpecificationAttribute(psa); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + psa.AllowFiltering = model.AllowFiltering; + psa.ShowOnProductPage = model.ShowOnProductPage; + psa.DisplayOrder = model.DisplayOrder; + _specificationAttributeService.UpdateProductSpecificationAttribute(psa); + } return ProductSpecAttrList(command, psa.ProductId); } @@ -2580,15 +2352,13 @@ public ActionResult ProductSpecAttrUpdate(int psaId, ProductSpecificationAttribu [GridAction(EnableCustomBinding = true)] public ActionResult ProductSpecAttrDelete(int psaId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var psa = _specificationAttributeService.GetProductSpecificationAttributeById(psaId); - if (psa == null) - throw new ArgumentException("No specification attribute found with the specified id"); + var psa = _specificationAttributeService.GetProductSpecificationAttributeById(psaId); + var productId = psa.ProductId; - var productId = psa.ProductId; - _specificationAttributeService.DeleteProductSpecificationAttribute(psa); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _specificationAttributeService.DeleteProductSpecificationAttribute(psa); + } return ProductSpecAttrList(command, productId); } @@ -2608,28 +2378,33 @@ public ActionResult ProductTags() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductTags(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var tags = _productTagService.GetAllProductTags() - //order by product count - .OrderByDescending(x => _productTagService.GetProductCount(x.Id, 0)) - .Select(x => - { - return new ProductTagModel() - { - Id = x.Id, - Name = x.Name, - ProductCount = _productTagService.GetProductCount(x.Id, 0) - }; - }) - .ForCommand(command); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var tags = _productTagService.GetAllProductTags() + .OrderByDescending(x => _productTagService.GetProductCount(x.Id, 0)) + .Select(x => + { + return new ProductTagModel + { + Id = x.Id, + Name = x.Name, + ProductCount = _productTagService.GetProductCount(x.Id, 0) + }; + }) + .ForCommand(command); + + model.Data = tags.PagedForCommand(command); + model.Total = tags.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var model = new GridModel - { - Data = tags.PagedForCommand(command), - Total = tags.Count() - }; return new JsonResult { Data = model @@ -2639,13 +2414,12 @@ public ActionResult ProductTags(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult ProductTagDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var tag = _productTagService.GetProductTagById(id); - var tag = _productTagService.GetProductTagById(id); - if (tag == null) - throw new ArgumentException("No product tag found with the specified id"); - _productTagService.DeleteProductTag(tag); + _productTagService.DeleteProductTag(tag); + } return ProductTags(command); } @@ -2704,463 +2478,7 @@ public ActionResult EditProductTag(string btnId, string formId, ProductTagModel return View(model); } - #endregion - - #region Export / Import - - public ActionResult ExportXmlAll() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - try - { - using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, forceNoTracking: true)) - { - var ctx = new ProductSearchContext(); - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageSize = int.MaxValue; - ctx.ShowHidden = true; - - var products = _productService.SearchProducts(ctx); - - var fileName = string.Format("products_{0}.xml", DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")); - var xml = _exportManager.ExportProductsToXml(products); - return new XmlDownloadResult(xml, fileName); - } - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } - } - - [HttpPost] - public ActionResult ExportXmlSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, forceNoTracking: true)) - { - var products = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - products.AddRange(_productService.GetProductsByIds(ids)); - } - - var xml = _exportManager.ExportProductsToXml(products); - return new XmlDownloadResult(xml, "products.xml"); - } - } - - public ActionResult ExportExcelAll() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - try - { - using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, forceNoTracking: true)) - { - var ctx = new ProductSearchContext(); - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageSize = int.MaxValue; - ctx.ShowHidden = true; - - var products = _productService.SearchProducts(ctx); - - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportProductsToXlsx(stream, products); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "products.xlsx"); - } - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } - } - - [HttpPost] - public ActionResult ExportExcelSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, forceNoTracking: true)) - { - var products = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - products.AddRange(_productService.GetProductsByIds(ids)); - } - - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportProductsToXlsx(stream, products); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "products.xlsx"); - } - } - - public ActionResult ExportPdf(bool all, string selectedIds = null) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - if (!all && selectedIds.IsEmpty()) - { - NotifyInfo(T("Admin.Common.ExportNoData")); - return RedirectToAction("List"); - } - - IList products; - - using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, forceNoTracking: true)) - { - if (all) - { - products = _productService.SearchProducts(new ProductSearchContext - { - LanguageId = _workContext.WorkingLanguage.Id, - OrderBy = ProductSortingEnum.Position, - PageSize = int.MaxValue, - ShowHidden = true - }); - } - else - { - var ids = selectedIds.ToIntArray(); - products = _productService.GetProductsByIds(ids); - } - } - - if (products.Count == 0) - { - NotifyInfo(T("Admin.Common.ExportNoData")); - return RedirectToAction("List"); - } - - if (products.Count > 500) - { - NotifyWarning(T("Admin.Common.ExportToPdf.TooManyItems")); - return RedirectToAction("List"); - } - - var model = new PrintableProductsModel(); - PreparePrintableProductsModel(model, products); - - var settings = new PdfConvertSettings - { - Title = model.StoreName, - Size = _pdfSettings.LetterPageSizeEnabled ? PdfPageSize.Letter : PdfPageSize.A4, - Margins = new PdfPageMargins { Top = 30, Bottom = 15 }, - Page = new PdfViewContent("PdfCatalog.Print", model, this.ControllerContext), - Header = new PdfRouteContent("PdfReceiptHeader", "Common", new RouteValueDictionary(new { area = "" }), this.ControllerContext), - HeaderOptions = new PdfHeaderFooterOptions { ShowLine = true }, - Footer = new PdfPartialViewContent("PdfCatalog.Print.Footer", model, this.ControllerContext), - }; - - if (products.Count > 5) - { - settings.Cover = new PdfViewContent("PdfCatalog.Print.Cover", model, this.ControllerContext); - settings.TocOptions = new PdfTocOptions { Enabled = true, TocHeaderText = T("Admin.Common.ExportToPdf.TocTitle") }; - } - - return new PdfResult(_pdfConverter, settings) { FileName = "products.pdf" }; - } - - public IList PrepareProductSpecificationModel(int productId) - { - var model = _specificationAttributeService.GetProductSpecificationAttributesByProductId(productId, null, true) - .Select(psa => - { - return new PrintableProductSpecificationModel() - { - SpecificationAttributeId = psa.SpecificationAttributeOption.SpecificationAttributeId, - SpecificationAttributeName = psa.SpecificationAttributeOption.SpecificationAttribute.GetLocalized(x => x.Name), - SpecificationAttributeOption = psa.SpecificationAttributeOption.GetLocalized(x => x.Name) - }; - }).ToList(); - return model; - } - - [NonAction] - protected void PreparePrintableProductsModel(PrintableProductsModel model, IList products) - { - var store = _services.StoreContext.CurrentStore; - - var companyInfoSettings = _services.Settings.LoadSetting(store.Id); - var contactSettings = _services.Settings.LoadSetting(store.Id); - var pdfSettings = _services.Settings.LoadSetting(store.Id); - - model.StoreName = store.Name; - model.StoreUrl = store.Url; - - var logoPicture = _pictureService.GetPictureById(pdfSettings.LogoPictureId); - if (logoPicture == null) - { - logoPicture = _pictureService.GetPictureById(store.LogoPictureId); - } - - if (logoPicture != null) - { - model.PrintLogoUrl = _pictureService.GetPictureUrl(logoPicture, showDefaultPicture: false); - } - - model.MerchantCompanyInfo = companyInfoSettings; - model.MerchantContactData = contactSettings; - - string dimension = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId).Name; - string currencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; - - foreach (var product in products) - { - var productModel = new PrintableProductModel(); - - productModel.Name = product.GetLocalized(x => x.Name); - productModel.ShortDescription = product.GetLocalized(x => x.ShortDescription); - productModel.FullDescription = product.GetLocalized(x => x.FullDescription); - - productModel.Sku = product.Sku; - productModel.Price = product.Price.ToString("0.00") + " " + currencyCode; - productModel.Weight = (product.Weight > 0) ? "{0} {1}".FormatCurrent(product.Weight.ToString("F2"), _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name) : ""; ; - productModel.Length = (product.Length > 0) ? "{0} {1}".FormatCurrent(product.Length.ToString("F2"), dimension) : ""; - productModel.Width = (product.Width > 0) ? "{0} {1}".FormatCurrent(product.Width.ToString("F2"), dimension) : ""; - productModel.Height = (product.Height > 0) ? "{0} {1}".FormatCurrent(product.Height.ToString("F2"), dimension) : ""; - - var manufacturers = _manufacturerService.GetProductManufacturersByProductId(product.Id).ToList(); - - productModel.SpecificationAttributes = PrepareProductSpecificationModel(product.Id); - productModel.Manufacturer = String.Join(", ", manufacturers.Select(x => x.Manufacturer.GetLocalized(y => y.Name))); - - var pictures = _pictureService.GetPicturesByProductId(product.Id); - if (pictures.Count > 0) - { - productModel.PictureUrl = _pictureService.GetPictureUrl(pictures[0].Id, 0, false); - } - - productModel.ProductType = product.ProductType; - - if (product.ProductType == ProductType.GroupedProduct) - { - var searchContext = new ProductSearchContext() - { - ParentGroupedProductId = product.Id, - PageSize = int.MaxValue, - ShowHidden = true - }; - - foreach (var associatedProduct in _productService.SearchProducts(searchContext)) - { - var picture = associatedProduct.ProductPictures.FirstOrDefault(); - var pictureUrl = String.Empty; - if (picture != null) - { - pictureUrl = _pictureService.GetPictureUrl(picture.Picture, 75, false); - } - - productModel.AssociatedProducts.Add(new PrintableProductModel - { - Name = associatedProduct.GetLocalized(x => x.Name), - ShortDescription = "{0}: {1} {2}, {3}: {4}".FormatWith(T("PDFProductCatalog.Price"), - associatedProduct.Price.ToString("0.00"), - currencyCode, - T("PDFProductCatalog.SKU"), - associatedProduct.Sku), - PictureUrl = pictureUrl - }); - } - } - - if (product.ProductType == ProductType.BundledProduct) - { - foreach (var bundledProduct in _productService.GetBundleItems(product.Id).Select(x => x.Item)) - { - var picture = bundledProduct.Product.ProductPictures.FirstOrDefault(); - var pictureUrl = String.Empty; - if (picture != null) - { - pictureUrl = _pictureService.GetPictureUrl(picture.Picture, 75, false); - } - - productModel.BundledItems.Add(new PrintableProductModel() - { - Name = bundledProduct.Product.GetLocalized(x => x.Name), - ShortDescription = "{0}: {1} {2}, {3}: {4}".FormatWith(T("PDFProductCatalog.Price"), - bundledProduct.Product.Price.ToString("0.00"), - currencyCode, - T("PDFProductCatalog.SKU"), - bundledProduct.Product.Sku), - PictureUrl = pictureUrl - }); - } - } - - model.Products.Add(productModel); - } - - } - - [HttpPost] - public ActionResult ImportExcel(FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - try - { - var file = Request.Files["importexcelfile"]; - if (file != null && file.ContentLength > 0) - { - var stream = new MemoryStream(); - file.InputStream.CopyTo(stream); - - var options = TaskCreationOptions.LongRunning; - var cts = new CancellationTokenSource(); - var scheduler = TaskScheduler.Default; - - var task = AsyncRunner.Run(c => - { - var progress = new Progress(p => - { - AsyncState.Current.Set(p); - }); - - try - { - AsyncState.Current.SetCancelTokenSource(cts); - var importManager = c.Resolve(); - var t = importManager.ImportProductsFromExcelAsync(stream, cts.Token, progress); - - var result = t.Result; - - // Saving the result enables us to show a report for last import. - AsyncState.Current.Set(result, neverExpires: true); - } - catch (AggregateException ae) - { - foreach (var e in ae.Flatten().InnerExceptions) - { - // TBD: do exactly WHAT now?! Logging? Nothing? - // [...] - } - } - finally - { - AsyncState.Current.Remove(); - } - - }, cts.Token, options, scheduler); - - task.Wait(500); - } - else - { - NotifyError(_localizationService.GetResource("Admin.Common.UploadFile")); - return RedirectToAction("List"); - } - - NotifySuccess(_localizationService.GetResource("Admin.Common.ImportFromExcel.InProgress")); - return RedirectToAction("List"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } - } - - [HttpPost] - public ActionResult ImportExcelProgress() - { - var progress = AsyncState.Current.Get(); - - //object lastResult = null; - string lastResultSummary = null; - string lastResultClass = ""; - var result = AsyncState.Current.Get(); - if (result != null) - { - var title = _localizationService.GetResource("Admin.Common.ImportFromExcel.LastResultTitle").FormatCurrent( - result.EndDateUtc.ToLocalTime(), - result.Cancelled ? " (" + _localizationService.GetResource("Common.Cancelled").ToLower() + ")" : ""); - var counts = _localizationService.GetResource("Admin.Common.ImportFromExcel.ProcessedCount").FormatCurrent( - result.AffectedRecords, - result.TotalRecords); - var stats = _localizationService.GetResource("Admin.Common.ImportFromExcel.QuickStats").FormatCurrent( - result.NewRecords, - result.ModifiedRecords, - result.Messages.Count(x => x.MessageType == ImportMessageType.Warning), - result.Messages.Count(x => x.MessageType == ImportMessageType.Error)); - - lastResultSummary = "{0} {1} {2}".FormatInvariant(title, counts, stats); - - if (!result.Cancelled) - { - lastResultClass = result.HasErrors ? "alert-error" : "alert-success"; - } - } - - if (progress == null) - { - return Json(new { NoRunningTask = true, LastResultSummary = lastResultSummary, LastResultClass = lastResultClass }, JsonRequestBehavior.DenyGet); - } - - return Json(new { Progress = progress, LastResultSummary = lastResultSummary, LastResultClass = lastResultClass }, JsonRequestBehavior.DenyGet); - } - - public ActionResult ImportExcelCancel() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var tcs = AsyncState.Current.GetCancelTokenSource(); - if (tcs != null) - { - tcs.Cancel(); - NotifySuccess(_localizationService.GetResource("Admin.Common.ImportFromExcel.Cancelled")); - } - - return RedirectToAction("List"); - } - - public ActionResult ImportExcelDownloadReport() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var result = AsyncState.Current.Get(); - if (result == null) - { - return Content(_localizationService.GetResource("Admin.Common.ImportFromExcel.NoReportAvailable")); - } - - var importManager = EngineContext.Current.Resolve(); - string report = importManager.CreateTextReport(result); - - return File(Encoding.UTF8.GetBytes(report), "text/plain", "excelimport-report-{0}.txt".FormatInvariant(result.StartDateUtc.ToLocalTime())); - } - - #endregion + #endregion #region Low stock reports @@ -3175,21 +2493,29 @@ public ActionResult LowStockReport() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult LowStockReportList(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var allProducts = _productService.GetLowStockProducts(); - var model = new GridModel() + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - Data = allProducts.PagedForCommand(command).Select(x => + var allProducts = _productService.GetLowStockProducts(); + + model.Data = allProducts.PagedForCommand(command).Select(x => { var productModel = x.ToModel(); productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); return productModel; - }), - Total = allProducts.Count - }; + }); + + model.Total = allProducts.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = model @@ -3205,25 +2531,29 @@ public ActionResult BulkEdit() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); + var allStores = _services.StoreService.GetAllStores(); + var model = new BulkEditListModel(); + model.GridPageSize = _adminAreaSettings.GridPageSize; - //categories var allCategories = _categoryService.GetAllCategories(showHidden: true); var mappedCategories = allCategories.ToDictionary(x => x.Id); foreach (var c in allCategories) { - model.AvailableCategories.Add(new SelectListItem() { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); + model.AvailableCategories.Add(new SelectListItem { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); } - //manufacturers foreach (var m in _manufacturerService.GetAllManufacturers(true)) { - model.AvailableManufacturers.Add(new SelectListItem() { Text = m.Name, Value = m.Id.ToString() }); + model.AvailableManufacturers.Add(new SelectListItem { Text = m.Name, Value = m.Id.ToString() }); + } + + foreach (var s in allStores) + { + model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); } - //product types model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); return View(model); } @@ -3231,43 +2561,55 @@ public ActionResult BulkEdit() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult BulkEditSelect(GridCommand command, BulkEditListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var searchContext = new ProductSearchContext() + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - CategoryIds = new List() { model.SearchCategoryId }, - ManufacturerId = model.SearchManufacturerId, - Keywords = model.SearchProductName, - SearchSku = !_catalogSettings.SuppressSkuSearch, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - ShowHidden = true, - ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null - }; + var searchContext = new ProductSearchContext + { + StoreId = model.SearchStoreId, + ManufacturerId = model.SearchManufacturerId, + Keywords = model.SearchProductName, + SearchSku = !_catalogSettings.SuppressSkuSearch, + PageIndex = command.Page - 1, + PageSize = command.PageSize, + ShowHidden = true, + ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null + }; - var products = _productService.SearchProducts(searchContext); - var gridModel = new GridModel(); + if (model.SearchCategoryId > 0) + searchContext.CategoryIds.Add(model.SearchCategoryId); - gridModel.Data = products.Select(x => - { - var productModel = new BulkEditProductModel() + var products = _productService.SearchProducts(searchContext); + + gridModel.Data = products.Select(x => { - Id = x.Id, - Name = x.Name, - ProductTypeName = x.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = x.ProductTypeLabelHint, - Sku = x.Sku, - OldPrice = x.OldPrice, - Price = x.Price, - ManageInventoryMethod = x.ManageInventoryMethod.GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id), - StockQuantity = x.StockQuantity, - Published = x.Published - }; + var productModel = new BulkEditProductModel + { + Id = x.Id, + Name = x.Name, + ProductTypeName = x.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = x.ProductTypeLabelHint, + Sku = x.Sku, + OldPrice = x.OldPrice, + Price = x.Price, + ManageInventoryMethod = x.ManageInventoryMethod.GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id), + StockQuantity = x.StockQuantity, + Published = x.Published + }; + + return productModel; + }); + + gridModel.Total = products.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - return productModel; - }); - gridModel.Total = products.TotalCount; return new JsonResult { Data = gridModel @@ -3281,38 +2623,39 @@ public ActionResult BulkEditSave(GridCommand command, [Bind(Prefix = "deleted")]IEnumerable deletedProducts, BulkEditListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - if (updatedProducts != null) + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - foreach (var pModel in updatedProducts) + if (updatedProducts != null) { - //update - var product = _productService.GetProductById(pModel.Id); - if (product != null) + foreach (var pModel in updatedProducts) { - product.Sku = pModel.Sku; - product.Price = pModel.Price; - product.OldPrice = pModel.OldPrice; - product.StockQuantity = pModel.StockQuantity; - product.Published = pModel.Published; - _productService.UpdateProduct(product); + var product = _productService.GetProductById(pModel.Id); + if (product != null) + { + product.Sku = pModel.Sku; + product.Price = pModel.Price; + product.OldPrice = pModel.OldPrice; + product.StockQuantity = pModel.StockQuantity; + product.Published = pModel.Published; + + _productService.UpdateProduct(product); + } } } - } - if (deletedProducts != null) - { - foreach (var pModel in deletedProducts) + + if (deletedProducts != null) { - //delete - var product = _productService.GetProductById(pModel.Id); - if (product != null) + foreach (var pModel in deletedProducts) { - _productService.DeleteProduct(product); + var product = _productService.GetProductById(pModel.Id); + if (product != null) + { + _productService.DeleteProduct(product); + } } } } + return BulkEditSelect(command, model); } @@ -3323,49 +2666,67 @@ public ActionResult BulkEditSave(GridCommand command, [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult TierPriceList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var product = _productService.GetProductById(productId); - if (product == null) - throw new ArgumentException("No product variant found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var product = _productService.GetProductById(productId); - var tierPricesModel = product.TierPrices - .OrderBy(x => x.StoreId) - .ThenBy(x => x.Quantity) - .ThenBy(x => x.CustomerRoleId) - .Select(x => - { - var storeName = ""; - if (x.StoreId > 0) + var allStores = _services.StoreService.GetAllStores(); + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); + string allRolesString = T("Admin.Catalog.Products.TierPrices.Fields.CustomerRole.AllRoles"); + string allStoresString = T("Admin.Common.StoresAll"); + string deletedString = "[{0}]".FormatInvariant(T("Admin.Common.Deleted")); + + var tierPricesModel = product.TierPrices + .OrderBy(x => x.StoreId) + .ThenBy(x => x.Quantity) + .ThenBy(x => x.CustomerRoleId) + .Select(x => { - var store = _storeService.GetStoreById(x.StoreId); - storeName = store != null ? store.Name : "[Deleted]"; - } - else - { - storeName = _localizationService.GetResource("Admin.Common.StoresAll"); - } - return new ProductModel.TierPriceModel() - { - Id = x.Id, - StoreId = x.StoreId, - Store = storeName, - CustomerRole = x.CustomerRoleId.HasValue ? _customerService.GetCustomerRoleById(x.CustomerRoleId.Value).Name : - _localizationService.GetResource("Admin.Catalog.Products.TierPrices.Fields.CustomerRole.AllRoles"), - ProductId = x.ProductId, - CustomerRoleId = x.CustomerRoleId.HasValue ? x.CustomerRoleId.Value : 0, - Quantity = x.Quantity, - Price1 = x.Price - }; - }) - .ToList(); + var tierPriceModel = new ProductModel.TierPriceModel + { + Id = x.Id, + StoreId = x.StoreId, + ProductId = x.ProductId, + CustomerRoleId = x.CustomerRoleId ?? 0, + Quantity = x.Quantity, + Price1 = x.Price + }; + + if (x.CustomerRoleId.HasValue) + { + var role = allCustomerRoles.FirstOrDefault(r => r.Id == x.CustomerRoleId.Value); + tierPriceModel.CustomerRole = (role == null ? allRolesString : role.Name); + } + else + { + tierPriceModel.CustomerRole = allRolesString; + } - var model = new GridModel + if (x.StoreId > 0) + { + var store = allStores.FirstOrDefault(s => s.Id == x.StoreId); + tierPriceModel.Store = (store == null ? deletedString : store.Name); + } + else + { + tierPriceModel.Store = allStoresString; + } + + return tierPriceModel; + }) + .ToList(); + + model.Data = tierPricesModel; + model.Total = tierPricesModel.Count; + } + else { - Data = tierPricesModel, - Total = tierPricesModel.Count - }; + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -3376,27 +2737,26 @@ public ActionResult TierPriceList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult TierPriceInsert(GridCommand command, ProductModel.TierPriceModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var product = _productService.GetProductById(model.ProductId); - var product = _productService.GetProductById(model.ProductId); - if (product == null) - throw new ArgumentException("No product found with the specified id"); + var tierPrice = new TierPrice + { + ProductId = model.ProductId, + // use Store property (not Store propertyId) because appropriate property is stored in it + StoreId = model.Store.ToInt(), + // use CustomerRole property (not CustomerRoleId) because appropriate property is stored in it + CustomerRoleId = model.CustomerRole.IsNumeric() && Int32.Parse(model.CustomerRole) != 0 ? Int32.Parse(model.CustomerRole) : (int?)null, + Quantity = model.Quantity, + Price = model.Price1 + }; - var tierPrice = new TierPrice() - { - ProductId = model.ProductId, - // use Store property (not Store propertyId) because appropriate property is stored in it - StoreId = model.Store.ToInt(), - // use CustomerRole property (not CustomerRoleId) because appropriate property is stored in it - CustomerRoleId = model.CustomerRole.IsNumeric() && Int32.Parse(model.CustomerRole) != 0 ? Int32.Parse(model.CustomerRole) : (int?)null, - Quantity = model.Quantity, - Price = model.Price1 - }; - _productService.InsertTierPrice(tierPrice); + _productService.InsertTierPrice(tierPrice); - //update "HasTierPrices" property - _productService.UpdateHasTierPricesProperty(product); + //update "HasTierPrices" property + _productService.UpdateHasTierPricesProperty(product); + } return TierPriceList(command, model.ProductId); } @@ -3404,20 +2764,19 @@ public ActionResult TierPriceInsert(GridCommand command, ProductModel.TierPriceM [GridAction(EnableCustomBinding = true)] public ActionResult TierPriceUpdate(GridCommand command, ProductModel.TierPriceModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var tierPrice = _productService.GetTierPriceById(model.Id); - if (tierPrice == null) - throw new ArgumentException("No tier price found with the specified id"); - //use Store property (not Store propertyId) because appropriate property is stored in it - tierPrice.StoreId = model.Store.ToInt(); - //use CustomerRole property (not CustomerRoleId) because appropriate property is stored in it - tierPrice.CustomerRoleId = model.CustomerRole.IsNumeric() && Int32.Parse(model.CustomerRole) != 0 ? Int32.Parse(model.CustomerRole) : (int?)null; - tierPrice.Quantity = model.Quantity; - tierPrice.Price = model.Price1; - _productService.UpdateTierPrice(tierPrice); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + //use Store property (not Store propertyId) because appropriate property is stored in it + tierPrice.StoreId = model.Store.ToInt(); + //use CustomerRole property (not CustomerRoleId) because appropriate property is stored in it + tierPrice.CustomerRoleId = model.CustomerRole.IsNumeric() && Int32.Parse(model.CustomerRole) != 0 ? Int32.Parse(model.CustomerRole) : (int?)null; + tierPrice.Quantity = model.Quantity; + tierPrice.Price = model.Price1; + + _productService.UpdateTierPrice(tierPrice); + } return TierPriceList(command, tierPrice.ProductId); } @@ -3425,22 +2784,18 @@ public ActionResult TierPriceUpdate(GridCommand command, ProductModel.TierPriceM [GridAction(EnableCustomBinding = true)] public ActionResult TierPriceDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var tierPrice = _productService.GetTierPriceById(id); - if (tierPrice == null) - throw new ArgumentException("No tier price found with the specified id"); - var productId = tierPrice.ProductId; - var product = _productService.GetProductById(productId); - if (product == null) - throw new ArgumentException("No product found with the specified id"); - _productService.DeleteTierPrice(tierPrice); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var product = _productService.GetProductById(productId); - //update "HasTierPrices" property - _productService.UpdateHasTierPricesProperty(product); + _productService.DeleteTierPrice(tierPrice); + + //update "HasTierPrices" property + _productService.UpdateHasTierPricesProperty(product); + } return TierPriceList(command, productId); } @@ -3452,40 +2807,45 @@ public ActionResult TierPriceDelete(int id, GridCommand command) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var productVariantAttributes = _productAttributeService.GetProductVariantAttributesByProductId(productId); - var productVariantAttributesModel = productVariantAttributes - .Select(x => - { - var pvaModel = new ProductModel.ProductVariantAttributeModel() + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productVariantAttributes = _productAttributeService.GetProductVariantAttributesByProductId(productId); + var productVariantAttributesModel = productVariantAttributes + .Select(x => { - Id = x.Id, - ProductId = x.ProductId, - ProductAttribute = _productAttributeService.GetProductAttributeById(x.ProductAttributeId).Name, - ProductAttributeId = x.ProductAttributeId, - TextPrompt = x.TextPrompt, - IsRequired = x.IsRequired, - AttributeControlType = x.AttributeControlType.GetLocalizedEnum(_localizationService, _workContext), - AttributeControlTypeId = x.AttributeControlTypeId, - DisplayOrder1 = x.DisplayOrder - }; + var pvaModel = new ProductModel.ProductVariantAttributeModel + { + Id = x.Id, + ProductId = x.ProductId, + ProductAttribute = _productAttributeService.GetProductAttributeById(x.ProductAttributeId).Name, + ProductAttributeId = x.ProductAttributeId, + TextPrompt = x.TextPrompt, + IsRequired = x.IsRequired, + AttributeControlType = x.AttributeControlType.GetLocalizedEnum(_localizationService, _workContext), + AttributeControlTypeId = x.AttributeControlTypeId, + DisplayOrder1 = x.DisplayOrder + }; - if (x.ShouldHaveValues()) - { - pvaModel.ViewEditUrl = Url.Action("EditAttributeValues", "Product", new { productVariantAttributeId = x.Id }); - pvaModel.ViewEditText = string.Format(_localizationService.GetResource("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.ViewLink"), x.ProductVariantAttributeValues != null ? x.ProductVariantAttributeValues.Count : 0); - } - return pvaModel; - }) - .ToList(); + if (x.ShouldHaveValues()) + { + pvaModel.ViewEditUrl = Url.Action("EditAttributeValues", "Product", new { productVariantAttributeId = x.Id }); + pvaModel.ViewEditText = string.Format(_localizationService.GetResource("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.ViewLink"), x.ProductVariantAttributeValues != null ? x.ProductVariantAttributeValues.Count : 0); + } + return pvaModel; + }) + .ToList(); - var model = new GridModel + model.Data = productVariantAttributesModel; + model.Total = productVariantAttributesModel.Count; + } + else { - Data = productVariantAttributesModel, - Total = productVariantAttributesModel.Count - }; + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -3496,19 +2856,20 @@ public ActionResult ProductVariantAttributeList(GridCommand command, int product [GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeInsert(GridCommand command, ProductModel.ProductVariantAttributeModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var pva = new ProductVariantAttribute() + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - ProductId = model.ProductId, - ProductAttributeId = Int32.Parse(model.ProductAttribute), //use ProductAttribute property (not ProductAttributeId) because appropriate property is stored in it - TextPrompt = model.TextPrompt, - IsRequired = model.IsRequired, - AttributeControlTypeId = Int32.Parse(model.AttributeControlType), //use AttributeControlType property (not AttributeControlTypeId) because appropriate property is stored in it - DisplayOrder = model.DisplayOrder1 - }; - _productAttributeService.InsertProductVariantAttribute(pva); + var pva = new ProductVariantAttribute + { + ProductId = model.ProductId, + ProductAttributeId = Int32.Parse(model.ProductAttribute), //use ProductAttribute property (not ProductAttributeId) because appropriate property is stored in it + TextPrompt = model.TextPrompt, + IsRequired = model.IsRequired, + AttributeControlTypeId = Int32.Parse(model.AttributeControlType), //use AttributeControlType property (not AttributeControlTypeId) because appropriate property is stored in it + DisplayOrder = model.DisplayOrder1 + }; + + _productAttributeService.InsertProductVariantAttribute(pva); + } return ProductVariantAttributeList(command, model.ProductId); } @@ -3516,21 +2877,20 @@ public ActionResult ProductVariantAttributeInsert(GridCommand command, ProductMo [GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeUpdate(GridCommand command, ProductModel.ProductVariantAttributeModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var pva = _productAttributeService.GetProductVariantAttributeById(model.Id); - if (pva == null) - throw new ArgumentException("No product variant attribute found with the specified id"); - //use ProductAttribute property (not ProductAttributeId) because appropriate property is stored in it - pva.ProductAttributeId = Int32.Parse(model.ProductAttribute); - pva.TextPrompt = model.TextPrompt; - pva.IsRequired = model.IsRequired; - //use AttributeControlType property (not AttributeControlTypeId) because appropriate property is stored in it - pva.AttributeControlTypeId = Int32.Parse(model.AttributeControlType); - pva.DisplayOrder = model.DisplayOrder1; - _productAttributeService.UpdateProductVariantAttribute(pva); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + //use ProductAttribute property (not ProductAttributeId) because appropriate property is stored in it + pva.ProductAttributeId = Int32.Parse(model.ProductAttribute); + pva.TextPrompt = model.TextPrompt; + pva.IsRequired = model.IsRequired; + //use AttributeControlType property (not AttributeControlTypeId) because appropriate property is stored in it + pva.AttributeControlTypeId = Int32.Parse(model.AttributeControlType); + pva.DisplayOrder = model.DisplayOrder1; + + _productAttributeService.UpdateProductVariantAttribute(pva); + } return ProductVariantAttributeList(command, pva.ProductId); } @@ -3538,16 +2898,13 @@ public ActionResult ProductVariantAttributeUpdate(GridCommand command, ProductMo [GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var pva = _productAttributeService.GetProductVariantAttributeById(id); - if (pva == null) - throw new ArgumentException("No product variant attribute found with the specified id"); - var productId = pva.ProductId; - _productAttributeService.DeleteProductVariantAttribute(pva); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productAttributeService.DeleteProductVariantAttribute(pva); + } return ProductVariantAttributeList(command, productId); } @@ -3584,11 +2941,11 @@ public ActionResult EditAttributeValues(int productVariantAttributeId) var pva = _productAttributeService.GetProductVariantAttributeById(productVariantAttributeId); if (pva == null) - throw new ArgumentException("No product variant attribute found with the specified id"); + throw new ArgumentException(T("Products.Variants.NotFound", productVariantAttributeId)); var product = _productService.GetProductById(pva.ProductId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", pva.ProductId)); var model = new ProductModel.ProductVariantAttributeValueListModel() { @@ -3604,22 +2961,19 @@ public ActionResult EditAttributeValues(int productVariantAttributeId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductAttributeValueList(int productVariantAttributeId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var pva = _productAttributeService.GetProductVariantAttributeById(productVariantAttributeId); - if (pva == null) - throw new ArgumentException("No product variant attribute found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var pva = _productAttributeService.GetProductVariantAttributeById(productVariantAttributeId); - var values = _productAttributeService.GetProductVariantAttributeValues(productVariantAttributeId); + var values = _productAttributeService.GetProductVariantAttributeValues(productVariantAttributeId); - var gridModel = new GridModel - { - Data = values.Select(x => + gridModel.Data = values.Select(x => { var linkedProduct = _productService.GetProductById(x.LinkedProductId); - var model = new ProductModel.ProductVariantAttributeValueModel() + var model = new ProductModel.ProductVariantAttributeValueModel { Id = x.Id, ProductVariantAttributeId = x.ProductVariantAttributeId, @@ -3650,9 +3004,17 @@ public ActionResult ProductAttributeValueList(int productVariantAttributeId, Gri } return model; - }), - Total = values.Count() - }; + }); + + gridModel.Total = values.Count(); + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel @@ -3666,18 +3028,19 @@ public ActionResult ProductAttributeValueCreatePopup(int productAttributeAttribu var pva = _productAttributeService.GetProductVariantAttributeById(productAttributeAttributeId); if (pva == null) - throw new ArgumentException("No product variant attribute found with the specified id"); + throw new ArgumentException(T("Products.Variants.NotFound", productAttributeAttributeId)); - var model = new ProductModel.ProductVariantAttributeValueModel() + var model = new ProductModel.ProductVariantAttributeValueModel { + ProductId = pva.ProductId, ProductVariantAttributeId = productAttributeAttributeId, DisplayColorSquaresRgb = pva.AttributeControlType == AttributeControlType.ColorSquares, ColorSquaresRgb = "#000000", Quantity = 1 }; - //locales AddLocales(_languageService, model.Locales); + return View(model); } @@ -3708,7 +3071,7 @@ public ActionResult ProductAttributeValueCreatePopup(string btnId, string formId if (ModelState.IsValid) { - var pvav = new ProductVariantAttributeValue() + var pvav = new ProductVariantAttributeValue { ProductVariantAttributeId = model.ProductVariantAttributeId, Name = model.Name, @@ -3747,8 +3110,9 @@ public ActionResult ProductAttributeValueEditPopup(int id) var linkedProduct = _productService.GetProductById(pvav.LinkedProductId); - var model = new ProductModel.ProductVariantAttributeValueModel() + var model = new ProductModel.ProductVariantAttributeValueModel { + ProductId = pvav.ProductVariantAttribute.ProductId, ProductVariantAttributeId = pvav.ProductVariantAttributeId, Name = pvav.Name, Alias = pvav.Alias, @@ -3775,7 +3139,6 @@ public ActionResult ProductAttributeValueEditPopup(int id) model.QuantityInfo = " × {0}".FormatWith(model.Quantity); } - //locales AddLocales(_languageService, model.Locales, (locale, languageId) => { locale.Name = pvav.GetLocalized(x => x.Name, languageId, false, false); @@ -3843,125 +3206,28 @@ public ActionResult ProductAttributeValueEditPopup(string btnId, string formId, [GridAction(EnableCustomBinding = true)] public ActionResult ProductAttributeValueDelete(int pvavId, int productVariantAttributeId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var pvav = _productAttributeService.GetProductVariantAttributeValueById(pvavId); - if (pvav == null) - throw new ArgumentException("No product variant attribute value found with the specified id"); - - _productAttributeService.DeleteProductVariantAttributeValue(pvav); - - return ProductAttributeValueList(productVariantAttributeId, command); - } - - public ActionResult ProductAttributeValueLinkagePopup() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var model = new ProductModel.ProductVariantAttributeValueModel.AddProductLinkageModel(); - - //categories - var allCategories = _categoryService.GetAllCategories(showHidden: true); - var mappedCategories = allCategories.ToDictionary(x => x.Id); - foreach (var c in allCategories) - { - model.AvailableCategories.Add(new SelectListItem() { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); - } - - //manufacturers - foreach (var m in _manufacturerService.GetAllManufacturers(true)) + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - model.AvailableManufacturers.Add(new SelectListItem() { Text = m.Name, Value = m.Id.ToString() }); - } + var pvav = _productAttributeService.GetProductVariantAttributeValueById(pvavId); - //stores - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var s in _storeService.GetAllStores()) - { - model.AvailableStores.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString() }); + _productAttributeService.DeleteProductVariantAttributeValue(pvav); } - //product types - model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - return View(model); - } - - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult ProductAttributeValueLinkagePopupList(GridCommand command, ProductModel.ProductVariantAttributeValueModel.AddProductLinkageModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var searchContext = new ProductSearchContext() - { - CategoryIds = new List() { model.SearchCategoryId }, - ManufacturerId = model.SearchManufacturerId, - StoreId = model.SearchStoreId, - Keywords = model.SearchProductName, - SearchSku = !_catalogSettings.SuppressSkuSearch, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - ShowHidden = true, - ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null - }; - - var products = _productService.SearchProducts(searchContext); - - var gridModel = new GridModel(); - gridModel.Data = products.Select(x => - { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - - return productModel; - }); - gridModel.Total = products.TotalCount; - - return new JsonResult - { - Data = gridModel - }; - } - - [HttpPost] - [FormValueRequired("save")] - public ActionResult ProductAttributeValueLinkagePopup(ProductModel.ProductVariantAttributeValueModel.AddProductLinkageModel model, string openerProductId, string openerProductName) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - ViewBag.RefreshPage = true; - ViewBag.openerProductId = openerProductId; - ViewBag.openerProductName = openerProductName; - - var linkedProduct = _productService.GetProductById(model.ProductId); - - if (linkedProduct == null) - { - ViewBag.productId = 0; - ViewBag.productName = ""; - } - else - { - ViewBag.productId = linkedProduct.Id; - ViewBag.productName = linkedProduct.Name; - } - return View(model); + return ProductAttributeValueList(productVariantAttributeId, command); } #endregion #region Product variant attribute combinations - private void PrepareProductAttributeCombinationModel(ProductVariantAttributeCombinationModel model, ProductVariantAttributeCombination entity, + private void PrepareProductAttributeCombinationModel( + ProductVariantAttributeCombinationModel model, + ProductVariantAttributeCombination entity, Product product, bool formatAttributes = false) { if (model == null) throw new ArgumentNullException("model"); + if (product == null) throw new ArgumentNullException("variant"); @@ -4051,60 +3317,69 @@ private void PrepareDeliveryTimes(ProductVariantAttributeCombinationModel model, [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeCombinationList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - // TODO: Replace ProductModel.ProductVariantAttributeCombinationModel by AddProductVariantAttributeCombinationModel - // when there's no grid-inline-editing anymore. + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + // TODO: Replace ProductModel.ProductVariantAttributeCombinationModel by AddProductVariantAttributeCombinationModel + // when there's no grid-inline-editing anymore. - var product = _productService.GetProductById(productId); - if (product == null) - throw new ArgumentException("No product found with the specified id"); + var product = _productService.GetProductById(productId); - var productVariantAttributeCombinations = _productAttributeService.GetAllProductVariantAttributeCombinations(productId); + var allCombinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id, command.Page - 1, command.PageSize); - var productUrlTitle = _localizationService.GetResource("Common.OpenInShop"); - var productUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }); - productUrl = "{0}{1}attributes=".FormatWith(productUrl, productUrl.Contains("?") ? "&" : "?"); + var productUrlTitle = _localizationService.GetResource("Common.OpenInShop"); + var productSeName = product.GetSeName(); - var productVariantAttributesModel = productVariantAttributeCombinations.Select(x => - { - var pvacModel = x.ToModel(); - PrepareProductAttributeCombinationModel(pvacModel, x, product, true); + var productVariantAttributesModel = allCombinations.Select(x => + { + var pvacModel = x.ToModel(); + PrepareProductAttributeCombinationModel(pvacModel, x, product, true); - pvacModel.ProductUrl = productUrl + _productAttributeParser.SerializeQueryData(product.Id, x.AttributesXml); - pvacModel.ProductUrlTitle = productUrlTitle; + pvacModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(x.AttributesXml, product.Id, productSeName); + pvacModel.ProductUrlTitle = productUrlTitle; - try - { - var firstAttribute = _productAttributeParser.DeserializeProductVariantAttributes(x.AttributesXml).FirstOrDefault(); + try + { + var firstAttribute = _productAttributeParser.DeserializeProductVariantAttributes(x.AttributesXml).FirstOrDefault(); - var attribute = x.Product.ProductVariantAttributes.FirstOrDefault(y => y.Id == firstAttribute.Key); - var attributeValue = attribute.ProductVariantAttributeValues.FirstOrDefault(y => y.Id == int.Parse(firstAttribute.Value.First())); + var attribute = x.Product.ProductVariantAttributes.FirstOrDefault(y => y.Id == firstAttribute.Key); + var attributeValue = attribute.ProductVariantAttributeValues.FirstOrDefault(y => y.Id == int.Parse(firstAttribute.Value.First())); - pvacModel.DisplayOrder = attributeValue.DisplayOrder; - } - catch (Exception exc) - { - exc.Dump(); - } + pvacModel.DisplayOrder = attributeValue.DisplayOrder; + } + catch (Exception exc) + { + exc.Dump(); + } - //if (x.IsDefaultCombination) - // pvacModel.AttributesXml = "{0}".FormatWith(pvacModel.AttributesXml); + //if (x.IsDefaultCombination) + // pvacModel.AttributesXml = "{0}".FormatWith(pvacModel.AttributesXml); - //warnings - var warnings = _shoppingCartService.GetShoppingCartItemAttributeWarnings(_workContext.CurrentCustomer, ShoppingCartType.ShoppingCart, x.Product, x.AttributesXml); - pvacModel.Warnings.AddRange(warnings); + //warnings + var warnings = _shoppingCartService.GetShoppingCartItemAttributeWarnings( + _workContext.CurrentCustomer, + ShoppingCartType.ShoppingCart, + x.Product, + x.AttributesXml, + combination: x); - return pvacModel; - }) - .OrderBy(x => x.DisplayOrder).ToList(); + pvacModel.Warnings.AddRange(warnings); - var model = new GridModel + return pvacModel; + }) + .OrderBy(x => x.DisplayOrder) + .ToList(); + + model.Data = productVariantAttributesModel; + model.Total = allCombinations.TotalCount; + } + else { - Data = productVariantAttributesModel, - Total = productVariantAttributesModel.Count - }; + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -4115,19 +3390,16 @@ public ActionResult ProductVariantAttributeCombinationList(GridCommand command, [GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeCombinationDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var pvac = _productAttributeService.GetProductVariantAttributeCombinationById(id); - if (pvac == null) - throw new ArgumentException("No product variant attribute combination found with the specified id"); - var productId = pvac.ProductId; - _productAttributeService.DeleteProductVariantAttributeCombination(pvac); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productAttributeService.DeleteProductVariantAttributeCombination(pvac); - var product = _productService.GetProductById(productId); - _productService.UpdateLowestAttributeCombinationPriceProperty(product); + var product = _productService.GetProductById(productId); + _productService.UpdateLowestAttributeCombinationPriceProperty(product); + } return ProductVariantAttributeCombinationList(command, productId); } @@ -4166,12 +3438,20 @@ public ActionResult AttributeCombinationCreatePopup(string btnId, string formId, var warnings = new List(); var variantAttributes = _productAttributeService.GetProductVariantAttributesByProductId(product.Id); - string attributeXml = form.CreateSelectedAttributesXml(product.Id, variantAttributes, _productAttributeParser, _localizationService, - _downloadService, _catalogSettings, this.Request, warnings, false); + string attributeXml = form.CreateSelectedAttributesXml( + product.Id, + variantAttributes, + _productAttributeParser, + _localizationService, + _downloadService, + _catalogSettings, + this.Request, + warnings, + false); warnings.AddRange(_shoppingCartService.GetShoppingCartItemAttributeWarnings(_workContext.CurrentCustomer, ShoppingCartType.ShoppingCart, product, attributeXml)); - if (null != _productAttributeParser.FindProductVariantAttributeCombination(product, attributeXml)) + if (_productAttributeParser.FindProductVariantAttributeCombination(product.Id, attributeXml) != null) { warnings.Add(_localizationService.GetResource("Admin.Catalog.Products.ProductVariantAttributes.AttributeCombinations.CombiExists")); } @@ -4261,7 +3541,7 @@ public ActionResult CreateAllAttributeCombinations(ProductVariantAttributeCombin var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); _productAttributeService.CreateAllProductVariantAttributeCombinations(product); @@ -4279,12 +3559,9 @@ public ActionResult DeleteAllAttributeCombinations(ProductVariantAttributeCombin var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); - foreach (var combination in _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id)) - { - _productAttributeService.DeleteProductVariantAttributeCombination(combination); - } + _pvacRepository.DeleteAll(x => x.ProductId == product.Id); _productService.UpdateLowestAttributeCombinationPriceProperty(product); @@ -4302,7 +3579,7 @@ public ActionResult CombinationExistenceNote(int productId, FormCollection form) string attributeXml = form.CreateSelectedAttributesXml(productId, attributes, _productAttributeParser, _localizationService, _downloadService, _catalogSettings, this.Request, warnings, false); - bool exists = (null != _productAttributeParser.FindProductVariantAttributeCombination(productId, attributeXml)); + bool exists = (_productAttributeParser.FindProductVariantAttributeCombination(productId, attributeXml) != null); if (!exists) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductReviewController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductReviewController.cs index 89155f4fb7..5f63d63b5b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductReviewController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductReviewController.cs @@ -11,11 +11,13 @@ using SmartStore.Services.Security; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public class ProductReviewController : AdminControllerBase { #region Fields @@ -107,34 +109,40 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, ProductReviewListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + DateTime? createdOnFromValue = (model.CreatedOnFrom == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnFrom.Value, _dateTimeHelper.CurrentTimeZone); - DateTime? createdOnFromValue = (model.CreatedOnFrom == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnFrom.Value, _dateTimeHelper.CurrentTimeZone); + DateTime? createdToFromValue = (model.CreatedOnTo == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - DateTime? createdToFromValue = (model.CreatedOnTo == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + var productReviews = _customerContentService.GetAllCustomerContent(0, null, createdOnFromValue, createdToFromValue); + + gridModel.Data = productReviews.PagedForCommand(command).Select(x => + { + var m = new ProductReviewModel(); + PrepareProductReviewModel(m, x, false, true); + return m; + }); + + gridModel.Total = productReviews.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var productReviews = _customerContentService.GetAllCustomerContent(0, null, - createdOnFromValue, createdToFromValue); - var gridModel = new GridModel - { - Data = productReviews.PagedForCommand(command).Select(x => - { - var m = new ProductReviewModel(); - PrepareProductReviewModel(m, x, false, true); - return m; - }), - Total = productReviews.Count, - }; return new JsonResult { Data = gridModel }; } - //edit public ActionResult Edit(int id) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -142,15 +150,15 @@ public ActionResult Edit(int id) var productReview = _customerContentService.GetCustomerContentById(id) as ProductReview; if (productReview == null) - //No product review found with the specified id return RedirectToAction("List"); var model = new ProductReviewModel(); PrepareProductReviewModel(model, productReview, false, false); + return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(ProductReviewModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -158,7 +166,6 @@ public ActionResult Edit(ProductReviewModel model, bool continueEditing) var productReview = _customerContentService.GetCustomerContentById(model.Id) as ProductReview; if (productReview == null) - //No product review found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -178,13 +185,11 @@ public ActionResult Edit(ProductReviewModel model, bool continueEditing) return continueEditing ? RedirectToAction("Edit", productReview.Id) : RedirectToAction("List"); } - //If we got this far, something failed, redisplay form PrepareProductReviewModel(model, productReview, true, false); return View(model); } - //delete [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { @@ -193,11 +198,11 @@ public ActionResult DeleteConfirmed(int id) var productReview = _customerContentService.GetCustomerContentById(id) as ProductReview; if (productReview == null) - //No product review found with the specified id return RedirectToAction("List"); var product = productReview.Product; _customerContentService.DeleteCustomerContent(productReview); + //update product totals _productService.UpdateProductReviewTotals(product); @@ -208,84 +213,107 @@ public ActionResult DeleteConfirmed(int id) [HttpPost] public ActionResult ApproveSelected(ICollection selectedIds) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var result = true; - if (selectedIds != null) - { - foreach (var id in selectedIds) - { - var productReview = _customerContentService.GetCustomerContentById(id) as ProductReview; - if (productReview != null) - { - productReview.IsApproved = true; - _customerContentService.UpdateCustomerContent(productReview); - - //update product totals - _productService.UpdateProductReviewTotals(productReview.Product); - - _customerService.RewardPointsForProductReview(productReview.Customer, productReview.Product, true); - } - } - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + if (selectedIds != null) + { + foreach (var id in selectedIds) + { + var productReview = _customerContentService.GetCustomerContentById(id) as ProductReview; + if (productReview != null) + { + productReview.IsApproved = true; + _customerContentService.UpdateCustomerContent(productReview); + + //update product totals + _productService.UpdateProductReviewTotals(productReview.Product); + + _customerService.RewardPointsForProductReview(productReview.Customer, productReview.Product, true); + } + } + } + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + } + else + { + result = false; + NotifyAccessDenied(); + } - return Json(new { Result = true }); + return Json(new { Result = result }); } [HttpPost] public ActionResult DeleteSelected(ICollection selectedIds) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - if (selectedIds != null) - { - foreach (var id in selectedIds) - { - var productReview = _customerContentService.GetCustomerContentById(id) as ProductReview; - if (productReview != null) - { - var product = productReview.Product; - _customerContentService.DeleteCustomerContent(productReview); - //update product totals - _productService.UpdateProductReviewTotals(product); - } - } - } + var result = true; - return Json(new { Result = true }); - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + if (selectedIds != null) + { + foreach (var id in selectedIds) + { + var productReview = _customerContentService.GetCustomerContentById(id) as ProductReview; + if (productReview != null) + { + var product = productReview.Product; + _customerContentService.DeleteCustomerContent(productReview); + //update product totals + _productService.UpdateProductReviewTotals(product); + } + } + } + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + } + else + { + result = false; + NotifyAccessDenied(); + } + return Json(new { Result = result }); + } [HttpPost] public ActionResult DisapproveSelected(ICollection selectedIds) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - if (selectedIds != null) - { - foreach (var id in selectedIds) - { - var productReview = _customerContentService.GetCustomerContentById(id) as ProductReview; - if (productReview != null) - { - productReview.IsApproved = false; - _customerContentService.UpdateCustomerContent(productReview); - - //update product totals - _productService.UpdateProductReviewTotals(productReview.Product); - - _customerService.RewardPointsForProductReview(productReview.Customer, productReview.Product, false); - } - } - } - - return Json(new { Result = true }); - } - + var result = true; + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + if (selectedIds != null) + { + foreach (var id in selectedIds) + { + var productReview = _customerContentService.GetCustomerContentById(id) as ProductReview; + if (productReview != null) + { + productReview.IsApproved = false; + _customerContentService.UpdateCustomerContent(productReview); + + //update product totals + _productService.UpdateProductReviewTotals(productReview.Product); + + _customerService.RewardPointsForProductReview(productReview.Customer, productReview.Product, false); + } + } + } + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + } + else + { + result = false; + NotifyAccessDenied(); + } + return Json(new { Result = result }); + } #endregion } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/QuantityUnitController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/QuantityUnitController.cs index 85bfe5b8db..3649c5522f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/QuantityUnitController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/QuantityUnitController.cs @@ -1,16 +1,15 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Directory; -using SmartStore.Core; using SmartStore.Core.Domain.Directory; -using SmartStore.Services.Configuration; +using SmartStore.Services; using SmartStore.Services.Directory; -using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -21,29 +20,24 @@ public class QuantityUnitController : AdminControllerBase #region Fields private readonly IQuantityUnitService _quantityUnitService; - private readonly ISettingService _settingService; - private readonly ILocalizationService _localizationService; - private readonly IPermissionService _permissionService; private readonly ILocalizedEntityService _localizedEntityService; private readonly ILanguageService _languageService; + private readonly ICommonServices _services; #endregion #region Constructors - public QuantityUnitController(IQuantityUnitService quantityUnitService, - ISettingService settingService, - ILocalizationService localizationService, - IPermissionService permissionService, + public QuantityUnitController( + IQuantityUnitService quantityUnitService, ILocalizedEntityService localizedEntityService, - ILanguageService languageService) + ILanguageService languageService, + ICommonServices services) { - this._quantityUnitService = quantityUnitService; - this._settingService = settingService; - this._localizationService = localizationService; - this._permissionService = permissionService; - this._localizedEntityService = localizedEntityService; - this._languageService = languageService; + _quantityUnitService = quantityUnitService; + _localizedEntityService = localizedEntityService; + _languageService = languageService; + _services = services; } #endregion @@ -60,7 +54,6 @@ public void UpdateLocales(QuantityUnit quantityUnit, QuantityUnitModel model) } } - #endregion #region Methods @@ -72,7 +65,7 @@ public ActionResult Index() public ActionResult List() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); var quantityUnitModel = _quantityUnitService.GetAllQuantityUnits().Select(x => x.ToModel()).ToList(); @@ -111,7 +104,7 @@ public ActionResult AllQuantityUnits(string label, int selectedId) public ActionResult Create() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); var model = new QuantityUnitModel(); @@ -121,10 +114,10 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(QuantityUnitModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); if (ModelState.IsValid) @@ -135,7 +128,7 @@ public ActionResult Create(QuantityUnitModel model, bool continueEditing) //locales UpdateLocales(quantityUnit, model); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.QuantityUnit.Added")); + NotifySuccess(T("Admin.Configuration.QuantityUnit.Added")); return continueEditing ? RedirectToAction("Edit", new { id = quantityUnit.Id }) : RedirectToAction("List"); } @@ -145,32 +138,32 @@ public ActionResult Create(QuantityUnitModel model, bool continueEditing) public ActionResult Edit(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); var quantityUnit = _quantityUnitService.GetQuantityUnitById(id); if (quantityUnit == null) - //No currency found with the specified id return RedirectToAction("List"); var model = quantityUnit.ToModel(); - //locales + AddLocales(_languageService, model.Locales, (locale, languageId) => { locale.Name = quantityUnit.GetLocalized(x => x.Name, languageId, false, false); + locale.Description = quantityUnit.GetLocalized(x => x.Description, languageId, false, false); }); + return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(QuantityUnitModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); var quantityUnit = _quantityUnitService.GetQuantityUnitById(model.Id); if (quantityUnit == null) - //No currency found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -178,14 +171,10 @@ public ActionResult Edit(QuantityUnitModel model, bool continueEditing) quantityUnit = model.ToEntity(quantityUnit); UpdateLocales(quantityUnit, model); - _quantityUnitService.UpdateQuantityUnit(quantityUnit); - if (model.IsDefault) - { - - } + _quantityUnitService.UpdateQuantityUnit(quantityUnit); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.QuantityUnits.Updated")); + NotifySuccess(T("Admin.Configuration.QuantityUnits.Updated")); return continueEditing ? RedirectToAction("Edit", new { id = quantityUnit.Id }) : RedirectToAction("List"); } @@ -195,7 +184,7 @@ public ActionResult Edit(QuantityUnitModel model, bool continueEditing) [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); var quantityUnit = _quantityUnitService.GetQuantityUnitById(id); @@ -208,7 +197,7 @@ public ActionResult DeleteConfirmed(int id) _quantityUnitService.DeleteQuantityUnit(quantityUnit); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.QuantityUnits.Deleted")); + NotifySuccess(T("Admin.Configuration.QuantityUnits.Deleted")); return RedirectToAction("List"); } catch (Exception exc) @@ -221,7 +210,7 @@ public ActionResult DeleteConfirmed(int id) [HttpPost] public ActionResult Save(FormCollection formValues) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); return RedirectToAction("List", "QuantityUnit"); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs index a3cd96ded8..7814196042 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Web; using System.Web.Mvc; using SmartStore.Admin.Models.Messages; using SmartStore.Core.Domain.Messages; @@ -8,7 +9,10 @@ using SmartStore.Services.Localization; using SmartStore.Services.Messages; using SmartStore.Services.Security; +using SmartStore.Utilities; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -21,7 +25,8 @@ public class QueuedEmailController : AdminControllerBase private readonly ILocalizationService _localizationService; private readonly IPermissionService _permissionService; - public QueuedEmailController(IQueuedEmailService queuedEmailService, + public QueuedEmailController( + IQueuedEmailService queuedEmailService, IDateTimeHelper dateTimeHelper, ILocalizationService localizationService, IPermissionService permissionService) { @@ -48,30 +53,48 @@ public ActionResult List() [GridAction(EnableCustomBinding = true)] public ActionResult QueuedEmailList(GridCommand command, QueuedEmailListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMessageQueue)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageMessageQueue)) + { + DateTime? startDateValue = (model.SearchStartDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.SearchStartDate.Value, _dateTimeHelper.CurrentTimeZone); + DateTime? endDateValue = (model.SearchEndDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.SearchEndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - DateTime? startDateValue = (model.SearchStartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.SearchStartDate.Value, _dateTimeHelper.CurrentTimeZone); + var q = new SearchEmailsQuery + { + EndTime = endDateValue, + From = model.SearchFromEmail, + MaxSendTries = model.SearchMaxSentTries, + OrderByLatest = true, + PageIndex = command.Page - 1, + PageSize = command.PageSize, + SendManually = model.SearchSendManually, + StartTime = startDateValue, + To = model.SearchToEmail, + UnsentOnly = model.SearchLoadNotSent + }; + var queuedEmails = _queuedEmailService.SearchEmails(q); - DateTime? endDateValue = (model.SearchEndDate == null) ? null - :(DateTime?)_dateTimeHelper.ConvertToUtcTime(model.SearchEndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + gridModel.Data = queuedEmails.Select(x => + { + var m = x.ToModel(); + m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + + if (x.SentOnUtc.HasValue) + m.SentOn = _dateTimeHelper.ConvertToUserTime(x.SentOnUtc.Value, DateTimeKind.Utc); + + return m; + }); + + gridModel.Total = queuedEmails.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var queuedEmails = _queuedEmailService.SearchEmails(model.SearchFromEmail, model.SearchToEmail, - startDateValue, endDateValue, - model.SearchLoadNotSent, model.SearchMaxSentTries, true, - command.Page - 1, command.PageSize); - var gridModel = new GridModel - { - Data = queuedEmails.Select(x => { - var m = x.ToModel(); - m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - if (x.SentOnUtc.HasValue) - m.SentOn = _dateTimeHelper.ConvertToUserTime(x.SentOnUtc.Value, DateTimeKind.Utc); - return m; - }), - Total = queuedEmails.TotalCount - }; return new JsonResult { Data = gridModel @@ -96,13 +119,13 @@ public ActionResult Edit(int id) var email = _queuedEmailService.GetQueuedEmailById(id); if (email == null) - //No email found with the specified id return RedirectToAction("List"); var model = email.ToModel(); model.CreatedOn = _dateTimeHelper.ConvertToUserTime(email.CreatedOnUtc, DateTimeKind.Utc); if (email.SentOnUtc.HasValue) model.SentOn = _dateTimeHelper.ConvertToUserTime(email.SentOnUtc.Value, DateTimeKind.Utc); + return View(model); } @@ -116,7 +139,6 @@ public ActionResult Edit(QueuedEmailModel model, bool continueEditing) var email = _queuedEmailService.GetQueuedEmailById(model.Id); if (email == null) - //No email found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -132,6 +154,7 @@ public ActionResult Edit(QueuedEmailModel model, bool continueEditing) model.CreatedOn = _dateTimeHelper.ConvertToUserTime(email.CreatedOnUtc, DateTimeKind.Utc); if (email.SentOnUtc.HasValue) model.SentOn = _dateTimeHelper.ConvertToUserTime(email.SentOnUtc.Value, DateTimeKind.Utc); + return View(model); } @@ -143,10 +166,9 @@ public ActionResult Requeue(QueuedEmailModel queuedEmailModel) var queuedEmail = _queuedEmailService.GetQueuedEmailById(queuedEmailModel.Id); if (queuedEmail == null) - //No email found with the specified id return RedirectToAction("List"); - var requeuedEmail = new QueuedEmail() + var requeuedEmail = new QueuedEmail { Priority = queuedEmail.Priority, From = queuedEmail.From, @@ -158,14 +180,36 @@ public ActionResult Requeue(QueuedEmailModel queuedEmailModel) Subject = queuedEmail.Subject, Body = queuedEmail.Body, CreatedOnUtc = DateTime.UtcNow, - EmailAccountId = queuedEmail.EmailAccountId + EmailAccountId = queuedEmail.EmailAccountId, + SendManually = queuedEmail.SendManually }; _queuedEmailService.InsertQueuedEmail(requeuedEmail); NotifySuccess(_localizationService.GetResource("Admin.System.QueuedEmails.Requeued")); + return RedirectToAction("Edit", requeuedEmail.Id); } + [HttpPost, ActionName("Edit"), FormValueRequired("sendnow")] + public ActionResult SendNow(QueuedEmailModel queuedEmailModel) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageMessageQueue)) + return AccessDeniedView(); + + var queuedEmail = _queuedEmailService.GetQueuedEmailById(queuedEmailModel.Id); + if (queuedEmail == null) + return RedirectToAction("List"); + + var result = _queuedEmailService.SendEmail(queuedEmail); + + if (result) + NotifySuccess(_localizationService.GetResource("Admin.Common.TaskSuccessfullyProcessed")); + else + NotifyError(_localizationService.GetResource("Common.Error.SendMail")); + + return RedirectToAction("Edit", queuedEmail.Id); + } + [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { @@ -174,12 +218,12 @@ public ActionResult DeleteConfirmed(int id) var email = _queuedEmailService.GetQueuedEmailById(id); if (email == null) - //No email found with the specified id return RedirectToAction("List"); _queuedEmailService.DeleteQueuedEmail(email); NotifySuccess(_localizationService.GetResource("Admin.System.QueuedEmails.Deleted")); + return RedirectToAction("List"); } @@ -192,11 +236,72 @@ public ActionResult DeleteSelected(ICollection selectedIds) if (selectedIds != null) { var queuedEmails = _queuedEmailService.GetQueuedEmailsByIds(selectedIds.ToArray()); - foreach (var queuedEmail in queuedEmails) - _queuedEmailService.DeleteQueuedEmail(queuedEmail); + + foreach (var queuedEmail in queuedEmails) + { + _queuedEmailService.DeleteQueuedEmail(queuedEmail); + } } return Json(new { Result = true }); } + + [HttpPost, ActionName("List"), FormValueRequired("delete-all")] + public ActionResult DeleteAll() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageMessageQueue)) + return AccessDeniedView(); + + int count = _queuedEmailService.DeleteAllQueuedEmails(); + + NotifySuccess(T("Admin.Common.RecordsDeleted", count)); + + return RedirectToAction("List"); + } + + public ActionResult DownloadAttachment(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageMessageQueue)) + return AccessDeniedView(); + + var qea = _queuedEmailService.GetQueuedEmailAttachmentById(id); + if (qea == null) + return HttpNotFound("List"); + + if (qea.StorageLocation == EmailAttachmentStorageLocation.Blob) + { + return File(qea.Data, qea.MimeType, qea.Name); + } + else if (qea.StorageLocation == EmailAttachmentStorageLocation.Path) + { + var path = qea.Path; + if (path[0] == '~' || path[0] == '/') + { + path = CommonHelper.MapPath(VirtualPathUtility.ToAppRelative(path), false); + } + + if (!System.IO.File.Exists(path)) + { + NotifyError(string.Concat(T("Admin.Common.FileNotFound"), ": ", path)); + + var referrer = Services.WebHelper.GetUrlReferrer(); + if (referrer.HasValue()) + return Redirect(referrer); + + return RedirectToAction("List"); + } + + return File(path, qea.MimeType, qea.Name); + } + + // FileReference + if (qea.FileId.HasValue) + { + return RedirectToAction("DownloadFile", "Download", new { downloadId = qea.FileId.Value }); + } + + NotifyError(T("Admin.System.QueuedEmails.CouldNotDownloadAttachment")); + return RedirectToAction("List"); + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/RecurringPaymentController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/RecurringPaymentController.cs index 215b42d3d0..0f4b15968e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/RecurringPaymentController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/RecurringPaymentController.cs @@ -12,6 +12,8 @@ using SmartStore.Services.Security; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -111,7 +113,6 @@ private void PrepareRecurringPaymentHistoryModel(RecurringPaymentModel.Recurring #region Recurring payment - //list public ActionResult Index() { return RedirectToAction("List"); @@ -129,27 +130,34 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var payments = _orderService.SearchRecurringPayments(0, 0, 0, null, true); + + gridModel.Data = payments.PagedForCommand(command).Select(x => + { + var m = new RecurringPaymentModel(); + PrepareRecurringPaymentModel(m, x, false); + return m; + }); + + gridModel.Total = payments.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var payments = _orderService.SearchRecurringPayments(0, 0, 0, null, true); - var gridModel = new GridModel - { - Data = payments.PagedForCommand(command).Select(x => - { - var m = new RecurringPaymentModel(); - PrepareRecurringPaymentModel(m, x, false); - return m; - }), - Total = payments.Count, - }; return new JsonResult { Data = gridModel }; } - //edit public ActionResult Edit(int id) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) @@ -165,7 +173,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [FormValueRequired("save", "save-continue")] public ActionResult Edit(RecurringPaymentModel model, bool continueEditing) { @@ -187,7 +195,6 @@ public ActionResult Edit(RecurringPaymentModel model, bool continueEditing) return continueEditing ? RedirectToAction("Edit", payment.Id) : RedirectToAction("List"); } - //delete [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { @@ -212,26 +219,30 @@ public ActionResult DeleteConfirmed(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult HistoryList(int recurringPaymentId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var payment = _orderService.GetRecurringPaymentById(recurringPaymentId); - if (payment == null) - throw new ArgumentException("No recurring payment found with the specified id"); - - var historyModel = payment.RecurringPaymentHistory.OrderBy(x => x.CreatedOnUtc) - .Select(x => - { - var m = new RecurringPaymentModel.RecurringPaymentHistoryModel(); - PrepareRecurringPaymentHistoryModel(m, x); - return m; - }) - .ToList(); - var model = new GridModel - { - Data = historyModel, - Total = historyModel.Count - }; + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var payment = _orderService.GetRecurringPaymentById(recurringPaymentId); + + var historyModel = payment.RecurringPaymentHistory.OrderBy(x => x.CreatedOnUtc) + .Select(x => + { + var m = new RecurringPaymentModel.RecurringPaymentHistoryModel(); + PrepareRecurringPaymentHistoryModel(m, x); + return m; + }) + .ToList(); + + model.Data = historyModel; + model.Total = historyModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ReturnRequestController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ReturnRequestController.cs index e6c75f0b05..9e65217d0c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ReturnRequestController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ReturnRequestController.cs @@ -1,28 +1,30 @@ using System; -using System.Linq; using System.Collections.Generic; +using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Orders; using SmartStore.Core; +using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Logging; +using SmartStore.Services.Catalog; using SmartStore.Services.Customers; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; -using SmartStore.Core.Logging; using SmartStore.Services.Messages; using SmartStore.Services.Orders; using SmartStore.Services.Security; -using SmartStore.Services.Catalog; +using SmartStore.Services.Stores; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; -using SmartStore.Core.Domain.Common; -using SmartStore.Services.Stores; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public class ReturnRequestController : AdminControllerBase { #region Fields @@ -76,15 +78,11 @@ public ReturnRequestController(IOrderService orderService, private void PrepareReturnRequestListModel(ReturnRequestListModel model) { - string allString = _localizationService.GetResource("Admin.Common.All"); - model.GridPageSize = _adminAreaSettings.GridPageSize; - model.AvailableStores.Add(new SelectListItem() { Text = allString, Value = "0" }); - model.AvailableStores.AddRange(_storeService.GetAllStores().ToSelectListItems()); + model.AvailableStores = _storeService.GetAllStores().ToSelectListItems(); model.AvailableReturnRequestStatus = ReturnRequestStatus.Pending.ToSelectList(false).ToList(); - model.AvailableReturnRequestStatus.Insert(0, new SelectListItem() { Text = allString }); } private bool PrepareReturnRequestModel(ReturnRequestModel model, ReturnRequest returnRequest, bool excludeProperties) @@ -116,13 +114,19 @@ private bool PrepareReturnRequestModel(ReturnRequestModel model, ReturnRequest r model.Quantity = returnRequest.Quantity; model.ReturnRequestStatusStr = returnRequest.ReturnRequestStatus.GetLocalizedEnum(_localizationService, _workContext); model.CreatedOn = _dateTimeHelper.ConvertToUserTime(returnRequest.CreatedOnUtc, DateTimeKind.Utc); + model.UpdatedOn = _dateTimeHelper.ConvertToUserTime(returnRequest.UpdatedOnUtc, DateTimeKind.Utc); if (!excludeProperties) { model.ReasonForReturn = returnRequest.ReasonForReturn; model.RequestedAction = returnRequest.RequestedAction; + + if (returnRequest.RequestedActionUpdatedOnUtc.HasValue) + model.RequestedActionUpdated = _dateTimeHelper.ConvertToUserTime(returnRequest.RequestedActionUpdatedOnUtc.Value, DateTimeKind.Utc); + model.CustomerComments = returnRequest.CustomerComments; model.StaffNotes = returnRequest.StaffNotes; + model.AdminComment = returnRequest.AdminComment; model.ReturnRequestStatusId = returnRequest.ReturnRequestStatusId; } @@ -163,7 +167,6 @@ private bool PrepareReturnRequestModel(ReturnRequestModel model, ReturnRequest r #region Methods - //list public ActionResult Index() { return RedirectToAction("List"); @@ -183,26 +186,31 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, ReturnRequestListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageReturnRequests)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageReturnRequests)) + { + var data = new List(); - var data = new List(); + var returnRequests = _orderService.SearchReturnRequests(model.SearchStoreId, 0, 0, model.SearchReturnRequestStatus, + command.Page - 1, command.PageSize, model.SearchId ?? 0); - var returnRequests = _orderService.SearchReturnRequests(model.SearchStoreId, 0, 0, model.SearchReturnRequestStatus, - command.Page - 1, command.PageSize, model.SearchId ?? 0); + foreach (var rr in returnRequests) + { + var m = new ReturnRequestModel(); + if (PrepareReturnRequestModel(m, rr, false)) + data.Add(m); + } - foreach (var rr in returnRequests) - { - var m = new ReturnRequestModel(); - if (PrepareReturnRequestModel(m, rr, false)) - data.Add(m); + gridModel.Data = data; + gridModel.Total = returnRequests.TotalCount; } - - var gridModel = new GridModel + else { - Data = data, - Total = returnRequests.TotalCount, - }; + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -210,7 +218,6 @@ public ActionResult List(GridCommand command, ReturnRequestListModel model) }; } - //edit public ActionResult Edit(int id) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageReturnRequests)) @@ -225,7 +232,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [FormValueRequired("save", "save-continue")] public ActionResult Edit(ReturnRequestModel model, bool continueEditing) { @@ -238,13 +245,19 @@ public ActionResult Edit(ReturnRequestModel model, bool continueEditing) if (ModelState.IsValid) { + var utcNow = DateTime.UtcNow; + + if (returnRequest.RequestedAction != model.RequestedAction) + returnRequest.RequestedActionUpdatedOnUtc = utcNow; + returnRequest.Quantity = model.Quantity; returnRequest.ReasonForReturn = model.ReasonForReturn; returnRequest.RequestedAction = model.RequestedAction; returnRequest.CustomerComments = model.CustomerComments; returnRequest.StaffNotes = model.StaffNotes; + returnRequest.AdminComment = model.AdminComment; returnRequest.ReturnRequestStatusId = model.ReturnRequestStatusId; - returnRequest.UpdatedOnUtc = DateTime.UtcNow; + returnRequest.UpdatedOnUtc = utcNow; if (returnRequest.ReasonForReturn == null) returnRequest.ReasonForReturn = ""; @@ -261,7 +274,6 @@ public ActionResult Edit(ReturnRequestModel model, bool continueEditing) return continueEditing ? RedirectToAction("Edit", returnRequest.Id) : RedirectToAction("List"); } - //If we got this far, something failed, redisplay form PrepareReturnRequestModel(model, returnRequest, true); return View(model); @@ -287,7 +299,6 @@ public ActionResult NotifyCustomer(ReturnRequestModel model) return RedirectToAction("Edit", returnRequest.Id); } - //delete [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs new file mode 100644 index 0000000000..e0c2ca36c2 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs @@ -0,0 +1,828 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.IO.Compression; +using System.Text.RegularExpressions; +using System.Web; +using SmartStore.Services.Security; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; + +namespace SmartStore.Admin.Controllers +{ + [AdminAuthorize] + public class RoxyFileManagerController : AdminControllerBase + { + private const string CONFIG_FILE = "~/Administration/Content/filemanager/conf.json"; + + private Dictionary _lang = null; + private Dictionary _settings = null; + + private readonly IPermissionService _permissionService; + private readonly HttpContextBase _context; + private readonly HttpResponseBase _response; + + public RoxyFileManagerController( + IPermissionService permissionService, + HttpContextBase context) + { + _permissionService = permissionService; + _context = context; + _response = _context.Response; + } + + #region Utilities + + private string FixPath(string path) + { + if (path == null) + { + path = ""; + } + + if (!path.StartsWith("~")) + { + if (!path.StartsWith("/")) + path = "/" + path; + path = "~" + path; + } + + //var filesRoot = GetSetting("FILES_ROOT"); + //if (!path.ToLowerInvariant().Contains(filesRoot.ToLowerInvariant())) + //{ + // path = filesRoot; + //} + + return _context.Server.MapPath(path); + } + + private string GetLangFile() + { + //return "../lang/en.json"; + + var filename = "../lang/" + GetSetting("LANG") + ".json"; + + if (!System.IO.File.Exists(_context.Server.MapPath(filename))) + filename = "../lang/en.json"; + + return filename; + } + + protected string LangRes(string name) + { + string ret = name; + if (_lang == null) + _lang = ParseJSON(GetLangFile()); + if (_lang.ContainsKey(name)) + ret = _lang[name]; + + return ret; + } + + protected string GetFileType(string ext) + { + string ret = "file"; + ext = ext.ToLower(); + if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif") + ret = "image"; + else if (ext == ".swf" || ext == ".flv") + ret = "flash"; + return ret; + } + + protected bool CanHandleFile(string filename) + { + bool ret = false; + FileInfo file = new FileInfo(filename); + string ext = file.Extension.Replace(".", "").ToLower(); + string setting = GetSetting("FORBIDDEN_UPLOADS").Trim().ToLower(); + if (setting != "") + { + ArrayList tmp = new ArrayList(); + tmp.AddRange(Regex.Split(setting, "\\s+")); + if (!tmp.Contains(ext)) + ret = true; + } + setting = GetSetting("ALLOWED_UPLOADS").Trim().ToLower(); + if (setting != "") + { + ArrayList tmp = new ArrayList(); + tmp.AddRange(Regex.Split(setting, "\\s+")); + if (!tmp.Contains(ext)) + ret = false; + } + + return ret; + } + + protected Dictionary ParseJSON(string file) + { + Dictionary ret = new Dictionary(); + string json = ""; + try + { + json = System.IO.File.ReadAllText(_context.Server.MapPath(file), System.Text.Encoding.UTF8); + } + catch { } + + json = json.Trim(); + if (json != "") + { + if (json.StartsWith("{")) + json = json.Substring(1, json.Length - 2); + json = json.Trim(); + json = json.Substring(1, json.Length - 2); + string[] lines = Regex.Split(json, "\"\\s*,\\s*\""); + foreach (string line in lines) + { + string[] tmp = Regex.Split(line, "\"\\s*:\\s*\""); + try + { + if (tmp[0] != "" && !ret.ContainsKey(tmp[0])) + { + ret.Add(tmp[0], tmp[1]); + } + } + catch { } + } + } + return ret; + } + + protected string GetFilesRoot() + { + string ret = GetSetting("FILES_ROOT"); + if (GetSetting("SESSION_PATH_KEY") != "" && _context.Session[GetSetting("SESSION_PATH_KEY")] != null) + ret = (string)_context.Session[GetSetting("SESSION_PATH_KEY")]; + + if (ret == "") + ret = _context.Server.MapPath("../Uploads"); + else + ret = FixPath(ret); + return ret; + } + + protected void LoadConf() + { + if (_settings == null) + _settings = ParseJSON(CONFIG_FILE); + } + + protected string GetSetting(string name) + { + string ret = ""; + LoadConf(); + if (_settings.ContainsKey(name)) + ret = _settings[name]; + + return ret; + } + + protected void CheckPath(string path) + { + if (FixPath(path).IndexOf(GetFilesRoot()) != 0) + { + throw new Exception("Access to " + path + " is denied"); + } + } + + protected void VerifyAction(string action) + { + string setting = GetSetting(action); + if (setting.IndexOf("?") > -1) + setting = setting.Substring(0, setting.IndexOf("?")); + if (!setting.StartsWith("/")) + setting = "/" + setting; + setting = ".." + setting; + + if (_context.Server.MapPath(setting) != _context.Server.MapPath(_context.Request.Url.LocalPath)) + throw new Exception(LangRes("E_ActionDisabled")); + } + + protected string GetResultStr(string type, string msg) + { + return "{\"res\":\"" + type + "\",\"msg\":\"" + msg.Replace("\"", "\\\"") + "\"}"; + } + + protected string GetSuccessRes(string msg) + { + return GetResultStr("ok", msg); + } + + protected string GetSuccessRes() + { + return GetSuccessRes(""); + } + + protected string GetErrorRes(string msg) + { + return GetResultStr("error", msg); + } + + private void _copyDir(string path, string dest) + { + if (!Directory.Exists(dest)) + Directory.CreateDirectory(dest); + foreach (string f in Directory.GetFiles(path)) + { + FileInfo file = new FileInfo(f); + if (!System.IO.File.Exists(Path.Combine(dest, file.Name))) + { + System.IO.File.Copy(f, Path.Combine(dest, file.Name)); + } + } + foreach (string d in Directory.GetDirectories(path)) + { + DirectoryInfo dir = new DirectoryInfo(d); + _copyDir(d, Path.Combine(dest, dir.Name)); + } + } + + protected void CopyDir(string path, string newPath) + { + CheckPath(path); + CheckPath(newPath); + DirectoryInfo dir = new DirectoryInfo(FixPath(path)); + DirectoryInfo newDir = new DirectoryInfo(FixPath(newPath + "/" + dir.Name)); + + if (!dir.Exists) + { + throw new Exception(LangRes("E_CopyDirInvalidPath")); + } + else if (newDir.Exists) + { + throw new Exception(LangRes("E_DirAlreadyExists")); + } + else + { + _copyDir(dir.FullName, newDir.FullName); + } + _response.Write(GetSuccessRes()); + } + + protected string MakeUniqueFilename(string dir, string filename) + { + string ret = filename; + int i = 0; + while (System.IO.File.Exists(Path.Combine(dir, ret))) + { + i++; + ret = Path.GetFileNameWithoutExtension(filename) + " - Copy " + i.ToString() + Path.GetExtension(filename); + } + return ret; + } + + protected void CopyFile(string path, string newPath) + { + CheckPath(path); + FileInfo file = new FileInfo(FixPath(path)); + newPath = FixPath(newPath); + if (!file.Exists) + throw new Exception(LangRes("E_CopyFileInvalisPath")); + else + { + string newName = MakeUniqueFilename(newPath, file.Name); + try + { + System.IO.File.Copy(file.FullName, Path.Combine(newPath, newName)); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_CopyFile")); + } + } + } + + protected void CreateDir(string path, string name) + { + CheckPath(path); + path = FixPath(path); + if (!Directory.Exists(path)) + throw new Exception(LangRes("E_CreateDirInvalidPath")); + else + { + try + { + path = Path.Combine(path, name); + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_CreateDirFailed")); + } + } + } + + protected void DeleteDir(string path) + { + CheckPath(path); + path = FixPath(path); + if (!Directory.Exists(path)) + throw new Exception(LangRes("E_DeleteDirInvalidPath")); + else if (path == GetFilesRoot()) + throw new Exception(LangRes("E_CannotDeleteRoot")); + else if (Directory.GetDirectories(path).Length > 0 || Directory.GetFiles(path).Length > 0) + throw new Exception(LangRes("E_DeleteNonEmpty")); + else + { + try + { + Directory.Delete(path); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_CannotDeleteDir")); + } + } + } + + protected void DeleteFile(string path) + { + CheckPath(path); + path = FixPath(path); + if (!System.IO.File.Exists(path)) + throw new Exception(LangRes("E_DeleteFileInvalidPath")); + else + { + try + { + System.IO.File.Delete(path); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_DeletеFile")); + } + } + } + + private List GetFiles(string path, string type) + { + List ret = new List(); + if (type == "#") + type = ""; + string[] files = Directory.GetFiles(path); + foreach (string f in files) + { + if ((GetFileType(new FileInfo(f).Extension) == type) || (type == "")) + ret.Add(f); + } + return ret; + } + + private ArrayList ListDirs(string path) + { + string[] dirs = Directory.GetDirectories(path); + ArrayList ret = new ArrayList(); + foreach (string dir in dirs) + { + ret.Add(dir); + ret.AddRange(ListDirs(dir)); + } + return ret; + } + + protected void ListDirTree(string type) + { + DirectoryInfo d = new DirectoryInfo(GetFilesRoot()); + if (!d.Exists) + throw new Exception("Invalid files root directory. Check your configuration."); + + ArrayList dirs = ListDirs(d.FullName); + dirs.Insert(0, d.FullName); + + string localPath = _context.Server.MapPath("~/"); + _response.Write("["); + for (int i = 0; i < dirs.Count; i++) + { + string dir = (string)dirs[i]; + _response.Write("{\"p\":\"/" + dir.Replace(localPath, "").Replace("\\", "/") + "\",\"f\":\"" + GetFiles(dir, type).Count.ToString() + "\",\"d\":\"" + Directory.GetDirectories(dir).Length.ToString() + "\"}"); + if (i < dirs.Count - 1) + _response.Write(","); + } + _response.Write("]"); + } + + protected double LinuxTimestamp(DateTime d) + { + DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0).ToLocalTime(); + TimeSpan timeSpan = (d.ToLocalTime() - epoch); + + return timeSpan.TotalSeconds; + + } + + protected void ListFiles(string path, string type) + { + CheckPath(path); + string fullPath = FixPath(path); + List files = GetFiles(fullPath, type); + _response.Write("["); + for (int i = 0; i < files.Count; i++) + { + FileInfo f = new FileInfo(files[i]); + int w = 0, h = 0; + if (GetFileType(f.Extension) == "image") + { + try + { + FileStream fs = new FileStream(f.FullName, FileMode.Open); + Image img = Image.FromStream(fs); + w = img.Width; + h = img.Height; + fs.Close(); + fs.Dispose(); + img.Dispose(); + } + catch (Exception ex) + { + throw ex; + } + } + _response.Write("{"); + _response.Write("\"p\":\"" + path + "/" + f.Name + "\""); + _response.Write(",\"t\":\"" + Math.Ceiling(LinuxTimestamp(f.LastWriteTime)).ToString() + "\""); + _response.Write(",\"s\":\"" + f.Length.ToString() + "\""); + _response.Write(",\"w\":\"" + w.ToString() + "\""); + _response.Write(",\"h\":\"" + h.ToString() + "\""); + _response.Write("}"); + if (i < files.Count - 1) + _response.Write(","); + } + _response.Write("]"); + } + + public void DownloadDir(string path) + { + path = FixPath(path); + if (!Directory.Exists(path)) + throw new Exception(LangRes("E_CreateArchive")); + string dirName = new FileInfo(path).Name; + string tmpZip = _context.Server.MapPath("../tmp/" + dirName + ".zip"); + if (System.IO.File.Exists(tmpZip)) + System.IO.File.Delete(tmpZip); + ZipFile.CreateFromDirectory(path, tmpZip, CompressionLevel.Fastest, true); + _response.Clear(); + _response.Headers.Add("Content-Disposition", "attachment; filename=\"" + dirName + ".zip\""); + _response.ContentType = "application/force-download"; + _response.TransmitFile(tmpZip); + _response.Flush(); + System.IO.File.Delete(tmpZip); + _response.End(); + } + + protected void DownloadFile(string path) + { + CheckPath(path); + FileInfo file = new FileInfo(FixPath(path)); + if (file.Exists) + { + _response.Clear(); + _response.Headers.Add("Content-Disposition", "attachment; filename=\"" + file.Name + "\""); + _response.ContentType = "application/force-download"; + _response.TransmitFile(file.FullName); + _response.Flush(); + _response.End(); + } + } + + protected void MoveDir(string path, string newPath) + { + CheckPath(path); + CheckPath(newPath); + DirectoryInfo source = new DirectoryInfo(FixPath(path)); + DirectoryInfo dest = new DirectoryInfo(FixPath(Path.Combine(newPath, source.Name))); + if (dest.FullName.IndexOf(source.FullName) == 0) + throw new Exception(LangRes("E_CannotMoveDirToChild")); + else if (!source.Exists) + throw new Exception(LangRes("E_MoveDirInvalisPath")); + else if (dest.Exists) + throw new Exception(LangRes("E_DirAlreadyExists")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_MoveDir") + " \"" + path + "\""); + } + } + } + + protected void MoveFile(string path, string newPath) + { + CheckPath(path); + CheckPath(newPath); + FileInfo source = new FileInfo(FixPath(path)); + FileInfo dest = new FileInfo(FixPath(newPath)); + if (!source.Exists) + throw new Exception(LangRes("E_MoveFileInvalisPath")); + else if (dest.Exists) + throw new Exception(LangRes("E_MoveFileAlreadyExists")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_MoveFile") + " \"" + path + "\""); + } + } + } + + protected void RenameDir(string path, string name) + { + CheckPath(path); + DirectoryInfo source = new DirectoryInfo(FixPath(path)); + DirectoryInfo dest = new DirectoryInfo(Path.Combine(source.Parent.FullName, name)); + if (source.FullName == GetFilesRoot()) + throw new Exception(LangRes("E_CannotRenameRoot")); + else if (!source.Exists) + throw new Exception(LangRes("E_RenameDirInvalidPath")); + else if (dest.Exists) + throw new Exception(LangRes("E_DirAlreadyExists")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_RenameDir") + " \"" + path + "\""); + } + } + } + + protected void RenameFile(string path, string name) + { + CheckPath(path); + FileInfo source = new FileInfo(FixPath(path)); + FileInfo dest = new FileInfo(Path.Combine(source.Directory.FullName, name)); + if (!source.Exists) + throw new Exception(LangRes("E_RenameFileInvalidPath")); + else if (!CanHandleFile(name)) + throw new Exception(LangRes("E_FileExtensionForbidden")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch (Exception ex) + { + throw new Exception(ex.Message + "; " + LangRes("E_RenameFile") + " \"" + path + "\""); + } + } + } + + public bool ThumbnailCallback() + { + return false; + } + + protected void ShowThumbnail(string path, int width, int height) + { + CheckPath(path); + FileStream fs = new FileStream(FixPath(path), FileMode.Open); + Bitmap img = new Bitmap(Bitmap.FromStream(fs)); + fs.Close(); + fs.Dispose(); + int cropWidth = img.Width, cropHeight = img.Height; + int cropX = 0, cropY = 0; + + double imgRatio = (double)img.Width / (double)img.Height; + + if (height == 0) + height = Convert.ToInt32(Math.Floor((double)width / imgRatio)); + + if (width > img.Width) + width = img.Width; + if (height > img.Height) + height = img.Height; + + double cropRatio = (double)width / (double)height; + cropWidth = Convert.ToInt32(Math.Floor((double)img.Height * cropRatio)); + cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); + if (cropWidth > img.Width) + { + cropWidth = img.Width; + cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); + } + if (cropHeight > img.Height) + { + cropHeight = img.Height; + cropWidth = Convert.ToInt32(Math.Floor((double)cropHeight * cropRatio)); + } + if (cropWidth < img.Width) + { + cropX = Convert.ToInt32(Math.Floor((double)(img.Width - cropWidth) / 2)); + } + if (cropHeight < img.Height) + { + cropY = Convert.ToInt32(Math.Floor((double)(img.Height - cropHeight) / 2)); + } + + Rectangle area = new Rectangle(cropX, cropY, cropWidth, cropHeight); + Bitmap cropImg = img.Clone(area, System.Drawing.Imaging.PixelFormat.DontCare); + img.Dispose(); + Image.GetThumbnailImageAbort imgCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback); + + _response.AddHeader("Content-Type", "image/png"); + cropImg.GetThumbnailImage(width, height, imgCallback, IntPtr.Zero).Save(_response.OutputStream, ImageFormat.Png); + _response.OutputStream.Close(); + cropImg.Dispose(); + } + + private ImageFormat GetImageFormat(string filename) + { + ImageFormat ret = ImageFormat.Jpeg; + switch (new FileInfo(filename).Extension.ToLower()) + { + case ".png": + ret = ImageFormat.Png; + break; + case ".gif": + ret = ImageFormat.Gif; + break; + } + return ret; + } + + protected void ImageResize(string path, string dest, int width, int height) + { + FileStream fs = new FileStream(path, FileMode.Open); + Image img = Image.FromStream(fs); + fs.Close(); + fs.Dispose(); + float ratio = (float)img.Width / (float)img.Height; + if ((img.Width <= width && img.Height <= height) || (width == 0 && height == 0)) + return; + + int newWidth = width; + int newHeight = Convert.ToInt16(Math.Floor((float)newWidth / ratio)); + if ((height > 0 && newHeight > height) || (width == 0)) + { + newHeight = height; + newWidth = Convert.ToInt16(Math.Floor((float)newHeight * ratio)); + } + Bitmap newImg = new Bitmap(newWidth, newHeight); + Graphics g = Graphics.FromImage((Image)newImg); + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.DrawImage(img, 0, 0, newWidth, newHeight); + img.Dispose(); + g.Dispose(); + if (dest != "") + { + newImg.Save(dest, GetImageFormat(dest)); + } + newImg.Dispose(); + } + + protected void Upload(string path) + { + CheckPath(path); + path = FixPath(path); + string res = GetSuccessRes(); + try + { + for (int i = 0; i < Request.Files.Count; i++) + { + if (CanHandleFile(Request.Files[i].FileName)) + { + string filename = MakeUniqueFilename(path, Request.Files[i].FileName); + string dest = Path.Combine(path, filename); + Request.Files[i].SaveAs(dest); + if (GetFileType(new FileInfo(filename).Extension) == "image") + { + int w = 0; + int h = 0; + int.TryParse(GetSetting("MAX_IMAGE_WIDTH"), out w); + int.TryParse(GetSetting("MAX_IMAGE_HEIGHT"), out h); + ImageResize(dest, dest, w, h); + } + } + else + res = GetSuccessRes(LangRes("E_UploadNotAll")); + } + } + catch (Exception ex) + { + res = GetErrorRes(ex.Message); + } + _response.Write(""); + } + + public bool IsReusable + { + get + { + return false; + } + } + + #endregion + + public void ProcessRequest() + { + if (!_permissionService.Authorize(StandardPermissionProvider.UploadPictures)) + { + _response.Write(GetErrorRes("You do not have the required permission")); + return; + } + + string action = "DIRLIST"; + try + { + if (_context.Request["a"] != null) + action = (string)_context.Request["a"]; + + //VerifyAction(action); + switch (action.ToUpper()) + { + case "DIRLIST": + ListDirTree(_context.Request["type"]); + break; + case "FILESLIST": + ListFiles(_context.Request["d"], _context.Request["type"]); + break; + case "COPYDIR": + CopyDir(_context.Request["d"], _context.Request["n"]); + break; + case "COPYFILE": + CopyFile(_context.Request["f"], _context.Request["n"]); + break; + case "CREATEDIR": + CreateDir(_context.Request["d"], _context.Request["n"]); + break; + case "DELETEDIR": + DeleteDir(_context.Request["d"]); + break; + case "DELETEFILE": + DeleteFile(_context.Request["f"]); + break; + case "DOWNLOAD": + DownloadFile(_context.Request["f"]); + break; + case "DOWNLOADDIR": + DownloadDir(_context.Request["d"]); + break; + case "MOVEDIR": + MoveDir(_context.Request["d"], _context.Request["n"]); + break; + case "MOVEFILE": + MoveFile(_context.Request["f"], _context.Request["n"]); + break; + case "RENAMEDIR": + RenameDir(_context.Request["d"], _context.Request["n"]); + break; + case "RENAMEFILE": + RenameFile(_context.Request["f"], _context.Request["n"]); + break; + case "GENERATETHUMB": + int w = 140, h = 0; + int.TryParse(_context.Request["width"].Replace("px", ""), out w); + int.TryParse(_context.Request["height"].Replace("px", ""), out h); + ShowThumbnail(_context.Request["f"], w, h); + break; + case "UPLOAD": + Upload(_context.Request["d"]); + break; + default: + _response.Write(GetErrorRes("This action is not implemented.")); + break; + } + } + catch (Exception ex) + { + if (action == "UPLOAD") + { + _response.Write(""); + } + else + { + _response.Write(GetErrorRes(ex.Message)); + } + } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs index 1384933d48..453ae689c8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs @@ -1,58 +1,54 @@ using System; using System.Linq; +using System.Collections.Generic; +using System.Threading; using System.Web.Mvc; -using System.Threading.Tasks; -using System.Reflection; -using SmartStore.Admin.Models.Directory; +using SmartStore.Admin.Extensions; using SmartStore.Admin.Models.Tasks; -using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Tasks; -using SmartStore.Core.Localization; -using SmartStore.Services.Configuration; -using SmartStore.Services.Directory; +using SmartStore.Core.Plugins; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Security; using SmartStore.Services.Tasks; -using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; -using Telerik.Web.Mvc; -using SmartStore.Core.Async; -using Autofac; -using SmartStore.Core.Logging; -using SmartStore.Core.Plugins; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; +using SmartStore.Core; namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public partial class ScheduleTaskController : AdminControllerBase { - #region Fields - private readonly IScheduleTaskService _scheduleTaskService; + private readonly ITaskScheduler _taskScheduler; private readonly IPermissionService _permissionService; private readonly IDateTimeHelper _dateTimeHelper; - - #endregion - - #region Constructors - - public ScheduleTaskController(IScheduleTaskService scheduleTaskService, IPermissionService permissionService, IDateTimeHelper dateTimeHelper) + private readonly ILocalizationService _localizationService; + private readonly IWorkContext _workContext; + + public ScheduleTaskController( + IScheduleTaskService scheduleTaskService, + ITaskScheduler taskScheduler, + IPermissionService permissionService, + IDateTimeHelper dateTimeHelper, + ILocalizationService localizationService, + IWorkContext workContext) { this._scheduleTaskService = scheduleTaskService; + this._taskScheduler = taskScheduler; this._permissionService = permissionService; this._dateTimeHelper = dateTimeHelper; - T = NullLocalizer.Instance; + this._localizationService = localizationService; + this._workContext = workContext; } - #endregion - - public Localizer T { get; set; } - - #region Utility - - private bool IsTaskInstalled(ScheduleTask task) + private bool IsTaskVisible(ScheduleTask task) { + if (task.IsHidden) + return false; + var type = Type.GetType(task.Type); if (type != null) { @@ -61,39 +57,30 @@ private bool IsTaskInstalled(ScheduleTask task) return false; } - [NonAction] - protected ScheduleTaskModel PrepareScheduleTaskModel(ScheduleTask task) - { - var model = new ScheduleTaskModel() - { - Id = task.Id, - Name = task.Name, - Seconds = task.Seconds, - Enabled = task.Enabled, - StopOnError = task.StopOnError, - LastStartUtc = task.LastStartUtc.HasValue ? _dateTimeHelper.ConvertToUserTime(task.LastStartUtc.Value, DateTimeKind.Utc).ToString("G") : "", - LastEndUtc = task.LastEndUtc.HasValue ? _dateTimeHelper.ConvertToUserTime(task.LastEndUtc.Value, DateTimeKind.Utc).ToString("G") : "", - LastSuccessUtc = task.LastSuccessUtc.HasValue ? _dateTimeHelper.ConvertToUserTime(task.LastSuccessUtc.Value, DateTimeKind.Utc).ToString("G") : "", - LastError = task.LastError.EmptyNull(), - IsRunning = task.IsRunning, //task.LastStartUtc.GetValueOrDefault() > task.LastEndUtc.GetValueOrDefault(), - Duration = "" - }; - - var span = TimeSpan.Zero; - if (task.LastStartUtc.HasValue) - { - span = model.IsRunning ? DateTime.UtcNow - task.LastStartUtc.Value : task.LastEndUtc.Value - task.LastStartUtc.Value; - model.Duration = span.ToString("g"); - } + private string GetTaskMessage(ScheduleTask task, string resourceKey) + { + string message = null; - return model; - } + var taskClassName = task.Type + .SplitSafe(",") + .SafeGet(0) + .SplitSafe(".") + .LastOrDefault(); - #endregion + if (taskClassName.HasValue()) + { + message = _localizationService.GetResource(string.Concat(resourceKey, ".", taskClassName), returnEmptyIfNotFound: true); + } - #region Methods + if (message.IsEmpty()) + { + message = T(resourceKey); + } - public ActionResult Index() + return message; + } + + public ActionResult Index() { return RedirectToAction("List"); } @@ -103,115 +90,223 @@ public ActionResult List() if (!_permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks)) return AccessDeniedView(); - return View(); - } + var model = _scheduleTaskService.GetAllTasks(true) + .Where(IsTaskVisible) + .Select(x => x.ToScheduleTaskModel(_localizationService, _dateTimeHelper, Url)) + .ToList(); - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult List(GridCommand command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks)) - return AccessDeniedView(); - - var models = _scheduleTaskService.GetAllTasks(true) - .Where(IsTaskInstalled) - .Select(PrepareScheduleTaskModel) - .ToList(); - var model = new GridModel - { - Data = models, - Total = models.Count - }; - - return new JsonResult - { - Data = model - }; + return View(model); } - [GridAction(EnableCustomBinding = true)] - public ActionResult TaskUpdate(ScheduleTaskModel model, GridCommand command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks)) - return AccessDeniedView(); - - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } - var scheduleTask = _scheduleTaskService.GetTaskById(model.Id); - if (scheduleTask == null) - return Content("Schedule task cannot be loaded"); - - scheduleTask.Name = model.Name; - scheduleTask.Seconds = model.Seconds; - scheduleTask.Enabled = model.Enabled; - scheduleTask.StopOnError = model.StopOnError; + [HttpPost] + public ActionResult GetRunningTasks() + { + if (!_scheduleTaskService.HasRunningTasks()) + return Json(null); + + var runningTasks = from t in _scheduleTaskService.GetRunningTasks() + select new + { + id = t.Id, + percent = t.ProgressPercent, + message = t.ProgressMessage, + }; + + return Json(runningTasks.ToArray()); + } - int max = Int32.MaxValue / 1000; + [HttpPost] + public ActionResult GetTaskRunInfo(int id /* taskId */) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks)) + return new HttpUnauthorizedResult(); - scheduleTask.Seconds = (model.Seconds > max ? max : model.Seconds); + var task = _scheduleTaskService.GetTaskById(id); + if (task == null) + { + return HttpNotFound(); + } - _scheduleTaskService.UpdateTask(scheduleTask); + var model = task.ToScheduleTaskModel(_localizationService, _dateTimeHelper, Url); - return List(command); - } + return Json(new + { + lastRunHtml = this.RenderPartialViewToString("_LastRun", model), + nextRunHtml = this.RenderPartialViewToString("_NextRun", model) + }); + } public ActionResult RunJob(int id, string returnUrl = "") { if (!_permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks)) return AccessDeniedView(); - returnUrl = returnUrl.NullEmpty() ?? Request.UrlReferrer.ToString(); + var taskParams = new Dictionary(); + taskParams[TaskExecutor.CurrentCustomerIdParamName] = _workContext.CurrentCustomer.Id.ToString(); - var t = AsyncRunner.Run(c => - { - try - { - var svc = c.Resolve(); + _taskScheduler.RunSingleTask(id, taskParams); - var scheduleTask = svc.GetTaskById(id); - if (scheduleTask == null) - throw new Exception("Schedule task cannot be loaded"); + // The most tasks are completed rather quickly. Wait a while... + var start = DateTime.UtcNow; + Thread.Sleep(200); - var job = new Job(scheduleTask); - job.Enabled = true; - job.Execute(c, false); + // ...check and return suitable notifications + var task = _scheduleTaskService.GetTaskById(id); + if (task != null) + { + if (task.IsRunning) + { + NotifyInfo(GetTaskMessage(task, "Admin.System.ScheduleTasks.RunNow.Progress")); } - catch + else { - try + if (task.LastError.HasValue()) + { + NotifyError(task.LastError); + } + else { - _scheduleTaskService.EnsureTaskIsNotRunning(id); + NotifySuccess(GetTaskMessage(task, "Admin.System.ScheduleTasks.RunNow.Success")); } - catch (Exception) { } } - }); + } + + return RedirectToReferrer(returnUrl); + } - // wait only 100 ms. - t.Wait(100); + public ActionResult CancelJob(int id /* scheduleTaskId */, string returnUrl = "") + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks)) + return AccessDeniedView(); - if (t.IsCompleted) + var cts = _taskScheduler.GetCancelTokenSourceFor(id); + if (cts != null) { - if (!t.IsFaulted) - { - NotifySuccess(T("Admin.System.ScheduleTasks.RunNow.Completed")); - } - else - { - NotifyError(t.Exception.Flatten().InnerException); - } + cts.Cancel(); + NotifyWarning(T("Admin.System.ScheduleTasks.CancellationRequested")); + } + + return RedirectToReferrer(returnUrl); + } + + public ActionResult Edit(int id /* taskId */, string returnUrl = null) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks)) + return AccessDeniedView(); + + var task = _scheduleTaskService.GetTaskById(id); + if (task == null) + { + return HttpNotFound(); + } + + var model = task.ToScheduleTaskModel(_localizationService, _dateTimeHelper, Url); + ViewBag.ReturnUrl = returnUrl; + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + [ValidateAntiForgeryToken] + public ActionResult Edit(ScheduleTaskModel model, bool continueEditing, string returnUrl = null) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks)) + return AccessDeniedView(); + + ViewBag.ReturnUrl = returnUrl; + + if (!ModelState.IsValid) + { + return View(model); + } + + var reloadResult = RedirectToAction("Edit", new { id = model.Id, returnUrl = returnUrl }); + var returnResult = RedirectToReferrer(returnUrl, () => RedirectToAction("List")); + + var scheduleTask = _scheduleTaskService.GetTaskById(model.Id); + if (scheduleTask == null) + { + NotifyError("Schedule task cannot be loaded"); + return reloadResult; + } + + scheduleTask.Name = model.Name; + scheduleTask.Enabled = model.Enabled; + scheduleTask.StopOnError = model.StopOnError; + scheduleTask.CronExpression = model.CronExpression; + + if (model.Enabled) + { + scheduleTask.NextRunUtc = _scheduleTaskService.GetNextSchedule(scheduleTask); } else { - NotifyInfo(T("Admin.System.ScheduleTasks.RunNow.Progress")); + scheduleTask.NextRunUtc = null; + } + + _scheduleTaskService.UpdateTask(scheduleTask); + + NotifySuccess(T("Admin.System.ScheduleTasks.UpdateSuccess")); + + if (continueEditing) + return reloadResult; + + return returnResult; + } + + [HttpPost] + public ActionResult FutureSchedules(string expression) + { + try + { + var now = DateTime.Now; + var model = CronExpression.GetFutureSchedules(expression, now, now.AddYears(1), 20); + ViewBag.Description = CronExpression.GetFriendlyDescription(expression); + return PartialView(model); + } + catch (Exception ex) + { + ViewBag.CronScheduleParseError = ex.Message; + return PartialView(Enumerable.Empty()); + } + } + + [ChildActionOnly] + public ActionResult MinimalTask(int taskId, string returnUrl /* mandatory on purpose */, bool cancellable = true, bool reloadPage = false) + { + var task = _scheduleTaskService.GetTaskById(taskId); + if (task == null) + { + return Content(""); + } + + ViewBag.HasPermission = _permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks); + ViewBag.ReturnUrl = returnUrl; + ViewBag.Cancellable = cancellable; + ViewBag.ReloadPage = reloadPage; + + var model = task.ToScheduleTaskModel(_localizationService, _dateTimeHelper, Url); + + return PartialView(model); + } + + [HttpPost] + public ActionResult GetMinimalTaskWidget(int taskId, string returnUrl /* mandatory on purpose */) + { + var task = _scheduleTaskService.GetTaskById(taskId); + if (task == null) + { + return Content(""); } - return Redirect(returnUrl); + ViewBag.HasPermission = _permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks); + ViewBag.ReturnUrl = returnUrl; + + var model = task.ToScheduleTaskModel(_localizationService, _dateTimeHelper, Url); + + return PartialView("_MinimalTaskWidget", model); } - #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SecurityController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SecurityController.cs index da30ac662e..45b5aa901a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SecurityController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SecurityController.cs @@ -1,15 +1,14 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Customers; using SmartStore.Admin.Models.Security; using SmartStore.Core; -using SmartStore.Services.Customers; -using SmartStore.Services.Localization; using SmartStore.Core.Logging; +using SmartStore.Services.Customers; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; namespace SmartStore.Admin.Controllers { @@ -21,7 +20,6 @@ public class SecurityController : AdminControllerBase private readonly IWorkContext _workContext; private readonly IPermissionService _permissionService; private readonly ICustomerService _customerService; - private readonly ILocalizationService _localizationService; #endregion @@ -29,12 +27,11 @@ public class SecurityController : AdminControllerBase public SecurityController(IWorkContext workContext, IPermissionService permissionService, - ICustomerService customerService, ILocalizationService localizationService) + ICustomerService customerService) { this._workContext = workContext; this._permissionService = permissionService; this._customerService = customerService; - this._localizationService = localizationService; } #endregion  @@ -44,14 +41,15 @@ public SecurityController(IWorkContext workContext, public ActionResult AccessDenied(string pageUrl) { var currentCustomer = _workContext.CurrentCustomer; + if (currentCustomer == null || currentCustomer.IsGuest()) { - Logger.Information(string.Format("Access denied to anonymous request on {0}", pageUrl)); + Logger.Information(T("Admin.System.Warnings.AccessDeniedToAnonymousRequest", pageUrl.NaIfEmpty())); return View(); } - Logger.Information(string.Format("Access denied to user #{0} '{1}' on {2}", currentCustomer.Email, currentCustomer.Email, pageUrl)); - + Logger.Information(T("Admin.System.Warnings.AccessDeniedToUser", + currentCustomer.Email.NaIfEmpty(), currentCustomer.Email.NaIfEmpty(), pageUrl.NaIfEmpty())); return View(); } @@ -65,30 +63,38 @@ public ActionResult Permissions() var permissionRecords = _permissionService.GetAllPermissionRecords(); var customerRoles = _customerService.GetAllCustomerRoles(true); + foreach (var pr in permissionRecords) { - model.AvailablePermissions.Add(new PermissionRecordModel() + model.AvailablePermissions.Add(new PermissionRecordModel { Name = pr.Name, - SystemName = pr.SystemName + SystemName = pr.SystemName, + Category = pr.Category }); } + foreach (var cr in customerRoles) { - model.AvailableCustomerRoles.Add(new CustomerRoleModel() + model.AvailableCustomerRoles.Add(new CustomerRoleModel { Id = cr.Id, Name = cr.Name }); } - foreach (var pr in permissionRecords) - foreach (var cr in customerRoles) - { - bool allowed = pr.CustomerRoles.Where(x => x.Id == cr.Id).ToList().Count() > 0; - if (!model.Allowed.ContainsKey(pr.SystemName)) - model.Allowed[pr.SystemName] = new Dictionary(); - model.Allowed[pr.SystemName][cr.Id] = allowed; - } + + foreach (var pr in permissionRecords) + { + foreach (var cr in customerRoles) + { + var allowed = pr.CustomerRoles.Any(x => x.Id == cr.Id); + + if (!model.Allowed.ContainsKey(pr.SystemName)) + model.Allowed[pr.SystemName] = new Dictionary(); + + model.Allowed[pr.SystemName][cr.Id] = allowed; + } + } return View(model); } @@ -102,38 +108,38 @@ public ActionResult PermissionsSave(FormCollection form) var permissionRecords = _permissionService.GetAllPermissionRecords(); var customerRoles = _customerService.GetAllCustomerRoles(true); - foreach (var cr in customerRoles) { - string formKey = "allow_" + cr.Id; - var permissionRecordSystemNamesToRestrict = form[formKey] != null ? form[formKey].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() : new List(); + var restrictedSystemNames = form["allow_" + cr.Id.ToString()].SplitSafe(",").ToList(); - foreach (var pr in permissionRecords) + foreach (var permission in permissionRecords) { + bool allow = restrictedSystemNames.Contains(permission.SystemName); - bool allow = permissionRecordSystemNamesToRestrict.Contains(pr.SystemName); if (allow) { - if (pr.CustomerRoles.Where(x => x.Id == cr.Id).FirstOrDefault() == null) + if (!permission.CustomerRoles.Any(x => x.Id == cr.Id)) { - pr.CustomerRoles.Add(cr); - _permissionService.UpdatePermissionRecord(pr); + permission.CustomerRoles.Add(cr); + _permissionService.UpdatePermissionRecord(permission); } } else { - if (pr.CustomerRoles.Where(x => x.Id == cr.Id).FirstOrDefault() != null) + if (permission.CustomerRoles.Any(x => x.Id == cr.Id)) { - pr.CustomerRoles.Remove(cr); - _permissionService.UpdatePermissionRecord(pr); + permission.CustomerRoles.Remove(cr); + _permissionService.UpdatePermissionRecord(permission); } } } } - NotifySuccess(_localizationService.GetResource("Admin.Configuration.ACL.Updated")); + NotifySuccess(T("Admin.Configuration.ACL.Updated")); + return RedirectToAction("Permissions"); } + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 11e012f77a..d26e835c28 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -4,12 +4,12 @@ using System.Web.Mvc; using SmartStore.Admin.Models.Common; using SmartStore.Admin.Models.Settings; -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.DataExchange; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Localization; @@ -22,8 +22,8 @@ using SmartStore.Core.Domain.Tax; using SmartStore.Core.Logging; using SmartStore.Core.Themes; +using SmartStore.Services; using SmartStore.Services.Common; -using SmartStore.Services.Configuration; using SmartStore.Services.Customers; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; @@ -31,11 +31,13 @@ using SmartStore.Services.Media; using SmartStore.Services.Orders; using SmartStore.Services.Security; -using SmartStore.Services.Stores; using SmartStore.Services.Tax; +using SmartStore.Utilities; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Localization; +using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; using SmartStore.Web.Framework.UI.Captcha; using Telerik.Web.Mvc; @@ -47,30 +49,24 @@ public partial class SettingController : AdminControllerBase { #region Fields - private readonly ISettingService _settingService; - private readonly ICountryService _countryService; - private readonly IStateProvinceService _stateProvinceService; - private readonly IAddressService _addressService; - private readonly ITaxCategoryService _taxCategoryService; - private readonly ICurrencyService _currencyService; - private readonly IPictureService _pictureService; - private readonly ILocalizationService _localizationService; - private readonly IDateTimeHelper _dateTimeHelper; - private readonly IOrderService _orderService; - private readonly IEncryptionService _encryptionService; - private readonly IThemeRegistry _themeRegistry; - private readonly ICustomerService _customerService; - private readonly ICustomerActivityService _customerActivityService; - private readonly IPermissionService _permissionService; - private readonly IWebHelper _webHelper; - private readonly IFulltextService _fulltextService; - private readonly IMaintenanceService _maintenanceService; - private readonly IStoreService _storeService; - private readonly IWorkContext _workContext; + private readonly ICountryService _countryService; + private readonly IStateProvinceService _stateProvinceService; + private readonly IAddressService _addressService; + private readonly ITaxCategoryService _taxCategoryService; + private readonly IPictureService _pictureService; + private readonly IDateTimeHelper _dateTimeHelper; + private readonly IOrderService _orderService; + private readonly IEncryptionService _encryptionService; + private readonly IThemeRegistry _themeRegistry; + private readonly ICustomerService _customerService; + private readonly ICustomerActivityService _customerActivityService; + private readonly IFulltextService _fulltextService; + private readonly IMaintenanceService _maintenanceService; private readonly IGenericAttributeService _genericAttributeService; private readonly ILocalizedEntityService _localizedEntityService; private readonly ILanguageService _languageService; private readonly IDeliveryTimeService _deliveryTimesService; + private readonly ICommonServices _services; private StoreDependingSettingHelper _storeDependingSettings; @@ -78,48 +74,49 @@ public partial class SettingController : AdminControllerBase #region Constructors - public SettingController(ISettingService settingService, - ICountryService countryService, IStateProvinceService stateProvinceService, - IAddressService addressService, ITaxCategoryService taxCategoryService, - ICurrencyService currencyService, IPictureService pictureService, - ILocalizationService localizationService, IDateTimeHelper dateTimeHelper, - IOrderService orderService, IEncryptionService encryptionService, - IThemeRegistry themeRegistry, ICustomerService customerService, - ICustomerActivityService customerActivityService, IPermissionService permissionService, - IWebHelper webHelper, IFulltextService fulltextService, - IMaintenanceService maintenanceService, IStoreService storeService, - IWorkContext workContext, IGenericAttributeService genericAttributeService, + public SettingController( + ICountryService countryService, + IStateProvinceService stateProvinceService, + IAddressService addressService, + ITaxCategoryService taxCategoryService, + IPictureService pictureService, + IDateTimeHelper dateTimeHelper, + IOrderService orderService, + IEncryptionService encryptionService, + IThemeRegistry themeRegistry, + ICustomerService customerService, + ICustomerActivityService customerActivityService, + IFulltextService fulltextService, + IMaintenanceService maintenanceService, + IGenericAttributeService genericAttributeService, ILocalizedEntityService localizedEntityService, ILanguageService languageService, - IDeliveryTimeService deliveryTimesService) + IDeliveryTimeService deliveryTimesService, + ICommonServices services) { - this._settingService = settingService; this._countryService = countryService; this._stateProvinceService = stateProvinceService; this._addressService = addressService; this._taxCategoryService = taxCategoryService; - this._currencyService = currencyService; this._pictureService = pictureService; - this._localizationService = localizationService; this._dateTimeHelper = dateTimeHelper; this._orderService = orderService; this._encryptionService = encryptionService; this._themeRegistry = themeRegistry; this._customerService = customerService; this._customerActivityService = customerActivityService; - this._permissionService = permissionService; - this._webHelper = webHelper; this._fulltextService = fulltextService; this._maintenanceService = maintenanceService; - this._storeService = storeService; - this._workContext = workContext; this._genericAttributeService = genericAttributeService; this._localizedEntityService = localizedEntityService; this._languageService = languageService; this._deliveryTimesService = deliveryTimesService; + this._services = services; } - #endregion  + #endregion + + #region Utilities private StoreDependingSettingHelper StoreDependingSettings { @@ -131,23 +128,25 @@ private StoreDependingSettingHelper StoreDependingSettings } } - #region Methods + #endregion + + #region Methods [ChildActionOnly] public ActionResult StoreScopeConfiguration() { - var allStores = _storeService.GetAllStores(); + var allStores = _services.StoreService.GetAllStores(); if (allStores.Count < 2) return Content(""); - var model = new StoreScopeConfigurationModel() + var model = new StoreScopeConfigurationModel { - StoreId = this.GetActiveStoreScopeConfiguration(_storeService, _workContext) + StoreId = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext) }; foreach (var store in allStores) { - model.AllStores.Add(new SelectListItem() + model.AllStores.Add(new SelectListItem { Text = store.Name, Selected = (store.Id == model.StoreId), @@ -155,9 +154,9 @@ public ActionResult StoreScopeConfiguration() }); } - model.AllStores.Insert(0, new SelectListItem() + model.AllStores.Insert(0, new SelectListItem { - Text = _localizationService.GetResource("Admin.Common.StoresAll"), + Text = _services.Localization.GetResource("Admin.Common.StoresAll"), Selected = (0 == model.StoreId), Value = Url.Action("ChangeStoreScopeConfiguration", "Setting", new { storeid = 0, returnUrl = Request.RawUrl }) }); @@ -167,58 +166,50 @@ public ActionResult StoreScopeConfiguration() public ActionResult ChangeStoreScopeConfiguration(int storeid, string returnUrl = "") { - var store = _storeService.GetStoreById(storeid); + var store = _services.StoreService.GetStoreById(storeid); if (store != null || storeid == 0) { - _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, + _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, SystemCustomerAttributeNames.AdminAreaStoreScopeConfiguration, storeid); } - - //url referrer - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = _webHelper.GetUrlReferrer(); - - //home page - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = Url.Action("Index", "Home", new { area = "Admin" }); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl, () => RedirectToAction("Index", "Home", new { area = "Admin" })); } public ActionResult Blog() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var blogSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var blogSettings = _services.Settings.LoadSetting(storeScope); var model = blogSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(blogSettings, model, storeScope, _settingService); + StoreDependingSettings.GetOverrideKeys(blogSettings, model, storeScope, _services.Settings); return View(model); } [HttpPost] public ActionResult Blog(BlogSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var blogSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var blogSettings = _services.Settings.LoadSetting(storeScope); blogSettings = model.ToEntity(blogSettings); - StoreDependingSettings.UpdateSettings(blogSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(blogSettings, form, storeScope, _services.Settings); //now clear settings cache - _settingService.ClearCache(); + _services.Settings.ClearCache(); //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); return RedirectToAction("Blog"); } @@ -227,15 +218,15 @@ public ActionResult Blog(BlogSettingsModel model, FormCollection form) public ActionResult Forum() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var forumSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var forumSettings = _services.Settings.LoadSetting(storeScope); var model = forumSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(forumSettings, model, storeScope, _settingService); + StoreDependingSettings.GetOverrideKeys(forumSettings, model, storeScope, _services.Settings); model.ForumEditorValues = forumSettings.ForumEditor.ToSelectList(); @@ -244,23 +235,23 @@ public ActionResult Forum() [HttpPost] public ActionResult Forum(ForumSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var forumSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var forumSettings = _services.Settings.LoadSetting(storeScope); forumSettings = model.ToEntity(forumSettings); - StoreDependingSettings.UpdateSettings(forumSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(forumSettings, form, storeScope, _services.Settings); //now clear settings cache - _settingService.ClearCache(); + _services.Settings.ClearCache(); //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); return RedirectToAction("Forum"); } @@ -269,38 +260,38 @@ public ActionResult Forum(ForumSettingsModel model, FormCollection form) public ActionResult News() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var newsSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var newsSettings = _services.Settings.LoadSetting(storeScope); var model = newsSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(newsSettings, model, storeScope, _settingService); + StoreDependingSettings.GetOverrideKeys(newsSettings, model, storeScope, _services.Settings); return View(model); } [HttpPost] public ActionResult News(NewsSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var newsSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var newsSettings = _services.Settings.LoadSetting(storeScope); newsSettings = model.ToEntity(newsSettings); - StoreDependingSettings.UpdateSettings(newsSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(newsSettings, form, storeScope, _services.Settings); //now clear settings cache - _settingService.ClearCache(); + _services.Settings.ClearCache(); //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); return RedirectToAction("News"); } @@ -309,18 +300,18 @@ public ActionResult News(NewsSettingsModel model, FormCollection form) public ActionResult Shipping() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var shippingSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var shippingSettings = _services.Settings.LoadSetting(storeScope); var model = shippingSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(shippingSettings, model, storeScope, _settingService); + StoreDependingSettings.GetOverrideKeys(shippingSettings, model, storeScope, _services.Settings); //shipping origin - if (storeScope > 0 && _settingService.SettingExists(shippingSettings, x => x.ShippingOriginAddressId, storeScope)) + if (storeScope > 0 && _services.Settings.SettingExists(shippingSettings, x => x.ShippingOriginAddressId, storeScope)) StoreDependingSettings.AddOverrideKey(shippingSettings, "ShippingOriginAddress"); var originAddress = shippingSettings.ShippingOriginAddressId > 0 @@ -351,7 +342,7 @@ public ActionResult Shipping() else { model.ShippingOriginAddress.AvailableStates.Add( - new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" } + new SelectListItem() { Text = _services.Localization.GetResource("Admin.Address.OtherNonUS"), Value = "0" } ); } @@ -365,22 +356,22 @@ public ActionResult Shipping() [HttpPost] public ActionResult Shipping(ShippingSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var shippingSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var shippingSettings = _services.Settings.LoadSetting(storeScope); shippingSettings = model.ToEntity(shippingSettings); - StoreDependingSettings.UpdateSettings(shippingSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(shippingSettings, form, storeScope, _services.Settings); bool shippingOriginAddressOverride = StoreDependingSettings.IsOverrideChecked(shippingSettings, "ShippingOriginAddress", form); if (shippingOriginAddressOverride || storeScope == 0) { //update address - var addressId = _settingService.SettingExists(shippingSettings, x => x.ShippingOriginAddressId, storeScope) ? shippingSettings.ShippingOriginAddressId : 0; + var addressId = _services.Settings.SettingExists(shippingSettings, x => x.ShippingOriginAddressId, storeScope) ? shippingSettings.ShippingOriginAddressId : 0; var originAddress = _addressService.GetAddressById(addressId) ?? new Core.Domain.Common.Address() { CreatedOnUtc = DateTime.UtcNow }; @@ -393,22 +384,22 @@ public ActionResult Shipping(ShippingSettingsModel model, FormCollection form) _addressService.InsertAddress(originAddress); shippingSettings.ShippingOriginAddressId = originAddress.Id; - _settingService.SaveSetting(shippingSettings, x => x.ShippingOriginAddressId, storeScope, false); + _services.Settings.SaveSetting(shippingSettings, x => x.ShippingOriginAddressId, storeScope, false); } else { _addressService.DeleteAddress(shippingSettings.ShippingOriginAddressId); - _settingService.DeleteSetting(shippingSettings, x => x.ShippingOriginAddressId, storeScope); + _services.Settings.DeleteSetting(shippingSettings, x => x.ShippingOriginAddressId, storeScope); } //now clear settings cache - _settingService.ClearCache(); + _services.Settings.ClearCache(); //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); return RedirectToAction("Shipping"); } @@ -417,15 +408,15 @@ public ActionResult Shipping(ShippingSettingsModel model, FormCollection form) public ActionResult Tax() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var taxSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var taxSettings = _services.Settings.LoadSetting(storeScope); var model = taxSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(taxSettings, model, storeScope, _settingService); + StoreDependingSettings.GetOverrideKeys(taxSettings, model, storeScope, _services.Settings); model.TaxBasedOnValues = taxSettings.TaxBasedOn.ToSelectList(); model.TaxDisplayTypeValues = taxSettings.TaxDisplayType.ToSelectList(); @@ -464,7 +455,7 @@ public ActionResult Tax() else model.DefaultTaxAddress = new AddressModel(); - if (storeScope > 0 && _settingService.SettingExists(taxSettings, x => x.DefaultTaxAddressId, storeScope)) + if (storeScope > 0 && _services.Settings.SettingExists(taxSettings, x => x.DefaultTaxAddressId, storeScope)) StoreDependingSettings.AddOverrideKey(taxSettings, "DefaultTaxAddress"); foreach (var c in _countryService.GetAllCountries(true)) @@ -488,7 +479,7 @@ public ActionResult Tax() else { model.DefaultTaxAddress.AvailableStates.Add( - new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" } + new SelectListItem() { Text = _services.Localization.GetResource("Admin.Address.OtherNonUS"), Value = "0" } ); } model.DefaultTaxAddress.CountryEnabled = true; @@ -501,25 +492,25 @@ public ActionResult Tax() [HttpPost] public ActionResult Tax(TaxSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var taxSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var taxSettings = _services.Settings.LoadSetting(storeScope); taxSettings = model.ToEntity(taxSettings); - StoreDependingSettings.UpdateSettings(taxSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(taxSettings, form, storeScope, _services.Settings); bool defaultTaxAddressOverride = StoreDependingSettings.IsOverrideChecked(taxSettings, "DefaultTaxAddress", form); taxSettings.AllowCustomersToSelectTaxDisplayType = false; - _settingService.UpdateSetting(taxSettings, x => x.AllowCustomersToSelectTaxDisplayType, false, storeScope); + _services.Settings.UpdateSetting(taxSettings, x => x.AllowCustomersToSelectTaxDisplayType, false, storeScope); if (defaultTaxAddressOverride || storeScope == 0) { //update address - var addressId = _settingService.SettingExists(taxSettings, x => x.DefaultTaxAddressId, storeScope) ? taxSettings.DefaultTaxAddressId : 0; + var addressId = _services.Settings.SettingExists(taxSettings, x => x.DefaultTaxAddressId, storeScope) ? taxSettings.DefaultTaxAddressId : 0; var originAddress = _addressService.GetAddressById(addressId) ?? new Core.Domain.Common.Address() { CreatedOnUtc = DateTime.UtcNow }; @@ -532,22 +523,22 @@ public ActionResult Tax(TaxSettingsModel model, FormCollection form) _addressService.InsertAddress(originAddress); taxSettings.DefaultTaxAddressId = originAddress.Id; - _settingService.SaveSetting(taxSettings, x => x.DefaultTaxAddressId, storeScope, false); + _services.Settings.SaveSetting(taxSettings, x => x.DefaultTaxAddressId, storeScope, false); } else if (storeScope > 0) { _addressService.DeleteAddress(taxSettings.DefaultTaxAddressId); - _settingService.DeleteSetting(taxSettings, x => x.DefaultTaxAddressId, storeScope); + _services.Settings.DeleteSetting(taxSettings, x => x.DefaultTaxAddressId, storeScope); } //now clear settings cache - _settingService.ClearCache(); + _services.Settings.ClearCache(); //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); return RedirectToAction("Tax"); } @@ -556,23 +547,29 @@ public ActionResult Tax(TaxSettingsModel model, FormCollection form) public ActionResult Catalog() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var catalogSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var catalogSettings = _services.Settings.LoadSetting(storeScope); var model = catalogSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(catalogSettings, model, storeScope, _settingService); + StoreDependingSettings.GetOverrideKeys(catalogSettings, model, storeScope, _services.Settings); + + model.AvailableSubCategoryDisplayTypes = catalogSettings.SubCategoryDisplayType.ToSelectList(); + model.AvailablePriceDisplayTypes = catalogSettings.PriceDisplayType.ToSelectList(); model.AvailableDefaultViewModes.Add( - new SelectListItem { Value = "grid", Text = _localizationService.GetResource("Common.Grid"), Selected = model.DefaultViewMode.IsCaseInsensitiveEqual("grid") } + new SelectListItem { Value = "grid", Text = _services.Localization.GetResource("Common.Grid"), Selected = model.DefaultViewMode.IsCaseInsensitiveEqual("grid") } ); model.AvailableDefaultViewModes.Add( - new SelectListItem { Value = "list", Text = _localizationService.GetResource("Common.List"), Selected = model.DefaultViewMode.IsCaseInsensitiveEqual("list") } + new SelectListItem { Value = "list", Text = _services.Localization.GetResource("Common.List"), Selected = model.DefaultViewMode.IsCaseInsensitiveEqual("list") } ); + //default sort order modes + model.AvailableSortOrderModes = catalogSettings.DefaultSortOrder.ToSelectList(); + var deliveryTimes = _deliveryTimesService.GetAllDeliveryTimes(); foreach (var dt in deliveryTimes) { @@ -589,23 +586,23 @@ public ActionResult Catalog() [HttpPost] public ActionResult Catalog(CatalogSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var catalogSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var catalogSettings = _services.Settings.LoadSetting(storeScope); catalogSettings = model.ToEntity(catalogSettings); - StoreDependingSettings.UpdateSettings(catalogSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(catalogSettings, form, storeScope, _services.Settings); //now clear settings cache - _settingService.ClearCache(); + _services.Settings.ClearCache(); //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); return RedirectToAction("Catalog"); } @@ -613,62 +610,60 @@ public ActionResult Catalog(CatalogSettingsModel model, FormCollection form) public ActionResult RewardPoints() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var rewardPointsSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var rewardPointsSettings = _services.Settings.LoadSetting(storeScope); + var store = (storeScope == 0 ? _services.StoreContext.CurrentStore : _services.StoreService.GetStoreById(storeScope)); + var model = rewardPointsSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(rewardPointsSettings, model, storeScope, _settingService); + StoreDependingSettings.GetOverrideKeys(rewardPointsSettings, model, storeScope, _services.Settings); - if (storeScope > 0 && (_settingService.SettingExists(rewardPointsSettings, x => x.PointsForPurchases_Amount, storeScope) || - _settingService.SettingExists(rewardPointsSettings, x => x.PointsForPurchases_Points, storeScope))) + if (storeScope > 0 && (_services.Settings.SettingExists(rewardPointsSettings, x => x.PointsForPurchases_Amount, storeScope) || + _services.Settings.SettingExists(rewardPointsSettings, x => x.PointsForPurchases_Points, storeScope))) { StoreDependingSettings.AddOverrideKey(rewardPointsSettings, "PointsForPurchases_OverrideForStore"); } - var currencySettings = _settingService.LoadSetting(storeScope); - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = store.PrimaryStoreCurrency.CurrencyCode; return View(model); } + [HttpPost] public ActionResult RewardPoints(RewardPointsSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); - if (ModelState.IsValid) - { - //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var rewardPointsSettings = _settingService.LoadSetting(storeScope); - rewardPointsSettings = model.ToEntity(rewardPointsSettings); + if (!ModelState.IsValid) + return RewardPoints(); - StoreDependingSettings.UpdateSettings(rewardPointsSettings, form, storeScope, _settingService); + ModelState.Clear(); - bool pointsForPurchases = StoreDependingSettings.IsOverrideChecked(rewardPointsSettings, "PointsForPurchases", form); + //load settings for a chosen store scope + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var rewardPointsSettings = _services.Settings.LoadSetting(storeScope); + rewardPointsSettings = model.ToEntity(rewardPointsSettings); - _settingService.UpdateSetting(rewardPointsSettings, x => x.PointsForPurchases_Amount, pointsForPurchases, storeScope); - _settingService.UpdateSetting(rewardPointsSettings, x => x.PointsForPurchases_Points, pointsForPurchases, storeScope); + StoreDependingSettings.UpdateSettings(rewardPointsSettings, form, storeScope, _services.Settings); - //now clear settings cache - _settingService.ClearCache(); + bool pointsForPurchases = StoreDependingSettings.IsOverrideChecked(rewardPointsSettings, "PointsForPurchases", form); - //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _services.Settings.UpdateSetting(rewardPointsSettings, x => x.PointsForPurchases_Amount, pointsForPurchases, storeScope); + _services.Settings.UpdateSetting(rewardPointsSettings, x => x.PointsForPurchases_Points, pointsForPurchases, storeScope); + + //now clear settings cache + _services.Settings.ClearCache(); + + //activity log + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); + + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); - } - else - { - //If we got this far, something failed, redisplay form - foreach (var modelState in ModelState.Values) - foreach (var error in modelState.Errors) - NotifyError(error.ErrorMessage); - } return RedirectToAction("RewardPoints"); } @@ -677,18 +672,22 @@ public ActionResult RewardPoints(RewardPointsSettingsModel model, FormCollection public ActionResult Order() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var orderSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var orderSettings = _services.Settings.LoadSetting(storeScope); + + var allStores = _services.StoreService.GetAllStores(); + var store = (storeScope == 0 ? _services.StoreContext.CurrentStore : allStores.FirstOrDefault(x => x.Id == storeScope)); + var model = orderSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(orderSettings, model, storeScope, _settingService); + StoreDependingSettings.GetOverrideKeys(orderSettings, model, storeScope, _services.Settings); - var currencySettings = _settingService.LoadSetting(storeScope); - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.PrimaryStoreCurrencyCode = store.PrimaryStoreCurrency.CurrencyCode; + model.StoreCount = allStores.Count; //gift card activation/deactivation model.GiftCards_Activated_OrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); @@ -708,67 +707,61 @@ public ActionResult Order() [HttpPost] public ActionResult Order(OrderSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); - if (ModelState.IsValid) - { - //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var orderSettings = _settingService.LoadSetting(storeScope); - orderSettings = model.ToEntity(orderSettings); + if (!ModelState.IsValid) + return Order(); - StoreDependingSettings.UpdateSettings(orderSettings, form, storeScope, _settingService); + ModelState.Clear(); - _settingService.SaveSetting(orderSettings, x => x.ReturnRequestActions, 0, false); - _settingService.SaveSetting(orderSettings, x => x.ReturnRequestReasons, 0, false); + //load settings for a chosen store scope + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var orderSettings = _services.Settings.LoadSetting(storeScope); + orderSettings = model.ToEntity(orderSettings); - foreach (var localized in model.Locales) - { - _localizedEntityService.SaveLocalizedValue(orderSettings, x => x.ReturnRequestActions, localized.ReturnRequestActions, localized.LanguageId); - _localizedEntityService.SaveLocalizedValue(orderSettings, x => x.ReturnRequestReasons, localized.ReturnRequestReasons, localized.LanguageId); - } + StoreDependingSettings.UpdateSettings(orderSettings, form, storeScope, _services.Settings); - if (model.GiftCards_Activated_OrderStatusId.HasValue) - _settingService.SaveSetting(orderSettings, x => x.GiftCards_Activated_OrderStatusId, 0, false); - else - _settingService.DeleteSetting(orderSettings, x => x.GiftCards_Activated_OrderStatusId); + _services.Settings.SaveSetting(orderSettings, x => x.ReturnRequestActions, 0, false); + _services.Settings.SaveSetting(orderSettings, x => x.ReturnRequestReasons, 0, false); - if (model.GiftCards_Deactivated_OrderStatusId.HasValue) - _settingService.SaveSetting(orderSettings, x => x.GiftCards_Deactivated_OrderStatusId, 0, false); - else - _settingService.DeleteSetting(orderSettings, x => x.GiftCards_Deactivated_OrderStatusId); + foreach (var localized in model.Locales) + { + _localizedEntityService.SaveLocalizedValue(orderSettings, x => x.ReturnRequestActions, localized.ReturnRequestActions, localized.LanguageId); + _localizedEntityService.SaveLocalizedValue(orderSettings, x => x.ReturnRequestReasons, localized.ReturnRequestReasons, localized.LanguageId); + } - //now clear settings cache - _settingService.ClearCache(); + if (model.GiftCards_Activated_OrderStatusId.HasValue) + _services.Settings.SaveSetting(orderSettings, x => x.GiftCards_Activated_OrderStatusId, 0, false); + else + _services.Settings.DeleteSetting(orderSettings, x => x.GiftCards_Activated_OrderStatusId); - //order ident - if (model.OrderIdent.HasValue) - { - try - { - _maintenanceService.SetTableIdent(model.OrderIdent.Value); - } - catch (Exception exc) - { - NotifyError(exc.Message); - } - } + if (model.GiftCards_Deactivated_OrderStatusId.HasValue) + _services.Settings.SaveSetting(orderSettings, x => x.GiftCards_Deactivated_OrderStatusId, 0, false); + else + _services.Settings.DeleteSetting(orderSettings, x => x.GiftCards_Deactivated_OrderStatusId); - //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + //now clear settings cache + _services.Settings.ClearCache(); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); - } - else + //order ident + if (model.OrderIdent.HasValue) { - //If we got this far, something failed, redisplay form - foreach (var modelState in ModelState.Values) - { - foreach (var error in modelState.Errors) - NotifyError(error.ErrorMessage); - } + try + { + _maintenanceService.SetTableIdent(model.OrderIdent.Value); + } + catch (Exception exc) + { + NotifyError(exc.Message); + } } + + //activity log + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); + + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); + return RedirectToAction("Order"); } @@ -777,39 +770,56 @@ public ActionResult Order(OrderSettingsModel model, FormCollection form) public ActionResult ShoppingCart() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); - //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var shoppingCartSettings = _settingService.LoadSetting(storeScope); + var allLanguages = _languageService.GetAllLanguages(true); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var shoppingCartSettings = _services.Settings.LoadSetting(storeScope); + var model = shoppingCartSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(shoppingCartSettings, model, storeScope, _settingService); + model.AvailableNewsLetterSubscriptions = shoppingCartSettings.NewsLetterSubscription.ToSelectList(); + model.AvailableThirdPartyEmailHandOver = shoppingCartSettings.ThirdPartyEmailHandOver.ToSelectList(); + StoreDependingSettings.GetOverrideKeys(shoppingCartSettings, model, storeScope, _services.Settings); + + AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.ThirdPartyEmailHandOverLabel = shoppingCartSettings.GetLocalized(x => x.ThirdPartyEmailHandOverLabel, languageId, false, false); + }); return View(model); } [HttpPost] public ActionResult ShoppingCart(ShoppingCartSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); - //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var shoppingCartSettings = _settingService.LoadSetting(storeScope); + if (!ModelState.IsValid) + return ShoppingCart(); + + ModelState.Clear(); + + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var shoppingCartSettings = _services.Settings.LoadSetting(storeScope); shoppingCartSettings = model.ToEntity(shoppingCartSettings); - StoreDependingSettings.UpdateSettings(shoppingCartSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(shoppingCartSettings, form, storeScope, _services.Settings); - //now clear settings cache - _settingService.ClearCache(); + _services.Settings.SaveSetting(shoppingCartSettings, x => x.ThirdPartyEmailHandOverLabel, 0, false); + + foreach (var localized in model.Locales) + { + _localizedEntityService.SaveLocalizedValue(shoppingCartSettings, x => x.ThirdPartyEmailHandOverLabel, localized.ThirdPartyEmailHandOverLabel, localized.LanguageId); + } + + _services.Settings.ClearCache(); - //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", T("ActivityLog.EditSettings")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); + NotifySuccess(T("Admin.Configuration.Updated")); return RedirectToAction("ShoppingCart"); } @@ -818,32 +828,32 @@ public ActionResult ShoppingCart(ShoppingCartSettingsModel model, FormCollection public ActionResult Media() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var mediaSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var mediaSettings = _services.Settings.LoadSetting(storeScope); var model = mediaSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(mediaSettings, model, storeScope, _settingService); + StoreDependingSettings.GetOverrideKeys(mediaSettings, model, storeScope, _services.Settings); model.PicturesStoredIntoDatabase = _pictureService.StoreInDb; var resKey = "Admin.Configuration.Settings.Media.PictureZoomType."; model.AvailablePictureZoomTypes.Add(new SelectListItem { - Text = _localizationService.GetResource(resKey + "Window"), + Text = _services.Localization.GetResource(resKey + "Window"), Value = "window", Selected = model.PictureZoomType.Equals("window") }); model.AvailablePictureZoomTypes.Add(new SelectListItem { - Text = _localizationService.GetResource(resKey + "Inner"), + Text = _services.Localization.GetResource(resKey + "Inner"), Value = "inner", Selected = model.PictureZoomType.Equals("inner") }); model.AvailablePictureZoomTypes.Add(new SelectListItem { - Text = _localizationService.GetResource(resKey + "Lens"), + Text = _services.Localization.GetResource(resKey + "Lens"), Value = "lens", Selected = model.PictureZoomType.Equals("lens") }); @@ -854,38 +864,38 @@ public ActionResult Media() [FormValueRequired("save")] public ActionResult Media(MediaSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var mediaSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var mediaSettings = _services.Settings.LoadSetting(storeScope); mediaSettings = model.ToEntity(mediaSettings); - StoreDependingSettings.UpdateSettings(mediaSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(mediaSettings, form, storeScope, _services.Settings); //now clear settings cache - _settingService.ClearCache(); + _services.Settings.ClearCache(); //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); return RedirectToAction("Media"); } [HttpPost, ActionName("Media")] [FormValueRequired("change-picture-storage")] public ActionResult ChangePictureStorage() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); _pictureService.StoreInDb = !_pictureService.StoreInDb; //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); return RedirectToAction("Media"); } @@ -893,85 +903,97 @@ public ActionResult ChangePictureStorage() public ActionResult CustomerUser() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); StoreDependingSettings.CreateViewDataObject(storeScope); - var customerSettings = _settingService.LoadSetting(storeScope); - var addressSettings = _settingService.LoadSetting(storeScope); - var dateTimeSettings = _settingService.LoadSetting(storeScope); - var externalAuthenticationSettings = _settingService.LoadSetting(storeScope); + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - //merge settings - var model = new CustomerUserSettingsModel(); + var customerSettings = _services.Settings.LoadSetting(storeScope); + var addressSettings = _services.Settings.LoadSetting(storeScope); + var dateTimeSettings = _services.Settings.LoadSetting(storeScope); + var externalAuthenticationSettings = _services.Settings.LoadSetting(storeScope); + + //merge settings + var model = new CustomerUserSettingsModel(); model.CustomerSettings = customerSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(customerSettings, model.CustomerSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(customerSettings, model.CustomerSettings, storeScope, _services.Settings, false); model.AddressSettings = addressSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(addressSettings, model.AddressSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(addressSettings, model.AddressSettings, storeScope, _services.Settings, false); model.DateTimeSettings.AllowCustomersToSetTimeZone = dateTimeSettings.AllowCustomersToSetTimeZone; model.DateTimeSettings.DefaultStoreTimeZoneId = _dateTimeHelper.DefaultStoreTimeZone.Id; + foreach (TimeZoneInfo timeZone in _dateTimeHelper.GetSystemTimeZones()) { - model.DateTimeSettings.AvailableTimeZones.Add(new SelectListItem() - { - Text = timeZone.DisplayName, - Value = timeZone.Id, - Selected = timeZone.Id.Equals(_dateTimeHelper.DefaultStoreTimeZone.Id, StringComparison.InvariantCultureIgnoreCase) - }); + model.DateTimeSettings.AvailableTimeZones.Add(new SelectListItem + { + Text = timeZone.DisplayName, + Value = timeZone.Id, + Selected = timeZone.Id.Equals(_dateTimeHelper.DefaultStoreTimeZone.Id, StringComparison.InvariantCultureIgnoreCase) + }); } - StoreDependingSettings.GetOverrideKeys(dateTimeSettings, model.DateTimeSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(dateTimeSettings, model.DateTimeSettings, storeScope, _services.Settings, false); model.ExternalAuthenticationSettings.AutoRegisterEnabled = externalAuthenticationSettings.AutoRegisterEnabled; - StoreDependingSettings.GetOverrideKeys(externalAuthenticationSettings, model.ExternalAuthenticationSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(externalAuthenticationSettings, model.ExternalAuthenticationSettings, storeScope, _services.Settings, false); - return View(model); + model.CustomerSettings.AvailableCustomerNumberMethods = customerSettings.CustomerNumberMethod.ToSelectList(false); + model.CustomerSettings.AvailableCustomerNumberVisibilities = customerSettings.CustomerNumberVisibility.ToSelectList(false); + + model.CustomerSettings.AvailableRegisterCustomerRoles = allCustomerRoles + .Where(x => x.SystemName != SystemCustomerRoleNames.Registered && x.SystemName != SystemCustomerRoleNames.Guests) + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + return View(model); } + [HttpPost] public ActionResult CustomerUser(CustomerUserSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); - var customerSettings = _settingService.LoadSetting(storeScope); + var customerSettings = _services.Settings.LoadSetting(storeScope); customerSettings = model.CustomerSettings.ToEntity(customerSettings); - StoreDependingSettings.UpdateSettings(customerSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(customerSettings, form, storeScope, _services.Settings); - _settingService.SaveSetting(customerSettings, x => x.DefaultPasswordFormat, 0, false); + _services.Settings.SaveSetting(customerSettings, x => x.DefaultPasswordFormat, 0, false); - var addressSettings = _settingService.LoadSetting(storeScope); + var addressSettings = _services.Settings.LoadSetting(storeScope); addressSettings = model.AddressSettings.ToEntity(addressSettings); - StoreDependingSettings.UpdateSettings(addressSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(addressSettings, form, storeScope, _services.Settings); - var dateTimeSettings = _settingService.LoadSetting(storeScope); + var dateTimeSettings = _services.Settings.LoadSetting(storeScope); dateTimeSettings.DefaultStoreTimeZoneId = model.DateTimeSettings.DefaultStoreTimeZoneId; dateTimeSettings.AllowCustomersToSetTimeZone = model.DateTimeSettings.AllowCustomersToSetTimeZone; - StoreDependingSettings.UpdateSettings(dateTimeSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(dateTimeSettings, form, storeScope, _services.Settings); - var externalAuthenticationSettings = _settingService.LoadSetting(storeScope); + var externalAuthenticationSettings = _services.Settings.LoadSetting(storeScope); externalAuthenticationSettings.AutoRegisterEnabled = model.ExternalAuthenticationSettings.AutoRegisterEnabled; - StoreDependingSettings.UpdateSettings(externalAuthenticationSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(externalAuthenticationSettings, form, storeScope, _services.Settings); //now clear settings cache - _settingService.ClearCache(); + _services.Settings.ClearCache(); //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); return RedirectToAction("CustomerUser"); } @@ -982,39 +1004,43 @@ public ActionResult CustomerUser(CustomerUserSettingsModel model, FormCollection public ActionResult GeneralCommon() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //set page timeout to 5 minutes this.Server.ScriptTimeout = 300; var model = new GeneralCommonSettingsModel(); - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); StoreDependingSettings.CreateViewDataObject(storeScope); //store information - var storeInformationSettings = _settingService.LoadSetting(storeScope); + var storeInformationSettings = _services.Settings.LoadSetting(storeScope); model.StoreInformationSettings.StoreClosed = storeInformationSettings.StoreClosed; model.StoreInformationSettings.StoreClosedAllowForAdmins = storeInformationSettings.StoreClosedAllowForAdmins; - StoreDependingSettings.GetOverrideKeys(storeInformationSettings, model.StoreInformationSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(storeInformationSettings, model.StoreInformationSettings, storeScope, _services.Settings, false); //seo settings - var seoSettings = _settingService.LoadSetting(storeScope); + var seoSettings = _services.Settings.LoadSetting(storeScope); model.SeoSettings.PageTitleSeoAdjustment = seoSettings.PageTitleSeoAdjustment; model.SeoSettings.PageTitleSeparator = seoSettings.PageTitleSeparator; model.SeoSettings.DefaultTitle = seoSettings.DefaultTitle; model.SeoSettings.DefaultMetaKeywords = seoSettings.DefaultMetaKeywords; model.SeoSettings.DefaultMetaDescription = seoSettings.DefaultMetaDescription; + model.SeoSettings.MetaRobotsContent = seoSettings.MetaRobotsContent; model.SeoSettings.ConvertNonWesternChars = seoSettings.ConvertNonWesternChars; + model.SeoSettings.AllowUnicodeCharsInUrls = seoSettings.AllowUnicodeCharsInUrls; + model.SeoSettings.SeoNameCharConversion = seoSettings.SeoNameCharConversion; model.SeoSettings.CanonicalUrlsEnabled = seoSettings.CanonicalUrlsEnabled; model.SeoSettings.CanonicalHostNameRule = seoSettings.CanonicalHostNameRule; + model.SeoSettings.ExtraRobotsDisallows = String.Join(Environment.NewLine, seoSettings.ExtraRobotsDisallows); - StoreDependingSettings.GetOverrideKeys(seoSettings, model.SeoSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(seoSettings, model.SeoSettings, storeScope, _services.Settings, false); //security settings - var securitySettings = _settingService.LoadSetting(storeScope); + var securitySettings = _services.Settings.LoadSetting(storeScope); model.SecuritySettings.EncryptionKey = securitySettings.EncryptionKey; if (securitySettings.AdminAreaAllowedIpAddresses != null) { @@ -1027,7 +1053,7 @@ public ActionResult GeneralCommon() } model.SecuritySettings.HideAdminMenuItemsBasedOnPermissions = securitySettings.HideAdminMenuItemsBasedOnPermissions; - var captchaSettings = _settingService.LoadSetting(storeScope); + var captchaSettings = _services.Settings.LoadSetting(storeScope); model.CaptchaSettings.Enabled = captchaSettings.Enabled; model.CaptchaSettings.ShowOnLoginPage = captchaSettings.ShowOnLoginPage; model.CaptchaSettings.ShowOnRegistrationPage = captchaSettings.ShowOnRegistrationPage; @@ -1041,18 +1067,20 @@ public ActionResult GeneralCommon() model.CaptchaSettings.ReCaptchaPublicKey = captchaSettings.ReCaptchaPublicKey; model.CaptchaSettings.ReCaptchaPrivateKey = captchaSettings.ReCaptchaPrivateKey; - StoreDependingSettings.GetOverrideKeys(captchaSettings, model.CaptchaSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(captchaSettings, model.CaptchaSettings, storeScope, _services.Settings, false); //PDF settings - var pdfSettings = _settingService.LoadSetting(storeScope); + var pdfSettings = _services.Settings.LoadSetting(storeScope); model.PdfSettings.Enabled = pdfSettings.Enabled; model.PdfSettings.LetterPageSizeEnabled = pdfSettings.LetterPageSizeEnabled; model.PdfSettings.LogoPictureId = pdfSettings.LogoPictureId; + model.PdfSettings.AttachOrderPdfToOrderPlacedEmail = pdfSettings.AttachOrderPdfToOrderPlacedEmail; + model.PdfSettings.AttachOrderPdfToOrderCompletedEmail = pdfSettings.AttachOrderPdfToOrderCompletedEmail; - StoreDependingSettings.GetOverrideKeys(pdfSettings, model.PdfSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(pdfSettings, model.PdfSettings, storeScope, _services.Settings, false); //localization - var localizationSettings = _settingService.LoadSetting(storeScope); + var localizationSettings = _services.Settings.LoadSetting(storeScope); model.LocalizationSettings.UseImagesForLanguageSelection = localizationSettings.UseImagesForLanguageSelection; model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled = localizationSettings.SeoFriendlyUrlsForLanguagesEnabled; model.LocalizationSettings.LoadAllLocaleRecordsOnStartup = localizationSettings.LoadAllLocaleRecordsOnStartup; @@ -1060,17 +1088,17 @@ public ActionResult GeneralCommon() model.LocalizationSettings.InvalidLanguageRedirectBehaviour = localizationSettings.InvalidLanguageRedirectBehaviour; model.LocalizationSettings.DetectBrowserUserLanguage = localizationSettings.DetectBrowserUserLanguage; - StoreDependingSettings.GetOverrideKeys(localizationSettings, model.LocalizationSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(localizationSettings, model.LocalizationSettings, storeScope, _services.Settings, false); //full-text support - var commonSettings = _settingService.LoadSetting(storeScope); + var commonSettings = _services.Settings.LoadSetting(storeScope); model.FullTextSettings.Supported = _fulltextService.IsFullTextSupported(); model.FullTextSettings.Enabled = commonSettings.UseFullTextSearch; model.FullTextSettings.SearchMode = commonSettings.FullTextMode; model.FullTextSettings.SearchModeValues = commonSettings.FullTextMode.ToSelectList(); //company information - var companySettings = _settingService.LoadSetting(storeScope); + var companySettings = _services.Settings.LoadSetting(storeScope); model.CompanyInformationSettings.CompanyName = companySettings.CompanyName; model.CompanyInformationSettings.Salutation = companySettings.Salutation; model.CompanyInformationSettings.Title = companySettings.Title; @@ -1088,7 +1116,7 @@ public ActionResult GeneralCommon() model.CompanyInformationSettings.CommercialRegister = companySettings.CommercialRegister; model.CompanyInformationSettings.TaxNumber = companySettings.TaxNumber; - StoreDependingSettings.GetOverrideKeys(companySettings, model.CompanyInformationSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(companySettings, model.CompanyInformationSettings, storeScope, _services.Settings, false); foreach (var c in _countryService.GetAllCountries(true)) { @@ -1116,7 +1144,7 @@ public ActionResult GeneralCommon() ResToSelectListItem("Admin.Configuration.Settings.GeneralCommon.CompanyInformationSettings.ManagementDescriptions.ManagingPartner")); //contact data - var contactDataSettings = _settingService.LoadSetting(storeScope); + var contactDataSettings = _services.Settings.LoadSetting(storeScope); model.ContactDataSettings.CompanyTelephoneNumber = contactDataSettings.CompanyTelephoneNumber; model.ContactDataSettings.HotlineTelephoneNumber = contactDataSettings.HotlineTelephoneNumber; model.ContactDataSettings.MobileTelephoneNumber = contactDataSettings.MobileTelephoneNumber; @@ -1126,10 +1154,10 @@ public ActionResult GeneralCommon() model.ContactDataSettings.SupportEmailAddress = contactDataSettings.SupportEmailAddress; model.ContactDataSettings.ContactEmailAddress = contactDataSettings.ContactEmailAddress; - StoreDependingSettings.GetOverrideKeys(contactDataSettings, model.ContactDataSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(contactDataSettings, model.ContactDataSettings, storeScope, _services.Settings, false); //bank connection - var bankConnectionSettings = _settingService.LoadSetting(storeScope); + var bankConnectionSettings = _services.Settings.LoadSetting(storeScope); model.BankConnectionSettings.Bankname = bankConnectionSettings.Bankname; model.BankConnectionSettings.Bankcode = bankConnectionSettings.Bankcode; model.BankConnectionSettings.AccountNumber = bankConnectionSettings.AccountNumber; @@ -1137,10 +1165,10 @@ public ActionResult GeneralCommon() model.BankConnectionSettings.Iban = bankConnectionSettings.Iban; model.BankConnectionSettings.Bic = bankConnectionSettings.Bic; - StoreDependingSettings.GetOverrideKeys(bankConnectionSettings, model.BankConnectionSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(bankConnectionSettings, model.BankConnectionSettings, storeScope, _services.Settings, false); //social - var socialSettings = _settingService.LoadSetting(storeScope); + var socialSettings = _services.Settings.LoadSetting(storeScope); model.SocialSettings.ShowSocialLinksInFooter = socialSettings.ShowSocialLinksInFooter; model.SocialSettings.FacebookLink = socialSettings.FacebookLink; model.SocialSettings.GooglePlusLink = socialSettings.GooglePlusLink; @@ -1148,14 +1176,14 @@ public ActionResult GeneralCommon() model.SocialSettings.PinterestLink = socialSettings.PinterestLink; model.SocialSettings.YoutubeLink = socialSettings.YoutubeLink; - StoreDependingSettings.GetOverrideKeys(socialSettings, model.SocialSettings, storeScope, _settingService, false); + StoreDependingSettings.GetOverrideKeys(socialSettings, model.SocialSettings, storeScope, _services.Settings, false); return View(model); } private SelectListItem ResToSelectListItem(string resourceKey) { - string value = _localizationService.GetResource(resourceKey).EmptyNull(); + string value = _services.Localization.GetResource(resourceKey).EmptyNull(); return new SelectListItem() { Text = value, Value = value }; } @@ -1163,34 +1191,45 @@ private SelectListItem ResToSelectListItem(string resourceKey) [FormValueRequired("save")] public ActionResult GeneralCommon(GeneralCommonSettingsModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); //store information - var storeInformationSettings = _settingService.LoadSetting(storeScope); + var storeInformationSettings = _services.Settings.LoadSetting(storeScope); storeInformationSettings.StoreClosed = model.StoreInformationSettings.StoreClosed; storeInformationSettings.StoreClosedAllowForAdmins = model.StoreInformationSettings.StoreClosedAllowForAdmins; - StoreDependingSettings.UpdateSettings(storeInformationSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(storeInformationSettings, form, storeScope, _services.Settings); //seo settings - var seoSettings = _settingService.LoadSetting(storeScope); + var seoSettings = _services.Settings.LoadSetting(storeScope); + var resetUserSeoCharacterTable = (seoSettings.SeoNameCharConversion != model.SeoSettings.SeoNameCharConversion); + seoSettings.PageTitleSeparator = model.SeoSettings.PageTitleSeparator; seoSettings.PageTitleSeoAdjustment = model.SeoSettings.PageTitleSeoAdjustment; seoSettings.DefaultTitle = model.SeoSettings.DefaultTitle; seoSettings.DefaultMetaKeywords = model.SeoSettings.DefaultMetaKeywords; seoSettings.DefaultMetaDescription = model.SeoSettings.DefaultMetaDescription; + seoSettings.MetaRobotsContent = model.SeoSettings.MetaRobotsContent; + seoSettings.AllowUnicodeCharsInUrls = model.SeoSettings.AllowUnicodeCharsInUrls; + seoSettings.SeoNameCharConversion = model.SeoSettings.SeoNameCharConversion; seoSettings.ConvertNonWesternChars = model.SeoSettings.ConvertNonWesternChars; seoSettings.CanonicalUrlsEnabled = model.SeoSettings.CanonicalUrlsEnabled; seoSettings.CanonicalHostNameRule = model.SeoSettings.CanonicalHostNameRule; + seoSettings.ExtraRobotsDisallows = new List(model.SeoSettings.ExtraRobotsDisallows.EmptyNull().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)); - StoreDependingSettings.UpdateSettings(seoSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(seoSettings, form, storeScope, _services.Settings); + + if (resetUserSeoCharacterTable) + { + SeoHelper.ResetUserSeoCharacterTable(); + } //security settings - var securitySettings = _settingService.LoadSetting(storeScope); + var securitySettings = _services.Settings.LoadSetting(storeScope); if (securitySettings.AdminAreaAllowedIpAddresses == null) securitySettings.AdminAreaAllowedIpAddresses = new List(); securitySettings.AdminAreaAllowedIpAddresses.Clear(); @@ -1203,9 +1242,9 @@ public ActionResult GeneralCommon(GeneralCommonSettingsModel model, FormCollecti } } securitySettings.HideAdminMenuItemsBasedOnPermissions = model.SecuritySettings.HideAdminMenuItemsBasedOnPermissions; - _settingService.SaveSetting(securitySettings); + _services.Settings.SaveSetting(securitySettings); - var captchaSettings = _settingService.LoadSetting(storeScope); + var captchaSettings = _services.Settings.LoadSetting(storeScope); captchaSettings.Enabled = model.CaptchaSettings.Enabled; captchaSettings.ShowOnLoginPage = model.CaptchaSettings.ShowOnLoginPage; captchaSettings.ShowOnRegistrationPage = model.CaptchaSettings.ShowOnRegistrationPage; @@ -1219,51 +1258,54 @@ public ActionResult GeneralCommon(GeneralCommonSettingsModel model, FormCollecti captchaSettings.ReCaptchaPublicKey = model.CaptchaSettings.ReCaptchaPublicKey; captchaSettings.ReCaptchaPrivateKey = model.CaptchaSettings.ReCaptchaPrivateKey; - StoreDependingSettings.UpdateSettings(captchaSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(captchaSettings, form, storeScope, _services.Settings); if (captchaSettings.Enabled && (String.IsNullOrWhiteSpace(captchaSettings.ReCaptchaPublicKey) || String.IsNullOrWhiteSpace(captchaSettings.ReCaptchaPrivateKey))) { - NotifyError(_localizationService.GetResource("Admin.Configuration.Settings.GeneralCommon.CaptchaEnabledNoKeys")); + NotifyError(_services.Localization.GetResource("Admin.Configuration.Settings.GeneralCommon.CaptchaEnabledNoKeys")); } - //PDF settings - var pdfSettings = _settingService.LoadSetting(storeScope); + // PDF settings + var pdfSettings = _services.Settings.LoadSetting(storeScope); pdfSettings.Enabled = model.PdfSettings.Enabled; pdfSettings.LetterPageSizeEnabled = model.PdfSettings.LetterPageSizeEnabled; + MediaHelper.UpdatePictureTransientState(pdfSettings.LogoPictureId, model.PdfSettings.LogoPictureId, true); pdfSettings.LogoPictureId = model.PdfSettings.LogoPictureId; + pdfSettings.AttachOrderPdfToOrderPlacedEmail = model.PdfSettings.AttachOrderPdfToOrderPlacedEmail; + pdfSettings.AttachOrderPdfToOrderCompletedEmail = model.PdfSettings.AttachOrderPdfToOrderCompletedEmail; - StoreDependingSettings.UpdateSettings(pdfSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(pdfSettings, form, storeScope, _services.Settings); //localization settings - var localizationSettings = _settingService.LoadSetting(storeScope); + var localizationSettings = _services.Settings.LoadSetting(storeScope); localizationSettings.LoadAllLocaleRecordsOnStartup = model.LocalizationSettings.LoadAllLocaleRecordsOnStartup; localizationSettings.DefaultLanguageRedirectBehaviour = model.LocalizationSettings.DefaultLanguageRedirectBehaviour; localizationSettings.InvalidLanguageRedirectBehaviour = model.LocalizationSettings.InvalidLanguageRedirectBehaviour; localizationSettings.UseImagesForLanguageSelection = model.LocalizationSettings.UseImagesForLanguageSelection; localizationSettings.DetectBrowserUserLanguage = model.LocalizationSettings.DetectBrowserUserLanguage; - StoreDependingSettings.UpdateSettings(localizationSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(localizationSettings, form, storeScope, _services.Settings); - _settingService.SaveSetting(localizationSettings, x => x.LoadAllLocaleRecordsOnStartup, 0, false); - _settingService.SaveSetting(localizationSettings, x => x.DefaultLanguageRedirectBehaviour, 0, false); - _settingService.SaveSetting(localizationSettings, x => x.InvalidLanguageRedirectBehaviour, 0, false); + _services.Settings.SaveSetting(localizationSettings, x => x.LoadAllLocaleRecordsOnStartup, 0, false); + _services.Settings.SaveSetting(localizationSettings, x => x.DefaultLanguageRedirectBehaviour, 0, false); + _services.Settings.SaveSetting(localizationSettings, x => x.InvalidLanguageRedirectBehaviour, 0, false); if (localizationSettings.SeoFriendlyUrlsForLanguagesEnabled != model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled) { localizationSettings.SeoFriendlyUrlsForLanguagesEnabled = model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled; - _settingService.SaveSetting(localizationSettings, x => x.SeoFriendlyUrlsForLanguagesEnabled, 0, false); + _services.Settings.SaveSetting(localizationSettings, x => x.SeoFriendlyUrlsForLanguagesEnabled, 0, false); System.Web.Routing.RouteTable.Routes.ClearSeoFriendlyUrlsCachedValueForRoutes(); // clear cached values of routes } //full-text - var commonSettings = _settingService.LoadSetting(storeScope); + var commonSettings = _services.Settings.LoadSetting(storeScope); commonSettings.FullTextMode = model.FullTextSettings.SearchMode; - _settingService.SaveSetting(commonSettings); + _services.Settings.SaveSetting(commonSettings); //company information - var companySettings = _settingService.LoadSetting(storeScope); + var companySettings = _services.Settings.LoadSetting(storeScope); companySettings.CompanyName = model.CompanyInformationSettings.CompanyName; companySettings.Salutation = model.CompanyInformationSettings.Salutation; companySettings.Title = model.CompanyInformationSettings.Title; @@ -1285,10 +1327,10 @@ public ActionResult GeneralCommon(GeneralCommonSettingsModel model, FormCollecti companySettings.CommercialRegister = model.CompanyInformationSettings.CommercialRegister; companySettings.TaxNumber = model.CompanyInformationSettings.TaxNumber; - StoreDependingSettings.UpdateSettings(companySettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(companySettings, form, storeScope, _services.Settings); //contact data - var contactDataSettings = _settingService.LoadSetting(storeScope); + var contactDataSettings = _services.Settings.LoadSetting(storeScope); contactDataSettings.CompanyTelephoneNumber = model.ContactDataSettings.CompanyTelephoneNumber; contactDataSettings.HotlineTelephoneNumber = model.ContactDataSettings.HotlineTelephoneNumber; contactDataSettings.MobileTelephoneNumber = model.ContactDataSettings.MobileTelephoneNumber; @@ -1298,10 +1340,10 @@ public ActionResult GeneralCommon(GeneralCommonSettingsModel model, FormCollecti contactDataSettings.SupportEmailAddress = model.ContactDataSettings.SupportEmailAddress; contactDataSettings.ContactEmailAddress = model.ContactDataSettings.ContactEmailAddress; - StoreDependingSettings.UpdateSettings(contactDataSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(contactDataSettings, form, storeScope, _services.Settings); //bank connection - var bankConnectionSettings = _settingService.LoadSetting(storeScope); + var bankConnectionSettings = _services.Settings.LoadSetting(storeScope); bankConnectionSettings.Bankname = model.BankConnectionSettings.Bankname; bankConnectionSettings.Bankcode = model.BankConnectionSettings.Bankcode; bankConnectionSettings.AccountNumber = model.BankConnectionSettings.AccountNumber; @@ -1309,10 +1351,10 @@ public ActionResult GeneralCommon(GeneralCommonSettingsModel model, FormCollecti bankConnectionSettings.Iban = model.BankConnectionSettings.Iban; bankConnectionSettings.Bic = model.BankConnectionSettings.Bic; - StoreDependingSettings.UpdateSettings(bankConnectionSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(bankConnectionSettings, form, storeScope, _services.Settings); //social - var socialSettings = _settingService.LoadSetting(storeScope); + var socialSettings = _services.Settings.LoadSetting(storeScope); socialSettings.ShowSocialLinksInFooter = model.SocialSettings.ShowSocialLinksInFooter; socialSettings.FacebookLink = model.SocialSettings.FacebookLink; socialSettings.GooglePlusLink = model.SocialSettings.GooglePlusLink; @@ -1320,29 +1362,29 @@ public ActionResult GeneralCommon(GeneralCommonSettingsModel model, FormCollecti socialSettings.PinterestLink = model.SocialSettings.PinterestLink; socialSettings.YoutubeLink = model.SocialSettings.YoutubeLink; - StoreDependingSettings.UpdateSettings(socialSettings, form, storeScope, _settingService); + StoreDependingSettings.UpdateSettings(socialSettings, form, storeScope, _services.Settings); //now clear settings cache - _settingService.ClearCache(); + _services.Settings.ClearCache(); //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Updated")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); return RedirectToAction("GeneralCommon"); } [HttpPost, ActionName("GeneralCommon")] [FormValueRequired("changeencryptionkey")] public ActionResult ChangeEnryptionKey(GeneralCommonSettingsModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); //set page timeout to 5 minutes this.Server.ScriptTimeout = 300; - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var securitySettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var securitySettings = _services.Settings.LoadSetting(storeScope); try { @@ -1353,11 +1395,11 @@ public ActionResult ChangeEnryptionKey(GeneralCommonSettingsModel model) var newEncryptionPrivateKey = model.SecuritySettings.EncryptionKey; if (String.IsNullOrEmpty(newEncryptionPrivateKey) || newEncryptionPrivateKey.Length != 16) - throw new SmartException(_localizationService.GetResource("Admin.Configuration.Settings.GeneralCommon.EncryptionKey.TooShort")); + throw new SmartException(_services.Localization.GetResource("Admin.Configuration.Settings.GeneralCommon.EncryptionKey.TooShort")); string oldEncryptionPrivateKey = securitySettings.EncryptionKey; if (oldEncryptionPrivateKey == newEncryptionPrivateKey) - throw new SmartException(_localizationService.GetResource("Admin.Configuration.Settings.GeneralCommon.EncryptionKey.TheSame")); + throw new SmartException(_services.Localization.GetResource("Admin.Configuration.Settings.GeneralCommon.EncryptionKey.TheSame")); //update encrypted order info var orders = _orderService.LoadAllOrders(); @@ -1429,8 +1471,8 @@ public ActionResult ChangeEnryptionKey(GeneralCommonSettingsModel model) } securitySettings.EncryptionKey = newEncryptionPrivateKey; - _settingService.SaveSetting(securitySettings); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Settings.GeneralCommon.EncryptionKey.Changed")); + _services.Settings.SaveSetting(securitySettings); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Settings.GeneralCommon.EncryptionKey.Changed")); } catch (Exception exc) { @@ -1442,34 +1484,34 @@ public ActionResult ChangeEnryptionKey(GeneralCommonSettingsModel model) [FormValueRequired("togglefulltext")] public ActionResult ToggleFullText(GeneralCommonSettingsModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var commonSettings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var commonSettings = _services.Settings.LoadSetting(storeScope); try { if (! _fulltextService.IsFullTextSupported()) - throw new SmartException(_localizationService.GetResource("Admin.Configuration.Settings.GeneralCommon.FullTextSettings.NotSupported")); + throw new SmartException(_services.Localization.GetResource("Admin.Configuration.Settings.GeneralCommon.FullTextSettings.NotSupported")); if (commonSettings.UseFullTextSearch) { _fulltextService.DisableFullText(); commonSettings.UseFullTextSearch = false; - _settingService.SaveSetting(commonSettings, storeScope); + _services.Settings.SaveSetting(commonSettings, storeScope); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Settings.GeneralCommon.FullTextSettings.Disabled")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Settings.GeneralCommon.FullTextSettings.Disabled")); } else { _fulltextService.EnableFullText(); commonSettings.UseFullTextSearch = true; - _settingService.SaveSetting(commonSettings, storeScope); + _services.Settings.SaveSetting(commonSettings, storeScope); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Settings.GeneralCommon.FullTextSettings.Enabled")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Settings.GeneralCommon.FullTextSettings.Enabled")); } } catch (Exception exc) @@ -1479,13 +1521,76 @@ public ActionResult ToggleFullText(GeneralCommonSettingsModel model) return RedirectToAction("GeneralCommon"); } + [HttpPost] + public ActionResult TestSeoNameCreation(GeneralCommonSettingsModel model) + { + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var seoSettings = _services.Settings.LoadSetting(storeScope); + + // we always test against persisted settings + + var result = SeoHelper.GetSeName(model.SeoSettings.TestSeoNameCreation, + seoSettings.ConvertNonWesternChars, + seoSettings.AllowUnicodeCharsInUrls, + seoSettings.SeoNameCharConversion); + return Content(result); + } + + public ActionResult DataExchange() + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + return AccessDeniedView(); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var settings = _services.Settings.LoadSetting(storeScope); + + var model = new DataExchangeSettingsModel + { + MaxFileNameLength = settings.MaxFileNameLength, + ImageImportFolder = settings.ImageImportFolder, + ImageDownloadTimeout = settings.ImageDownloadTimeout + }; + + StoreDependingSettings.GetOverrideKeys(settings, model, storeScope, _services.Settings); + + return View(model); + } + + [HttpPost] + public ActionResult DataExchange(DataExchangeSettingsModel model, FormCollection form) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + return AccessDeniedView(); - //all settings - public ActionResult AllSettings() + if (!ModelState.IsValid) + return DataExchange(); + + ModelState.Clear(); + + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var settings = _services.Settings.LoadSetting(storeScope); + + settings.MaxFileNameLength = model.MaxFileNameLength; + settings.ImageImportFolder = model.ImageImportFolder; + settings.ImageDownloadTimeout = model.ImageDownloadTimeout; + + StoreDependingSettings.UpdateSettings(settings, form, storeScope, _services.Settings); + + _services.Settings.ClearCache(); + + _customerActivityService.InsertActivity("EditSettings", T("ActivityLog.EditSettings")); + + NotifySuccess(T("Admin.Configuration.Updated")); + + return RedirectToAction("DataExchange"); + } + + + //all settings + public ActionResult AllSettings() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); return View(); @@ -1494,44 +1599,49 @@ public ActionResult AllSettings() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult AllSettings(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) - return AccessDeniedView(); + var model = new GridModel(); - var stores = _storeService.GetAllStores(); - string allStoresString = _localizationService.GetResource("Admin.Common.StoresAll"); - - var settings = _settingService - .GetAllSettings() - .Select(x => - { - var settingModel = new SettingModel() - { - Id = x.Id, - Name = x.Name, - Value = x.Value, - StoreId = x.StoreId - }; + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + { + var stores = _services.StoreService.GetAllStores(); + string allStoresString = _services.Localization.GetResource("Admin.Common.StoresAll"); - if (x.StoreId == 0) + var settings = _services.Settings + .GetAllSettings() + .Select(x => { - settingModel.Store = allStoresString; - } - else - { - var store = stores.FirstOrDefault(s => s.Id == x.StoreId); - settingModel.Store = store != null ? store.Name : "Unknown"; - } - - return settingModel; - }) - .ForCommand(command) - .ToList(); - - var model = new GridModel - { - Data = settings.PagedForCommand(command), - Total = settings.Count - }; + var settingModel = new SettingModel() + { + Id = x.Id, + Name = x.Name, + Value = x.Value, + StoreId = x.StoreId + }; + + if (x.StoreId == 0) + { + settingModel.Store = allStoresString; + } + else + { + var store = stores.FirstOrDefault(s => s.Id == x.StoreId); + settingModel.Store = store != null ? store.Name : "".NaIfEmpty(); + } + + return settingModel; + }) + .ForCommand(command) + .ToList(); + + model.Data = settings.PagedForCommand(command); + model.Total = settings.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1542,80 +1652,76 @@ public ActionResult AllSettings(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult SettingUpdate(SettingModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) - return AccessDeniedView(); - - if (model.Name != null) - model.Name = model.Name.Trim(); - if (model.Value != null) - model.Value = model.Value.Trim(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + { + if (model.Name != null) + model.Name = model.Name.Trim(); + if (model.Value != null) + model.Value = model.Value.Trim(); - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - var setting = _settingService.GetSettingById(model.Id); - if (setting == null) - return Content(_localizationService.GetResource("Admin.Configuration.Settings.NoneWithThatId")); + var setting = _services.Settings.GetSettingById(model.Id); + if (setting == null) + return Content(T("Admin.Configuration.Settings.NoneWithThatId")); - var storeId = model.Store.ToInt(); //use Store property (not StoreId) because appropriate property is stored in it + var storeId = model.Store.ToInt(); //use Store property (not StoreId) because appropriate property is stored in it - if (!setting.Name.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase) || - setting.StoreId != storeId) - { - //setting name or store has been changed - _settingService.DeleteSetting(setting); - } + if (!setting.Name.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase) || setting.StoreId != storeId) + { + //setting name or store has been changed + _services.Settings.DeleteSetting(setting); + } - _settingService.SetSetting(model.Name, model.Value, storeId); + _services.Settings.SetSetting(model.Name, model.Value, storeId); - //activity log - _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); + //activity log + _customerActivityService.InsertActivity("EditSettings", T("ActivityLog.EditSettings")); + } return AllSettings(command); } [GridAction(EnableCustomBinding = true)] public ActionResult SettingAdd([Bind(Exclude = "Id")] SettingModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) - return AccessDeniedView(); - - if (model.Name != null) - model.Name = model.Name.Trim(); - if (model.Value != null) - model.Value = model.Value.Trim(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + { + if (model.Name != null) + model.Name = model.Name.Trim(); + if (model.Value != null) + model.Value = model.Value.Trim(); - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - var storeId = model.Store.ToInt(); //use Store property (not StoreId) because appropriate property is stored in it - _settingService.SetSetting(model.Name, model.Value, storeId); + var storeId = model.Store.ToInt(); //use Store property (not StoreId) because appropriate property is stored in it + _services.Settings.SetSetting(model.Name, model.Value, storeId); - //activity log - _customerActivityService.InsertActivity("AddNewSetting", _localizationService.GetResource("ActivityLog.AddNewSetting"), model.Name); + //activity log + _customerActivityService.InsertActivity("AddNewSetting", T("ActivityLog.AddNewSetting", model.Name)); + } return AllSettings(command); } [GridAction(EnableCustomBinding = true)] public ActionResult SettingDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + { + var setting = _services.Settings.GetSettingById(id); - var setting = _settingService.GetSettingById(id); - if (setting == null) - throw new ArgumentException("No setting found with the specified id"); - _settingService.DeleteSetting(setting); + _services.Settings.DeleteSetting(setting); - //activity log - _customerActivityService.InsertActivity("DeleteSetting", _localizationService.GetResource("ActivityLog.DeleteSetting"), setting.Name); + //activity log + _customerActivityService.InsertActivity("DeleteSetting", T("ActivityLog.DeleteSetting", setting.Name)); + } return AllSettings(command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs index 11b1ee2560..ed497b0175 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs @@ -1,20 +1,18 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Web.Mvc; -using System.Web.Routing; -using SmartStore.Admin.Models.Directory; using SmartStore.Admin.Models.Shipping; using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Plugins; -using SmartStore.Services.Configuration; -using SmartStore.Services.Directory; +using SmartStore.Services; using SmartStore.Services.Localization; using SmartStore.Services.Security; using SmartStore.Services.Shipping; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -26,36 +24,28 @@ public partial class ShippingController : AdminControllerBase private readonly IShippingService _shippingService; private readonly ShippingSettings _shippingSettings; - private readonly ISettingService _settingService; - private readonly ICountryService _countryService; - private readonly ILocalizationService _localizationService; - private readonly IPermissionService _permissionService; private readonly ILocalizedEntityService _localizedEntityService; private readonly ILanguageService _languageService; - private readonly IPluginFinder _pluginFinder; private readonly PluginMediator _pluginMediator; + private readonly ICommonServices _services; #endregion #region Constructors - public ShippingController(IShippingService shippingService, ShippingSettings shippingSettings, - ISettingService settingService, ICountryService countryService, - ILocalizationService localizationService, IPermissionService permissionService, - ILocalizedEntityService localizedEntityService, ILanguageService languageService, - IPluginFinder pluginFinder, - PluginMediator pluginMediator) + public ShippingController(IShippingService shippingService, + ShippingSettings shippingSettings, + ILocalizedEntityService localizedEntityService, + ILanguageService languageService, + PluginMediator pluginMediator, + ICommonServices services) { this._shippingService = shippingService; this._shippingSettings = shippingSettings; - this._settingService = settingService; - this._countryService = countryService; - this._localizationService = localizationService; - this._permissionService = permissionService; this._localizedEntityService = localizedEntityService; this._languageService = languageService; - this._pluginFinder = pluginFinder; this._pluginMediator = pluginMediator; + this._services = services; } #endregion  @@ -79,13 +69,30 @@ protected void UpdateLocales(ShippingMethod shippingMethod, ShippingMethodModel } } + private void PrepareShippingMethodModel(ShippingMethodModel model, ShippingMethod shippingMethod) + { + var allFilters = _shippingService.GetAllShippingMethodFilters(); + + if (shippingMethod != null) + { + model.FilterConfigurationUrls = allFilters + .Select(x => "'" + x.GetConfigurationUrl(shippingMethod.Id) + "'") + .OrderBy(x => x) + .ToList(); + } + else + { + model.FilterConfigurationUrls = new List(); + } + } + #endregion #region Shipping rate computation methods public ActionResult Providers() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageShippingSettings)) return AccessDeniedView(); var shippingProvidersModel = new List(); @@ -102,19 +109,23 @@ public ActionResult Providers() public ActionResult ActivateProvider(string systemName, bool activate) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageShippingSettings)) return AccessDeniedView(); var srcm = _shippingService.LoadShippingRateComputationMethodBySystemName(systemName); - bool dirty = srcm.IsShippingRateComputationMethodActive(_shippingSettings) != activate; - if (dirty) + + if (activate && !srcm.Value.IsActive) + { + NotifyWarning(_services.Localization.GetResource("Admin.Configuration.Payment.CannotActivateShippingRateComputationMethod")); + } + else { if (!activate) _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Remove(srcm.Metadata.SystemName); else _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Add(srcm.Metadata.SystemName); - _settingService.SaveSetting(_shippingSettings); + _services.Settings.SaveSetting(_shippingSettings); _pluginMediator.ActivateDependentWidgets(srcm.Metadata, activate); } @@ -127,12 +138,13 @@ public ActionResult ActivateProvider(string systemName, bool activate) public ActionResult Methods() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageShippingSettings)) return AccessDeniedView(); var shippingMethodsModel = _shippingService.GetAllShippingMethods() .Select(x => x.ToModel()) .ToList(); + var model = new GridModel { Data = shippingMethodsModel, @@ -144,18 +156,24 @@ public ActionResult Methods() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Methods(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); + var model = new GridModel(); - var shippingMethodsModel = _shippingService.GetAllShippingMethods() - .Select(x => x.ToModel()) - .ForCommand(command) - .ToList(); - var model = new GridModel - { - Data = shippingMethodsModel, - Total = shippingMethodsModel.Count - }; + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageShippingSettings)) + { + var shippingMethodsModel = _shippingService.GetAllShippingMethods() + .Select(x => x.ToModel()) + .ForCommand(command) + .ToList(); + + model.Data = shippingMethodsModel; + model.Total = shippingMethodsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -166,29 +184,33 @@ public ActionResult Methods(GridCommand command) public ActionResult CreateMethod() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageShippingSettings)) return AccessDeniedView(); var model = new ShippingMethodModel(); + PrepareShippingMethodModel(model, null); + //locales AddLocales(_languageService, model.Locales); return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult CreateMethod(ShippingMethodModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageShippingSettings)) return AccessDeniedView(); if (ModelState.IsValid) { var sm = model.ToEntity(); + _shippingService.InsertShippingMethod(sm); - //locales + UpdateLocales(sm, model); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Shipping.Methods.Added")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Shipping.Methods.Added")); + return continueEditing ? RedirectToAction("EditMethod", new { id = sm.Id }) : RedirectToAction("Methods"); } @@ -198,16 +220,16 @@ public ActionResult CreateMethod(ShippingMethodModel model, bool continueEditing public ActionResult EditMethod(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageShippingSettings)) return AccessDeniedView(); var sm = _shippingService.GetShippingMethodById(id); if (sm == null) - //No shipping method found with the specified id return RedirectToAction("Methods"); var model = sm.ToModel(); - //locales + PrepareShippingMethodModel(model, sm); + AddLocales(_languageService, model.Locales, (locale, languageId) => { locale.Name = sm.GetLocalized(x => x.Name, languageId, false, false); @@ -217,132 +239,50 @@ public ActionResult EditMethod(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] - public ActionResult EditMethod(ShippingMethodModel model, bool continueEditing) + [HttpPost, ValidateInput(false), ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + public ActionResult EditMethod(ShippingMethodModel model, bool continueEditing, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageShippingSettings)) return AccessDeniedView(); var sm = _shippingService.GetShippingMethodById(model.Id); if (sm == null) - //No shipping method found with the specified id return RedirectToAction("Methods"); if (ModelState.IsValid) { sm = model.ToEntity(sm); + _shippingService.UpdateShippingMethod(sm); - //locales + UpdateLocales(sm, model); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Shipping.Methods.Updated")); + + _services.EventPublisher.Publish(new ModelBoundEvent(model, sm, form)); + + NotifySuccess(T("Admin.Configuration.Shipping.Methods.Updated")); + return continueEditing ? RedirectToAction("EditMethod", sm.Id) : RedirectToAction("Methods"); } - - //If we got this far, something failed, redisplay form return View(model); } [HttpPost] public ActionResult DeleteMethod(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageShippingSettings)) return AccessDeniedView(); var sm = _shippingService.GetShippingMethodById(id); if (sm == null) - //No shipping method found with the specified id return RedirectToAction("Methods"); _shippingService.DeleteShippingMethod(sm); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Shipping.Methods.Deleted")); + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Shipping.Methods.Deleted")); return RedirectToAction("Methods"); } - #endregion - - #region Restrictions - - public ActionResult Restrictions() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var model = new ShippingMethodRestrictionModel(); - - var countries = _countryService.GetAllCountries(true); - var shippingMethods = _shippingService.GetAllShippingMethods(); - foreach (var country in countries) - { - model.AvailableCountries.Add(new CountryModel() - { - Id = country.Id, - Name = country.Name - }); - } - foreach (var sm in shippingMethods) - { - model.AvailableShippingMethods.Add(new ShippingMethodModel() - { - Id = sm.Id, - Name = sm.Name - }); - } - foreach (var country in countries) - foreach (var shippingMethod in shippingMethods) - { - bool restricted = shippingMethod.CountryRestrictionExists(country.Id); - if (!model.Restricted.ContainsKey(country.Id)) - model.Restricted[country.Id] = new Dictionary(); - model.Restricted[country.Id][shippingMethod.Id] = restricted; - } - - return View(model); - } - - [HttpPost, ActionName("Restrictions")] - public ActionResult RestrictionSave(FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var countries = _countryService.GetAllCountries(true); - var shippingMethods = _shippingService.GetAllShippingMethods(); - - - foreach (var shippingMethod in shippingMethods) - { - string formKey = "restrict_" + shippingMethod.Id; - var countryIdsToRestrict = form[formKey] != null ? form[formKey].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => int.Parse(x)).ToList() : new List(); - - foreach (var country in countries) - { - - bool restrict = countryIdsToRestrict.Contains(country.Id); - if (restrict) - { - if (shippingMethod.RestrictedCountries.Where(c => c.Id == country.Id).FirstOrDefault() == null) - { - shippingMethod.RestrictedCountries.Add(country); - _shippingService.UpdateShippingMethod(shippingMethod); - } - } - else - { - if (shippingMethod.RestrictedCountries.Where(c => c.Id == country.Id).FirstOrDefault() != null) - { - shippingMethod.RestrictedCountries.Remove(country); - _shippingService.UpdateShippingMethod(shippingMethod); - } - } - } - } - - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Shipping.Restrictions.Updated")); - return RedirectToAction("Restrictions"); - } - - #endregion + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs index c2153a7baf..fccebd040d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs @@ -11,6 +11,7 @@ using SmartStore.Services.Stores; using SmartStore.Services.Tax; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -64,28 +65,32 @@ public ActionResult CurrentCarts() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CurrentCarts(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var customers = _customerService.GetAllCustomers(null, null, null, null, null, - null, null, 0, 0, null, null, null, true, ShoppingCartType.ShoppingCart, - command.Page - 1, command.PageSize); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var customers = _customerService.GetAllCustomers(null, null, null, null, null, null, null, + 0, 0, null, null, null, true, ShoppingCartType.ShoppingCart, command.Page - 1, command.PageSize); + + gridModel.Data = customers.Select(x => + { + return new ShoppingCartModel + { + CustomerId = x.Id, + CustomerEmail = x.IsGuest() ? T("Admin.Customers.Guest").Text : x.Email, + TotalItems = x.CountProductsInCart(ShoppingCartType.ShoppingCart) + }; + }); + + gridModel.Total = customers.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var gridModel = new GridModel - { - Data = customers.Select(x => - { - return new ShoppingCartModel() - { - CustomerId = x.Id, - CustomerEmail = x.IsGuest() ? - _localizationService.GetResource("Admin.Customers.Guest") : - x.Email, - TotalItems = x.CountProductsInCart(ShoppingCartType.ShoppingCart) - }; - }), - Total = customers.TotalCount - }; return new JsonResult { Data = gridModel @@ -95,35 +100,43 @@ public ActionResult CurrentCarts(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult GetCartDetails(int customerId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var customer = _customerService.GetCustomerById(customerId); - var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var customer = _customerService.GetCustomerById(customerId); + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart); - var gridModel = new GridModel() - { - Data = cart.Select(sci => - { - decimal taxRate; + gridModel.Data = cart.Select(sci => + { + decimal taxRate; var store = _storeService.GetStoreById(sci.Item.StoreId); - var sciModel = new ShoppingCartItemModel() - { - Id = sci.Item.Id, - Store = store != null ? store.Name : "Unknown", + + var sciModel = new ShoppingCartItemModel + { + Id = sci.Item.Id, + Store = store != null ? store.Name : "".NaIfEmpty(), ProductId = sci.Item.ProductId, - Quantity = sci.Item.Quantity, + Quantity = sci.Item.Quantity, ProductName = sci.Item.Product.Name, ProductTypeName = sci.Item.Product.GetProductTypeLabel(_localizationService), ProductTypeLabelHint = sci.Item.Product.ProductTypeLabelHint, - UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)), - Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetSubTotal(sci, true), out taxRate)), - UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.Item.UpdatedOnUtc, DateTimeKind.Utc) - }; - return sciModel; - }), - Total = cart.Count - }; + UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)), + Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetSubTotal(sci, true), out taxRate)), + UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.Item.UpdatedOnUtc, DateTimeKind.Utc) + }; + return sciModel; + }); + + gridModel.Total = cart.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel @@ -143,28 +156,32 @@ public ActionResult CurrentWishlists() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CurrentWishlists(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var customers = _customerService.GetAllCustomers(null, null, null, null, null, - null, null, 0, 0, null, null, null, - true, ShoppingCartType.Wishlist, command.Page - 1, command.PageSize); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var customers = _customerService.GetAllCustomers(null, null, null, null, null, null, null, + 0, 0, null, null, null, true, ShoppingCartType.Wishlist, command.Page - 1, command.PageSize); + + gridModel.Data = customers.Select(x => + { + return new ShoppingCartModel + { + CustomerId = x.Id, + CustomerEmail = x.IsGuest() ? T("Admin.Customers.Guest").Text : x.Email, + TotalItems = x.CountProductsInCart(ShoppingCartType.Wishlist) + }; + }); + + gridModel.Total = customers.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var gridModel = new GridModel - { - Data = customers.Select(x => - { - return new ShoppingCartModel() - { - CustomerId = x.Id, - CustomerEmail = x.IsGuest() ? - _localizationService.GetResource("Admin.Customers.Guest") : - x.Email, - TotalItems = x.CountProductsInCart(ShoppingCartType.Wishlist) - }; - }), - Total = customers.TotalCount - }; return new JsonResult { Data = gridModel @@ -174,35 +191,43 @@ public ActionResult CurrentWishlists(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult GetWishlistDetails(int customerId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var customer = _customerService.GetCustomerById(customerId); - var cart = customer.GetCartItems(ShoppingCartType.Wishlist); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var customer = _customerService.GetCustomerById(customerId); + var cart = customer.GetCartItems(ShoppingCartType.Wishlist); - var gridModel = new GridModel() - { - Data = cart.Select(sci => - { - decimal taxRate; - var store = _storeService.GetStoreById(sci.Item.StoreId); - var sciModel = new ShoppingCartItemModel() - { - Id = sci.Item.Id, - Store = store != null ? store.Name : "Unknown", - ProductId = sci.Item.ProductId, - Quantity = sci.Item.Quantity, + gridModel.Data = cart.Select(sci => + { + decimal taxRate; + var store = _storeService.GetStoreById(sci.Item.StoreId); + + var sciModel = new ShoppingCartItemModel + { + Id = sci.Item.Id, + Store = store != null ? store.Name : "".NaIfEmpty(), + ProductId = sci.Item.ProductId, + Quantity = sci.Item.Quantity, ProductName = sci.Item.Product.Name, ProductTypeName = sci.Item.Product.GetProductTypeLabel(_localizationService), ProductTypeLabelHint = sci.Item.Product.ProductTypeLabelHint, - UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)), - Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetSubTotal(sci, true), out taxRate)), - UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.Item.UpdatedOnUtc, DateTimeKind.Utc) - }; - return sciModel; - }), - Total = cart.Count - }; + UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)), + Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetSubTotal(sci, true), out taxRate)), + UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.Item.UpdatedOnUtc, DateTimeKind.Utc) + }; + return sciModel; + }); + + gridModel.Total = cart.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SpecificationAttributeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SpecificationAttributeController.cs index 55301c0f95..a15ca73afc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SpecificationAttributeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SpecificationAttributeController.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Catalog; +using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; using SmartStore.Core.Logging; @@ -10,6 +12,8 @@ using SmartStore.Services.Security; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -117,19 +121,31 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var data = _specificationAttributeService.GetSpecificationAttributes() - .ForCommand(command) - .Select(x => x.ToModel()) - .ToList(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var data = _specificationAttributeService.GetSpecificationAttributes() + .Expand(x => x.SpecificationAttributeOptions) + .ForCommand(command) + .Select(x => + { + var model = x.ToModel(); + model.OptionCount = x.SpecificationAttributeOptions.Count; + + return model; + }) + .ToList(); + + gridModel.Data = data.PagedForCommand(command); + gridModel.Total = data.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); - var gridModel = new GridModel - { - Data = data.PagedForCommand(command), - Total = data.Count - }; + NotifyAccessDenied(); + } return new JsonResult { @@ -137,7 +153,6 @@ public ActionResult List(GridCommand command) }; } - //create public ActionResult Create() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -149,7 +164,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(SpecificationAttributeModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -172,7 +187,6 @@ public ActionResult Create(SpecificationAttributeModel model, bool continueEditi return View(model); } - //edit public ActionResult Edit(int id) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -180,7 +194,6 @@ public ActionResult Edit(int id) var specificationAttribute = _specificationAttributeService.GetSpecificationAttributeById(id); if (specificationAttribute == null) - //No specification attribute found with the specified id return RedirectToAction("List"); var model = specificationAttribute.ToModel(); @@ -193,7 +206,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(SpecificationAttributeModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -201,7 +214,6 @@ public ActionResult Edit(SpecificationAttributeModel model, bool continueEditing var specificationAttribute = _specificationAttributeService.GetSpecificationAttributeById(model.Id); if (specificationAttribute == null) - //No specification attribute found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -222,7 +234,6 @@ public ActionResult Edit(SpecificationAttributeModel model, bool continueEditing return View(model); } - //delete [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { @@ -231,7 +242,6 @@ public ActionResult DeleteConfirmed(int id) var specificationAttribute = _specificationAttributeService.GetSpecificationAttributeById(id); if (specificationAttribute == null) - //No specification attribute found with the specified id return RedirectToAction("List"); _specificationAttributeService.DeleteSpecificationAttribute(specificationAttribute); @@ -243,6 +253,26 @@ public ActionResult DeleteConfirmed(int id) return RedirectToAction("List"); } + [HttpPost] + public ActionResult DeleteSelected(ICollection selectedIds) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + return AccessDeniedView(); + + if (selectedIds != null && selectedIds.Count > 0) + { + var attributes = _specificationAttributeService.GetSpecificationAttributesByIds(selectedIds.ToArray()).ToList(); + string deletedNames = string.Join(", ", attributes.Select(x => x.Name)); + + foreach (var attribute in attributes) + _specificationAttributeService.DeleteSpecificationAttribute(attribute); + + _customerActivityService.InsertActivity("DeleteSpecAttribute", _localizationService.GetResource("ActivityLog.DeleteSpecAttribute"), deletedNames); + } + + return Json(new { Result = true }); + } + [HttpPost] public ActionResult ProductMappingEdit(int specificationAttributeId, string field, bool value) { @@ -259,35 +289,31 @@ public ActionResult ProductMappingEdit(int specificationAttributeId, string fiel #region Specification attribute options - //list [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult OptionList(int specificationAttributeId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var options = _specificationAttributeService.GetSpecificationAttributeOptionsBySpecificationAttribute(specificationAttributeId); + + gridModel.Data = options.Select(x => x.ToModel()); + gridModel.Total = options.Count(); + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var options = _specificationAttributeService.GetSpecificationAttributeOptionsBySpecificationAttribute(specificationAttributeId); - var gridModel = new GridModel - { - Data = options.Select(x => - { - var model = x.ToModel(); - //locales - //AddLocales(_languageService, model.Locales, (locale, languageId) => - //{ - // locale.Name = x.GetLocalized(y => y.Name, languageId, false, false); - //}); - return model; - }), - Total = options.Count() - }; return new JsonResult { Data = gridModel }; } - //create public ActionResult OptionCreatePopup(int specificationAttributeId) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -337,7 +363,6 @@ public ActionResult OptionCreatePopup(string btnId, string formId, Specification return View(model); } - //edit public ActionResult OptionEditPopup(int id) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -345,7 +370,6 @@ public ActionResult OptionEditPopup(int id) var sao = _specificationAttributeService.GetSpecificationAttributeOptionById(id); if (sao == null) - //No specification attribute option found with the specified id return RedirectToAction("List"); var model = sao.ToModel(); @@ -366,7 +390,6 @@ public ActionResult OptionEditPopup(string btnId, string formId, SpecificationAt var sao = _specificationAttributeService.GetSpecificationAttributeOptionById(model.Id); if (sao == null) - //No specification attribute option found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -386,23 +409,19 @@ public ActionResult OptionEditPopup(string btnId, string formId, SpecificationAt return View(model); } - //delete [GridAction(EnableCustomBinding = true)] public ActionResult OptionDelete(int optionId, int specificationAttributeId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var sao = _specificationAttributeService.GetSpecificationAttributeOptionById(optionId); - if (sao == null) - throw new ArgumentException("No specification attribute option found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var sao = _specificationAttributeService.GetSpecificationAttributeOptionById(optionId); - _specificationAttributeService.DeleteSpecificationAttributeOption(sao); + _specificationAttributeService.DeleteSpecificationAttributeOption(sao); + } return OptionList(specificationAttributeId, command); } - //ajax [AcceptVerbs(HttpVerbs.Get)] public ActionResult GetOptionsByAttributeId(string attributeId) @@ -440,7 +459,6 @@ public ActionResult SetAttributeValue(string pk, string value, string name, Form } } - #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/StoreController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/StoreController.cs index b29282d610..38df324a4c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/StoreController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/StoreController.cs @@ -1,16 +1,17 @@ using System; using System.Linq; using System.Web.Mvc; -using SmartStore.Admin.Models.Directory; using SmartStore.Admin.Models.Stores; -using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Stores; using SmartStore.Services.Configuration; using SmartStore.Services.Directory; using SmartStore.Services.Localization; +using SmartStore.Services.Media; using SmartStore.Services.Security; using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -22,16 +23,30 @@ public partial class StoreController : AdminControllerBase private readonly ISettingService _settingService; private readonly ILocalizationService _localizationService; private readonly IPermissionService _permissionService; + private readonly ICurrencyService _currencyService; public StoreController(IStoreService storeService, ISettingService settingService, ILocalizationService localizationService, - IPermissionService permissionService) + IPermissionService permissionService, + ICurrencyService currencyService) { this._storeService = storeService; this._settingService = settingService; this._localizationService = localizationService; this._permissionService = permissionService; + this._currencyService = currencyService; + } + + private void PrepareStoreModel(StoreModel model, Store store) + { + model.AvailableCurrencies = _currencyService.GetAllCurrencies(false, store == null ? 0 : store.Id) + .Select(x => new SelectListItem + { + Text = x.Name, + Value = x.Id.ToString() + }) + .ToList(); } public ActionResult List() @@ -66,18 +81,32 @@ from m in stores [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageStores)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var storeModels = _storeService.GetAllStores() - .Select(x => x.ToModel()) - .ToList(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageStores)) + { + var storeModels = _storeService.GetAllStores() + .Select(x => + { + var model = x.ToModel(); + + PrepareStoreModel(model, x); + + model.Hosts = model.Hosts.EmptyNull().Replace(",", "
    "); + + return model; + }) + .ToList(); - var gridModel = new GridModel + gridModel.Data = storeModels; + gridModel.Total = storeModels.Count(); + } + else { - Data = storeModels, - Total = storeModels.Count() - }; + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -91,10 +120,12 @@ public ActionResult Create() return AccessDeniedView(); var model = new StoreModel(); + PrepareStoreModel(model, null); + return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(StoreModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageStores)) @@ -103,6 +134,7 @@ public ActionResult Create(StoreModel model, bool continueEditing) if (ModelState.IsValid) { var store = model.ToEntity(); + MediaHelper.UpdatePictureTransientStateFor(store, s => s.LogoPictureId); //ensure we have "/" at the end store.Url = store.Url.EnsureEndsWith("/"); _storeService.InsertStore(store); @@ -112,6 +144,7 @@ public ActionResult Create(StoreModel model, bool continueEditing) } //If we got this far, something failed, redisplay form + PrepareStoreModel(model, null); return View(model); } @@ -122,14 +155,15 @@ public ActionResult Edit(int id) var store = _storeService.GetStoreById(id); if (store == null) - //No store found with the specified id return RedirectToAction("List"); var model = store.ToModel(); + PrepareStoreModel(model, store); + return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [FormValueRequired("save", "save-continue")] public ActionResult Edit(StoreModel model, bool continueEditing) { @@ -138,12 +172,14 @@ public ActionResult Edit(StoreModel model, bool continueEditing) var store = _storeService.GetStoreById(model.Id); if (store == null) - //No store found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) { store = model.ToEntity(store); + + MediaHelper.UpdatePictureTransientStateFor(store, s => s.LogoPictureId); + //ensure we have "/" at the end store.Url = store.Url.EnsureEndsWith("/"); _storeService.UpdateStore(store); @@ -153,6 +189,7 @@ public ActionResult Edit(StoreModel model, bool continueEditing) } //If we got this far, something failed, redisplay form + PrepareStoreModel(model, store); return View(model); } @@ -164,7 +201,6 @@ public ActionResult Delete(int id) var store = _storeService.GetStoreById(id); if (store == null) - //No store found with the specified id return RedirectToAction("List"); try @@ -176,18 +212,20 @@ public ActionResult Delete(int id) .GetAllSettings() .Where(s => s.StoreId == id) .ToList(); - foreach (var setting in settingsToDelete) - _settingService.DeleteSetting(setting); + + settingsToDelete.ForEach(x => _settingService.DeleteSetting(x)); + //when we had two stores and now have only one store, we also should delete all "per store" settings var allStores = _storeService.GetAllStores(); + if (allStores.Count == 1) { settingsToDelete = _settingService .GetAllSettings() .Where(s => s.StoreId == allStores[0].Id) .ToList(); - foreach (var setting in settingsToDelete) - _settingService.DeleteSetting(setting); + + settingsToDelete.ForEach(x => _settingService.DeleteSetting(x)); } NotifySuccess(_localizationService.GetResource("Admin.Configuration.Stores.Deleted")); @@ -196,8 +234,8 @@ public ActionResult Delete(int id) catch (Exception exc) { NotifyError(exc); - return RedirectToAction("Edit", new { id = store.Id }); } + return RedirectToAction("Edit", new { id = store.Id }); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/TaxController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/TaxController.cs index 2498740e3d..a694f7df48 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/TaxController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/TaxController.cs @@ -12,6 +12,7 @@ using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -115,18 +116,24 @@ public ActionResult Categories() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Categories(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) - return AccessDeniedView(); + var model = new GridModel(); - var categoriesModel = _taxCategoryService.GetAllTaxCategories() - .Select(x => x.ToModel()) - .ForCommand(command) - .ToList(); - var model = new GridModel - { - Data = categoriesModel, - Total = categoriesModel.Count - }; + if (_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) + { + var categoriesModel = _taxCategoryService.GetAllTaxCategories() + .Select(x => x.ToModel()) + .ForCommand(command) + .ToList(); + + model.Data = categoriesModel; + model.Total = categoriesModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -137,19 +144,19 @@ public ActionResult Categories(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult CategoryUpdate(TaxCategoryModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var taxCategory = _taxCategoryService.GetTaxCategoryById(model.Id); + taxCategory = model.ToEntity(taxCategory); - var taxCategory = _taxCategoryService.GetTaxCategoryById(model.Id); - taxCategory = model.ToEntity(taxCategory); - _taxCategoryService.UpdateTaxCategory(taxCategory); + _taxCategoryService.UpdateTaxCategory(taxCategory); + } return Categories(command); } @@ -157,19 +164,19 @@ public ActionResult CategoryUpdate(TaxCategoryModel model, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult CategoryAdd([Bind(Exclude = "Id")] TaxCategoryModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var taxCategory = new TaxCategory(); + taxCategory = model.ToEntity(taxCategory); - var taxCategory = new TaxCategory(); - taxCategory = model.ToEntity(taxCategory); - _taxCategoryService.InsertTaxCategory(taxCategory); + _taxCategoryService.InsertTaxCategory(taxCategory); + } return Categories(command); } @@ -177,13 +184,12 @@ public ActionResult CategoryAdd([Bind(Exclude = "Id")] TaxCategoryModel model, G [GridAction(EnableCustomBinding = true)] public ActionResult CategoryDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) + { + var taxCategory = _taxCategoryService.GetTaxCategoryById(id); - var taxCategory = _taxCategoryService.GetTaxCategoryById(id); - if (taxCategory == null) - throw new ArgumentException("No tax category found with the specified id"); - _taxCategoryService.DeleteTaxCategory(taxCategory); + _taxCategoryService.DeleteTaxCategory(taxCategory); + } return Categories(command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs index f4c88744bf..c47332e250 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs @@ -9,6 +9,7 @@ using System.Web.Mvc; using SmartStore.Admin.Models.Themes; using SmartStore.Collections; +using SmartStore.Core; using SmartStore.Core.Domain.Themes; using SmartStore.Core.Localization; using SmartStore.Core.Packaging; @@ -20,8 +21,10 @@ using SmartStore.Services.Themes; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Mvc; -using SmartStore.Web.Framework.Themes; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Framework.Security; +using SmartStore.Web.Framework.Theming; namespace SmartStore.Admin.Controllers { @@ -61,12 +64,8 @@ public ThemeController( this._services = services; this._themeContext = themeContext; this._themeFileResolver = themeFileResolver; - - this.T = NullLocalizer.Instance; } - public Localizer T { get; set; } - #endregion  #region Methods @@ -206,7 +205,7 @@ public ActionResult Configure(string theme, int storeId) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Configure(string theme, int storeId, IDictionary values, bool continueEditing) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageThemes)) @@ -301,13 +300,12 @@ private string ValidateLess(ThemeManifest manifest, int storeId) virtualPath = file.ResultVirtualPath; } - var url = "{0}{1}?storeId={2}&theme={3}".FormatInvariant( - _services.WebHelper.GetStoreLocation().EnsureEndsWith("/"), - VirtualPathUtility.ToAbsolute(virtualPath).TrimStart('/'), + var url = "{0}?storeId={1}&theme={2}".FormatInvariant( + WebHelper.GetAbsoluteUrl(virtualPath, this.Request), storeId, manifest.ThemeName); - HttpWebRequest request = WebRequest.CreateHttp(url); + var request = WebHelper.CreateHttpRequestForSafeLocalCall(new Uri(url)); WebResponse response = null; try @@ -396,11 +394,7 @@ public ActionResult ImportVariables(string theme, int storeId, FormCollection fo if (file != null && file.ContentLength > 0) { int importedCount = 0; - using (var sr = new StreamReader(file.InputStream, Encoding.UTF8)) - { - string content = sr.ReadToEnd(); - importedCount = _themeVarService.ImportVariables(theme, storeId, content); - } + importedCount = _themeVarService.ImportVariables(theme, storeId, file.InputStream.AsString()); // activity log try @@ -546,7 +540,7 @@ public ActionResult PreviewToolPost(string theme, int storeId, string returnUrl) _services.StoreContext.SetPreviewStore(storeId); } - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [HttpPost, ActionName("PreviewTool"), FormValueRequired("PreviewMode.Exit")] @@ -561,12 +555,7 @@ public ActionResult ExitPreview() } var returnUrl = (string)TempData["PreviewModeReturnUrl"]; - if (returnUrl.IsEmpty()) - { - returnUrl = Url.Action("Index", "Home", new { area = (string)null }); - } - - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [HttpPost, ActionName("PreviewTool"), FormValueRequired("PreviewMode.Apply")] diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/TopicController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/TopicController.cs index 70e91c379a..065b2eadf0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/TopicController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/TopicController.cs @@ -9,7 +9,9 @@ using SmartStore.Services.Stores; using SmartStore.Services.Topics; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -121,10 +123,11 @@ public ActionResult List() return AccessDeniedView(); var model = new TopicListModel(); - //stores - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + foreach (var s in _storeService.GetAllStores()) - model.AvailableStores.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString() }); + { + model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); + } return View(model); } @@ -132,15 +135,29 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, TopicListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageTopics)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageTopics)) + { + var topics = _topicService.GetAllTopics(model.SearchStoreId); + + gridModel.Data = topics.Select(x => + { + var item = x.ToModel(); + // otherwise maxJsonLength could be exceeded + item.Body = ""; + return item; + }); + + gridModel.Total = topics.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var topics = _topicService.GetAllTopics(model.SearchStoreId); - var gridModel = new GridModel - { - Data = topics.Select(x => x.ToModel()), - Total = topics.Count - }; return new JsonResult { MaxJsonLength = int.MaxValue, @@ -168,7 +185,7 @@ public ActionResult Create() return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(TopicModel model, bool continueEditing) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageTopics)) @@ -224,7 +241,7 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] [ValidateInput(false)] public ActionResult Edit(TopicModel model, bool continueEditing, FormCollection form) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs new file mode 100644 index 0000000000..d51b32bb19 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using SmartStore.Admin.Models.UrlRecord; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Seo; +using SmartStore.Services; +using SmartStore.Services.Localization; +using SmartStore.Services.Security; +using SmartStore.Services.Seo; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; +using Telerik.Web.Mvc; + +namespace SmartStore.Admin.Controllers +{ + [AdminAuthorize] + public class UrlRecordController : AdminControllerBase + { + private readonly IUrlRecordService _urlRecordService; + private readonly AdminAreaSettings _adminAreaSettings; + private readonly ICommonServices _services; + private readonly ILanguageService _languageService; + + public UrlRecordController( + IUrlRecordService urlRecordService, + AdminAreaSettings adminAreaSettings, + ICommonServices services, + ILanguageService languageService) + { + _urlRecordService = urlRecordService; + _adminAreaSettings = adminAreaSettings; + _services = services; + _languageService = languageService; + } + + private void PrepareUrlRecordModel(UrlRecordModel model, UrlRecord urlRecord, bool forList = false) + { + if (!forList) + { + var allLanguages = _languageService.GetAllLanguages(true); + + model.AvailableLanguages = allLanguages + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.AvailableLanguages.Insert(0, new SelectListItem { Text = T("Admin.System.SeNames.Language.Standard"), Value = "0" }); + } + + if (urlRecord != null) + { + model.Id = urlRecord.Id; + model.Slug = urlRecord.Slug; + model.EntityName = urlRecord.EntityName; + model.EntityId = urlRecord.EntityId; + model.IsActive = urlRecord.IsActive; + model.LanguageId = urlRecord.LanguageId; + + if (urlRecord.EntityName.IsCaseInsensitiveEqual("BlogPost")) + { + model.EntityUrl = Url.Action("Edit", "Blog", new { id = urlRecord.EntityId }); + } + else if (urlRecord.EntityName.IsCaseInsensitiveEqual("Forum")) + { + model.EntityUrl = Url.Action("EditForum", "Forum", new { id = urlRecord.EntityId }); + } + else if (urlRecord.EntityName.IsCaseInsensitiveEqual("ForumGroup")) + { + model.EntityUrl = Url.Action("EditForumGroup", "Forum", new { id = urlRecord.EntityId }); + } + else if (urlRecord.EntityName.IsCaseInsensitiveEqual("NewsItem")) + { + model.EntityUrl = Url.Action("Edit", "News", new { id = urlRecord.EntityId }); + } + else + { + model.EntityUrl = Url.Action("Edit", urlRecord.EntityName, new { id = urlRecord.EntityId }); + } + } + } + + public ActionResult List(string entityName, int? entityId) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) + return AccessDeniedView(); + + var model = new UrlRecordListModel + { + GridPageSize = _adminAreaSettings.GridPageSize, + EntityName = entityName, + EntityId = entityId + }; + + var allLanguages = _languageService.GetAllLanguages(true); + + model.AvailableLanguages = allLanguages + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.AvailableLanguages.Insert(0, new SelectListItem { Text = T("Admin.System.SeNames.Language.Standard"), Value = "0" }); + + return View(model); + } + + [HttpPost, GridAction(EnableCustomBinding = true)] + public ActionResult List(GridCommand command, UrlRecordListModel model) + { + var gridModel = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) + { + var allLanguages = _languageService.GetAllLanguages(true); + var defaultLanguageName = T("Admin.System.SeNames.Language.Standard"); + + var urlRecords = _urlRecordService.GetAllUrlRecords(command.Page - 1, command.PageSize, + model.SeName, model.EntityName, model.EntityId, model.LanguageId, model.IsActive); + + var slugsPerEntity = _urlRecordService.CountSlugsPerEntity(urlRecords.Select(x => x.Id).Distinct().ToArray()); + + gridModel.Data = urlRecords.Select(x => + { + string languageName; + + if (x.LanguageId == 0) + { + languageName = defaultLanguageName; + } + else + { + var language = allLanguages.FirstOrDefault(y => y.Id == x.LanguageId); + languageName = (language != null ? language.Name : "".NaIfEmpty()); + } + + var urlRecordModel = new UrlRecordModel(); + PrepareUrlRecordModel(urlRecordModel, x, true); + + urlRecordModel.Language = languageName; + urlRecordModel.SlugsPerEntity = (slugsPerEntity.ContainsKey(x.Id) ? slugsPerEntity[x.Id] : 0); + + return urlRecordModel; + }); + + gridModel.Total = urlRecords.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + + return new JsonResult + { + Data = gridModel + }; + } + + public ActionResult Edit(int id) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) + return AccessDeniedView(); + + var urlRecord = _urlRecordService.GetUrlRecordById(id); + if (urlRecord == null) + return RedirectToAction("List"); + + var model = new UrlRecordModel(); + PrepareUrlRecordModel(model, urlRecord); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + public ActionResult Edit(UrlRecordModel model, bool continueEditing) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) + return AccessDeniedView(); + + var urlRecord = _urlRecordService.GetUrlRecordById(model.Id); + if (urlRecord == null) + return RedirectToAction("List"); + + if (!urlRecord.IsActive && model.IsActive) + { + var urlRecords = _urlRecordService.GetAllUrlRecords(0, int.MaxValue, null, model.EntityName, model.EntityId, model.LanguageId, true); + if (urlRecords.Count > 0) + { + ModelState.AddModelError("IsActive", T("Admin.System.SeNames.ActiveSlugAlreadyExist")); + } + } + + if (ModelState.IsValid) + { + urlRecord.Slug = model.Slug; + urlRecord.EntityName = model.EntityName; + urlRecord.IsActive = model.IsActive; + urlRecord.LanguageId = model.LanguageId; + + _urlRecordService.UpdateUrlRecord(urlRecord); + + NotifySuccess(T("Admin.Common.DataEditSuccess")); + + return continueEditing ? RedirectToAction("Edit", new { id = urlRecord.Id }) : RedirectToAction("List"); + } + + PrepareUrlRecordModel(model, null); + + return View(model); + } + + [HttpPost, ActionName("Delete")] + public ActionResult DeleteConfirmed(int id) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) + return AccessDeniedView(); + + var urlRecord = _urlRecordService.GetUrlRecordById(id); + if (urlRecord == null) + return RedirectToAction("List"); + + try + { + _urlRecordService.DeleteUrlRecord(urlRecord); + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + + return RedirectToAction("List"); + } + catch (Exception exc) + { + NotifyError(exc); + return RedirectToAction("Edit", new { id = urlRecord.Id }); + } + } + + [HttpPost] + public ActionResult DeleteSelected(ICollection selectedIds) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) + return AccessDeniedView(); + + if (selectedIds != null) + { + var urlRecords = _urlRecordService.GetUrlRecordsByIds(selectedIds.ToArray()); + + foreach (var urlRecord in urlRecords) + { + _urlRecordService.DeleteUrlRecord(urlRecord); + } + } + + return Json(new { Result = true }); + } + + [ChildActionOnly] + public ActionResult NamesPerEntity(string entityName, int entityId) + { + if (entityName.IsEmpty() || entityId == 0) + return new EmptyResult(); + + var count = _urlRecordService.CountSlugsPerEntity(entityName, entityId); + + ViewBag.CountSlugsPerEntity = count; + ViewBag.UrlRecordListUrl = Url.Action("List", "UrlRecord", new { entityName = entityName, entityId = entityId }); + + return View(); + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/WidgetController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/WidgetController.cs index f6dd962c6c..9e04047f3e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/WidgetController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/WidgetController.cs @@ -11,6 +11,7 @@ using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -100,29 +101,6 @@ public ActionResult ActivateProvider(string systemName, bool activate) return RedirectToAction("Providers"); } - - [ChildActionOnly] - public ActionResult WidgetsByZone(string widgetZone) - { - var model = new List(); - - var widgets = _widgetService.LoadActiveWidgetsByWidgetZone(widgetZone); - foreach (var widget in widgets) - { - var widgetModel = new RenderWidgetModel(); - - string actionName; - string controllerName; - RouteValueDictionary routeValues; - widget.Value.GetDisplayWidgetRoute(widgetZone, null, 0, out actionName, out controllerName, out routeValues); - widgetModel.ActionName = actionName; - widgetModel.ControllerName = controllerName; - widgetModel.RouteValues = routeValues; - - model.Add(widgetModel); - } - return PartialView(model); - } #endregion } diff --git a/src/Presentation/SmartStore.Web/Administration/Extensions/HtmlHelperExtensions.cs b/src/Presentation/SmartStore.Web/Administration/Extensions/HtmlHelperExtensions.cs index 1a3fad709a..70d8152faf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Extensions/HtmlHelperExtensions.cs +++ b/src/Presentation/SmartStore.Web/Administration/Extensions/HtmlHelperExtensions.cs @@ -6,7 +6,7 @@ using SmartStore.Admin.Models.Catalog; using SmartStore.Admin.Models.Plugins; using SmartStore.Core; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Plugins; namespace SmartStore.Admin.Extensions { diff --git a/src/Presentation/SmartStore.Web/Administration/Extensions/ScheduleTaskExtensions.cs b/src/Presentation/SmartStore.Web/Administration/Extensions/ScheduleTaskExtensions.cs new file mode 100644 index 0000000000..0949475ff0 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Extensions/ScheduleTaskExtensions.cs @@ -0,0 +1,85 @@ +using System; +using System.Web.Mvc; +using SmartStore.Admin.Models.Tasks; +using SmartStore.Core.Domain.Tasks; +using SmartStore.Services.Helpers; +using SmartStore.Services.Localization; +using SmartStore.Services.Tasks; +using SmartStore.Web.Framework; + +namespace SmartStore.Admin.Extensions +{ + public static class ScheduleTaskExtensions + { + public static ScheduleTaskModel ToScheduleTaskModel(this ScheduleTask task, ILocalizationService localization, IDateTimeHelper dateTimeHelper, UrlHelper urlHelper) + { + var now = DateTime.UtcNow; + + TimeSpan? dueIn = null; + if (task.NextRunUtc.HasValue) + { + dueIn = task.NextRunUtc.Value - now; + } + + var nextRunPretty = ""; + bool isOverdue = false; + if (dueIn.HasValue) + { + if (dueIn.Value.TotalSeconds > 0) + { + nextRunPretty = dueIn.Value.Prettify(); + } + else + { + nextRunPretty = localization.GetResource("Common.Waiting") + "..."; + isOverdue = true; + } + } + + var isRunning = task.IsRunning; + var lastStartOn = task.LastStartUtc.HasValue ? dateTimeHelper.ConvertToUserTime(task.LastStartUtc.Value, DateTimeKind.Utc) : (DateTime?)null; + var lastEndOn = task.LastEndUtc.HasValue ? dateTimeHelper.ConvertToUserTime(task.LastEndUtc.Value, DateTimeKind.Utc) : (DateTime?)null; + var lastSuccessOn = task.LastSuccessUtc.HasValue ? dateTimeHelper.ConvertToUserTime(task.LastSuccessUtc.Value, DateTimeKind.Utc) : (DateTime?)null; + var nextRunOn = task.NextRunUtc.HasValue ? dateTimeHelper.ConvertToUserTime(task.NextRunUtc.Value, DateTimeKind.Utc) : (DateTime?)null; + + var model = new ScheduleTaskModel + { + Id = task.Id, + Name = task.Name, + CronExpression = task.CronExpression, + CronDescription = CronExpression.GetFriendlyDescription(task.CronExpression), + Enabled = task.Enabled, + StopOnError = task.StopOnError, + LastStart = lastStartOn, + LastStartPretty = task.LastStartUtc.HasValue ? task.LastStartUtc.Value.RelativeFormat(true, "f") : "", + LastEnd = lastEndOn, + LastEndPretty = lastEndOn.HasValue ? lastEndOn.Value.ToString("G") : "", + LastSuccess = lastSuccessOn, + LastSuccessPretty = lastSuccessOn.HasValue ? lastSuccessOn.Value.ToString("G") : "", + NextRun = nextRunOn, + NextRunPretty = nextRunPretty, + LastError = task.LastError.EmptyNull(), + IsRunning = isRunning, + CancelUrl = urlHelper.Action("CancelJob", "ScheduleTask", new { id = task.Id }), + ExecuteUrl = urlHelper.Action("RunJob", "ScheduleTask", new { id = task.Id }), + EditUrl = urlHelper.Action("Edit", "ScheduleTask", new { id = task.Id }), + ProgressPercent = task.ProgressPercent, + ProgressMessage = task.ProgressMessage, + IsOverdue = isOverdue, + Duration = "" + }; + + var span = TimeSpan.Zero; + if (task.LastStartUtc.HasValue) + { + span = model.IsRunning ? now - task.LastStartUtc.Value : task.LastEndUtc.Value - task.LastStartUtc.Value; + if (span > TimeSpan.Zero) + { + model.Duration = span.ToString("g"); + } + } + + return model; + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AdminStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AdminStartupTask.cs index 13775dee76..c9f1b6ef7e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AdminStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AdminStartupTask.cs @@ -1,4 +1,7 @@ -using SmartStore.Core.Infrastructure; +using SmartStore.ComponentModel; +using SmartStore.Core.Infrastructure; +using SmartStore.Services.DataExchange.Csv; +using SmartStore.Services.DataExchange.Import; namespace SmartStore.Admin.Infrastructure { @@ -6,11 +9,9 @@ public class AdminStartupTask : IStartupTask { public void Execute() { - // codehint: sm-delete (Telerik internal localization works better for whatever reason) - ////set localization service for telerik - //Telerik.Web.Mvc.Infrastructure.DI.Current.Register( - // () => EngineContext.Current.Resolve()); - } + TypeConverterFactory.RegisterConverter(new CsvConfigurationConverter()); + TypeConverterFactory.RegisterConverter(new ColumnMapConverter()); + } public int Order { diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index c783737588..bee55cf932 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Linq; using AutoMapper; using SmartStore.Admin.Models.Blogs; using SmartStore.Admin.Models.Catalog; @@ -102,6 +103,7 @@ public void Execute() .ForMember(dest => dest.PhoneRequired, mo => mo.Ignore()) .ForMember(dest => dest.FaxEnabled, mo => mo.Ignore()) .ForMember(dest => dest.FaxRequired, mo => mo.Ignore()) + .ForMember(dest => dest.EmailMatch, mo => mo.Ignore()) .ForMember(dest => dest.CountryName, mo => mo.MapFrom(src => src.Country != null ? src.Country.Name : null)) .ForMember(dest => dest.StateProvinceName, mo => mo.MapFrom(src => src.StateProvince != null ? src.StateProvince.Name : null)); Mapper.CreateMap() @@ -115,7 +117,9 @@ public void Execute() .ForMember(dest => dest.RestrictedShippingMethods, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.NumberOfStates, mo => mo.MapFrom(src => src.StateProvinces != null ? src.StateProvinces.Count : 0)) - .ForMember(dest => dest.Locales, mo => mo.Ignore()); + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) + .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()); //state/provinces Mapper.CreateMap() .ForMember(dest => dest.DisplayOrder1, mo => mo.MapFrom(src => src.DisplayOrder)) @@ -125,16 +129,21 @@ public void Execute() .ForMember(dest => dest.Country, mo => mo.Ignore()); //language Mapper.CreateMap() - .ForMember(dest => dest.FlagFileNames, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableFlags, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableCultures, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableTwoLetterLanguageCodes, mo => mo.Ignore()) .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) - .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()); + .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) + .ForMember(dest => dest.FlagFileNames, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.LocaleStringResources, mo => mo.Ignore()); - //email account - Mapper.CreateMap() - .ForMember(dest => dest.IsDefaultEmailAccount, mo => mo.Ignore()) - .ForMember(dest => dest.SendTestEmailTo, mo => mo.Ignore()); - Mapper.CreateMap(); + //email account + Mapper.CreateMap() + .ForMember(dest => dest.IsDefaultEmailAccount, mo => mo.Ignore()) + .ForMember(dest => dest.SendTestEmailTo, mo => mo.Ignore()) + .ForMember(dest => dest.TestEmailShortErrorMessage, mo => mo.Ignore()) + .ForMember(dest => dest.TestEmailFullErrorMessage, mo => mo.Ignore()); + Mapper.CreateMap(); //message template Mapper.CreateMap() .ForMember(dest => dest.TokensTree, mo => mo.Ignore()) @@ -144,17 +153,20 @@ public void Execute() .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()); Mapper.CreateMap(); //queued email - Mapper.CreateMap() - .ForMember(dest => dest.EmailAccountName, mo => mo.MapFrom(src => src.EmailAccount != null ? src.EmailAccount.FriendlyName : string.Empty)) - .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) - .ForMember(dest => dest.SentOn, mo => mo.Ignore()); + Mapper.CreateMap() + .ForMember(dest => dest.EmailAccountName, mo => mo.MapFrom(src => src.EmailAccount != null ? src.EmailAccount.FriendlyName : string.Empty)) + .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) + .ForMember(dest => dest.SentOn, mo => mo.Ignore()) + .ForMember(dest => dest.AttachmentsCount, mo => mo.MapFrom(src => src.Attachments.Count)) + .ForMember(dest => dest.Attachments, mo => mo.MapFrom(src => src.Attachments.Select(x => new QueuedEmailModel.QueuedEmailAttachmentModel { Id = x.Id, Name = x.Name, MimeType = x.MimeType } ))); Mapper.CreateMap() .ForMember(dest=> dest.CreatedOnUtc, dt=> dt.Ignore()) .ForMember(dest => dest.SentOnUtc, mo => mo.Ignore()) .ForMember(dest => dest.EmailAccount, mo => mo.Ignore()) .ForMember(dest => dest.EmailAccountId, mo => mo.Ignore()) .ForMember(dest => dest.ReplyTo, mo => mo.Ignore()) - .ForMember(dest => dest.ReplyToName, mo => mo.Ignore()); + .ForMember(dest => dest.ReplyToName, mo => mo.Ignore()) + .ForMember(dest => dest.Attachments, mo => mo.Ignore()); //campaign Mapper.CreateMap() .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) @@ -171,12 +183,14 @@ public void Execute() .ForMember(dest => dest.Locales, mo => mo.Ignore()) .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableWidgetZones, mo => mo.Ignore()); + .ForMember(dest => dest.AvailableWidgetZones, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableTitleTags, mo => mo.Ignore()); Mapper.CreateMap(); //category Mapper.CreateMap() .ForMember(dest => dest.AvailableCategoryTemplates, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableDefaultViewModes, mo => mo.Ignore()) .ForMember(dest => dest.Locales, mo => mo.Ignore()) .ForMember(dest => dest.Breadcrumb, mo => mo.Ignore()) .ForMember(dest => dest.ParentCategoryBreadcrumb, mo => mo.Ignore()) @@ -188,7 +202,8 @@ public void Execute() .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) - .ForMember(dest => dest.UpdatedOn, mo => mo.Ignore()); + .ForMember(dest => dest.UpdatedOn, mo => mo.Ignore()) + .ForMember(dest => dest.GridPageSize, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.HasDiscountsApplied, mo => mo.Ignore()) .ForMember(dest => dest.CreatedOnUtc, mo => mo.Ignore()) @@ -204,7 +219,8 @@ public void Execute() .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) - .ForMember(dest => dest.UpdatedOn, mo => mo.Ignore()); + .ForMember(dest => dest.UpdatedOn, mo => mo.Ignore()) + .ForMember(dest => dest.GridPageSize, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.CreatedOnUtc, mo => mo.Ignore()) .ForMember(dest => dest.UpdatedOnUtc, mo => mo.Ignore()) @@ -234,6 +250,7 @@ public void Execute() .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) .ForMember(dest => dest.AvailableTaxCategories, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableMeasureUnits, mo => mo.Ignore()) .ForMember(dest => dest.PrimaryStoreCurrencyCode, mo => mo.Ignore()) .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) .ForMember(dest => dest.UpdatedOn, mo => mo.Ignore()) @@ -305,12 +322,16 @@ public void Execute() Mapper.CreateMap() .ForMember(dest => dest.ActivityLogTypeName, mo => mo.MapFrom(src => src.ActivityLogType.Name)) .ForMember(dest => dest.CustomerEmail, mo => mo.MapFrom(src => src.Customer.Email)) - .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()); - //currencies + .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) + .ForMember(dest => dest.IsSystemAccount, mo => mo.Ignore()) + .ForMember(dest => dest.SystemAccountName, mo => mo.Ignore()); + //currencies Mapper.CreateMap() .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) .ForMember(dest => dest.IsPrimaryExchangeRateCurrency, mo => mo.Ignore()) .ForMember(dest => dest.IsPrimaryStoreCurrency, mo => mo.Ignore()) + .ForMember(dest => dest.PrimaryStoreCurrencyStores, mo => mo.Ignore()) + .ForMember(dest => dest.PrimaryExchangeRateCurrencyStores, mo => mo.Ignore()) .ForMember(dest => dest.Locales, mo => mo.Ignore()) .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) @@ -327,7 +348,8 @@ public void Execute() // Measure unit Mapper.CreateMap() .ForMember(dest => dest.Locales, mo => mo.Ignore()); - Mapper.CreateMap(); + Mapper.CreateMap() + .ForMember(dest => dest.DisplayLocale, mo => mo.Ignore()); // ContentSlider slides Mapper.CreateMap() @@ -362,6 +384,7 @@ public void Execute() .ForMember(dest => dest.QuantityUnit, mo => mo.Ignore()) .ForMember(dest => dest.Product, mo => mo.Ignore()) .ForMember(dest => dest.AssignedPictureIds, mo => mo.Ignore()) + .ForMember(dest => dest.QuantityUnitId, mo => mo.Ignore()) .AfterMap((src, dest) => dest.SetAssignedPictureIds(src.AssignedPictureIds)); //measure weights @@ -377,16 +400,22 @@ public void Execute() Mapper.CreateMap(); //shipping methods Mapper.CreateMap() - .ForMember(dest => dest.Locales, mo => mo.Ignore()); - Mapper.CreateMap() - .ForMember(dest => dest.RestrictedCountries, mo => mo.Ignore()); + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.FilterConfigurationUrls, mo => mo.Ignore()); + Mapper.CreateMap() + .ForMember(dest => dest.RestrictedCountries, mo => mo.Ignore()); //plugins Mapper.CreateMap() .ForMember(dest => dest.ConfigurationUrl, mo => mo.Ignore()) .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) .ForMember(dest => dest.Locales, mo => mo.Ignore()) .ForMember(dest => dest.IconUrl, mo => mo.Ignore()) - .ForMember(dest => dest.ConfigurationRoute, mo => mo.Ignore()); + .ForMember(dest => dest.ConfigurationRoute, mo => mo.Ignore()) + .ForMember(dest => dest.LicenseUrl, mo => mo.Ignore()) + .ForMember(dest => dest.IsLicensable, mo => mo.Ignore()) + .ForMember(dest => dest.LicenseState, mo => mo.Ignore()) + .ForMember(dest => dest.TruncatedLicenseKey, mo => mo.Ignore()) + .ForMember(dest => dest.RemainingDemoUsageDays, mo => mo.Ignore()); //newsLetter subscriptions Mapper.CreateMap() .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) @@ -397,13 +426,19 @@ public void Execute() .ForMember(dest => dest.StoreId, mo => mo.Ignore()); //forums Mapper.CreateMap() - .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.SeName, mo => mo.MapFrom(src => src.GetSeName(0, true, false))) + .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) + .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) + .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) .ForMember(dest => dest.ForumModels, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.CreatedOnUtc, mo => mo.Ignore()) .ForMember(dest => dest.UpdatedOnUtc, mo => mo.Ignore()) .ForMember(dest => dest.Forums, mo => mo.Ignore()); Mapper.CreateMap() + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.SeName, mo => mo.MapFrom(src => src.GetSeName(0, true, false))) .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) .ForMember(dest => dest.ForumGroups, mo => mo.Ignore()); Mapper.CreateMap() @@ -475,7 +510,8 @@ public void Execute() Mapper.CreateMap(); //specification attributes Mapper.CreateMap() - .ForMember(dest => dest.Locales, mo => mo.Ignore()); + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.OptionCount, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.SpecificationAttributeOptions, mo => mo.Ignore()); Mapper.CreateMap() @@ -484,12 +520,14 @@ public void Execute() Mapper.CreateMap() .ForMember(dest => dest.SpecificationAttribute, mo => mo.Ignore()) .ForMember(dest => dest.ProductSpecificationAttributes, mo => mo.Ignore()); - //checkout attributes - Mapper.CreateMap() - .ForMember(dest => dest.AvailableTaxCategories, mo => mo.Ignore()) - .ForMember(dest => dest.AttributeControlTypeName, mo => mo.Ignore()) - .ForMember(dest => dest.Locales, mo => mo.Ignore()); - Mapper.CreateMap() + //checkout attributes + Mapper.CreateMap() + .ForMember(dest => dest.AvailableTaxCategories, mo => mo.Ignore()) + .ForMember(dest => dest.AttributeControlTypeName, mo => mo.Ignore()) + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()); + Mapper.CreateMap() .ForMember(dest => dest.AttributeControlType, mo => mo.Ignore()) .ForMember(dest => dest.CheckoutAttributeValues, mo => mo.Ignore()); Mapper.CreateMap() @@ -542,9 +580,13 @@ public void Execute() .ForMember(dest => dest.IsRecipientNotified, mo => mo.Ignore()) .ForMember(dest => dest.CreatedOnUtc, mo => mo.Ignore()); //stores - Mapper.CreateMap(); - Mapper.CreateMap(); - + Mapper.CreateMap() + .ForMember(dest => dest.AvailableCurrencies, mo => mo.Ignore()) + .ForMember(dest => dest.PrimaryStoreCurrencyName, mo => mo.Ignore()) + .ForMember(dest => dest.PrimaryExchangeRateCurrencyName, mo => mo.Ignore()); + Mapper.CreateMap() + .ForMember(dest => dest.PrimaryStoreCurrency, mo => mo.Ignore()) + .ForMember(dest => dest.PrimaryExchangeRateCurrency, mo => mo.Ignore()); //Settings Mapper.CreateMap() .ForMember(dest => dest.DefaultTaxAddress, mo => mo.Ignore()) @@ -579,9 +621,12 @@ public void Execute() Mapper.CreateMap() .ForMember(dest => dest.ActiveShippingRateComputationMethodSystemNames, mo => mo.Ignore()) .ForMember(dest => dest.ReturnValidOptionsIfThereAreAny, mo => mo.Ignore()); - Mapper.CreateMap() - .ForMember(dest => dest.AvailableDefaultViewModes, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableDeliveryTimes, mo => mo.Ignore()); + Mapper.CreateMap() + .ForMember(dest => dest.AvailableSubCategoryDisplayTypes, mo => mo.Ignore()) + .ForMember(dest => dest.AvailablePriceDisplayTypes, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableDefaultViewModes, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableDeliveryTimes, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableSortOrderModes, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.PageShareCode, mo => mo.Ignore()) .ForMember(dest => dest.DefaultProductRatingValue, mo => mo.Ignore()) @@ -593,10 +638,9 @@ public void Execute() .ForMember(dest => dest.DisplayTierPricesWithDiscounts, mo => mo.Ignore()) .ForMember(dest => dest.FileUploadMaximumSizeBytes, mo => mo.Ignore()) .ForMember(dest => dest.FileUploadAllowedExtensions, mo => mo.Ignore()) - .ForMember(dest => dest.ShowProductImagesInSearchAutoComplete, mo => mo.Ignore()) .ForMember(dest => dest.ProductSearchPageSize, mo => mo.Ignore()) - .ForMember(dest => dest.ManufacturersBlockItemsToDisplay, mo => mo.Ignore()) - .ForMember(dest => dest.MostRecentlyUsedCategoriesMaxSize, mo => mo.Ignore()); + .ForMember(dest => dest.MostRecentlyUsedCategoriesMaxSize, mo => mo.Ignore()) + .ForMember(dest => dest.MostRecentlyUsedManufacturersMaxSize, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.PrimaryStoreCurrencyCode, mo => mo.Ignore()) .ForMember(dest => dest.PointsForPurchases_OverrideForStore, mo => mo.Ignore()); @@ -605,24 +649,33 @@ public void Execute() .ForMember(dest => dest.GiftCards_Activated_OrderStatuses, mo => mo.Ignore()) .ForMember(dest => dest.GiftCards_Deactivated_OrderStatuses, mo => mo.Ignore()) .ForMember(dest => dest.PrimaryStoreCurrencyCode, mo => mo.Ignore()) + .ForMember(dest => dest.StoreCount, mo => mo.Ignore()) .ForMember(dest => dest.OrderIdent, mo => mo.Ignore()) .ForMember(dest => dest.Locales, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.MinimumOrderPlacementInterval, mo => mo.Ignore()) .ForMember(dest => dest.Id, mo => mo.Ignore()); - Mapper.CreateMap(); + Mapper.CreateMap() + .ForMember(dest => dest.AvailableNewsLetterSubscriptions, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableThirdPartyEmailHandOver, mo => mo.Ignore()) + .ForMember(dest => dest.Locales, mo => mo.Ignore()); Mapper.CreateMap() - .ForMember(dest => dest.MoveItemsFromWishlistToCart, mo => mo.Ignore()); - Mapper.CreateMap() - .ForMember(dest => dest.PicturesStoredIntoDatabase, mo => mo.Ignore()) - .ForMember(dest => dest.AvailablePictureZoomTypes, mo => mo.Ignore()); + .ForMember(dest => dest.Id, mo => mo.Ignore()) + .ForMember(dest => dest.MoveItemsFromWishlistToCart, mo => mo.Ignore()) + .ForMember(dest => dest.ShowItemsFromWishlistToCartButton, mo => mo.Ignore()); + Mapper.CreateMap() + .ForMember(dest => dest.PicturesStoredIntoDatabase, mo => mo.Ignore()) + .ForMember(dest => dest.AvailablePictureZoomTypes, mo => mo.Ignore()); Mapper.CreateMap() //.ForMember(dest => dest.DefaultPictureZoomEnabled, mo => mo.Ignore()) .ForMember(dest => dest.DefaultImageQuality, mo => mo.Ignore()) .ForMember(dest => dest.MultipleThumbDirectories, mo => mo.Ignore()) - .ForMember(dest => dest.AutoCompleteSearchThumbPictureSize, mo => mo.Ignore()); - Mapper.CreateMap(); - Mapper.CreateMap() + .ForMember(dest => dest.VariantValueThumbPictureSize, mo => mo.Ignore()); + Mapper.CreateMap() + .ForMember(dest => dest.AvailableCustomerNumberMethods, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableCustomerNumberVisibilities, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableRegisterCustomerRoles, mo => mo.Ignore()); + Mapper.CreateMap() .ForMember(dest => dest.HashedPasswordFormat, mo => mo.Ignore()) .ForMember(dest => dest.PasswordMinLength, mo => mo.Ignore()) .ForMember(dest => dest.AvatarMaximumSizeBytes, mo => mo.Ignore()) diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/PreviewModeFilter.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/PreviewModeFilter.cs index 1d0c0ab36a..5cce71034d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/PreviewModeFilter.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/PreviewModeFilter.cs @@ -6,7 +6,7 @@ using SmartStore.Core; using SmartStore.Services; using SmartStore.Services.Security; -using SmartStore.Web.Framework.Themes; +using SmartStore.Web.Framework.Theming; using SmartStore.Web.Framework.UI; namespace SmartStore.Admin.Infrastructure diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/RouteProvider.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/RouteProvider.cs index aa5f1fb80b..8c34ebc028 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/RouteProvider.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/RouteProvider.cs @@ -4,7 +4,7 @@ using System.Web; using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.Admin.Infrastructure { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Affiliates/AffiliateModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Affiliates/AffiliateModel.cs index f7630a7af5..e579c65822 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Affiliates/AffiliateModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Affiliates/AffiliateModel.cs @@ -3,7 +3,7 @@ using SmartStore.Admin.Models.Common; using SmartStore.Admin.Validators.Affiliates; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Affiliates { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogCommentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogCommentModel.cs index 852d4b691d..f042f334b7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogCommentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogCommentModel.cs @@ -1,7 +1,7 @@ using System; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Blogs { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogPostModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogPostModel.cs index 354f094acc..6eeaeef940 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogPostModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Blogs/BlogPostModel.cs @@ -6,7 +6,7 @@ using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.Blogs; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Blogs { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/BulkEditListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/BulkEditListModel.cs index 711cc2d322..b1e21d83b2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/BulkEditListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/BulkEditListModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { @@ -11,6 +11,7 @@ public BulkEditListModel() { AvailableCategories = new List(); AvailableManufacturers = new List(); + AvailableStores = new List(); AvailableProductTypes = new List(); } @@ -24,11 +25,17 @@ public BulkEditListModel() [SmartResourceDisplayName("Admin.Catalog.BulkEdit.List.SearchManufacturer")] public int SearchManufacturerId { get; set; } + [SmartResourceDisplayName("Admin.Common.Store.SearchFor")] + public int SearchStoreId { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] public int SearchProductTypeId { get; set; } public IList AvailableProductTypes { get; set; } + + public int GridPageSize { get; set; } public IList AvailableCategories { get; set; } public IList AvailableManufacturers { get; set; } + public IList AvailableStores { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/BulkEditProductModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/BulkEditProductModel.cs index 92ce436cba..f2e3ec65b5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/BulkEditProductModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/BulkEditProductModel.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryListModel.cs index 5778691c57..7714b57e57 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryListModel.cs @@ -1,20 +1,28 @@ -using System.Web.Mvc; +using System.Collections.Generic; +using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; -using Telerik.Web.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { public class CategoryListModel : ModelBase { + public CategoryListModel() + { + AvailableStores = new List(); + } + [SmartResourceDisplayName("Admin.Catalog.Categories.List.SearchCategoryName")] [AllowHtml] public string SearchCategoryName { get; set; } - /// codehint: sm-add [SmartResourceDisplayName("Admin.Catalog.Categories.List.SearchAlias")] public string SearchAlias { get; set; } - public GridModel Categories { get; set; } + [SmartResourceDisplayName("Admin.Common.Store.SearchFor")] + public int SearchStoreId { get; set; } + public IList AvailableStores { get; set; } + + public int GridPageSize { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryModel.cs index 48c683a880..49a10c1d46 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryModel.cs @@ -9,9 +9,8 @@ using SmartStore.Core.Domain.Discounts; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using Telerik.Web.Mvc; -using Telerik.Web.Mvc.UI; namespace SmartStore.Admin.Models.Catalog { @@ -22,21 +21,31 @@ public CategoryModel() { if (PageSize < 1) { - PageSize = 5; + PageSize = 12; } + Locales = new List(); AvailableCategoryTemplates = new List(); AvailableDefaultViewModes = new List(); } - [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.Name")] + public int GridPageSize { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.Name")] [AllowHtml] public string Name { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.FullName")] + public string FullName { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.Description")] [AllowHtml] public string Description { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.BottomDescription")] + [AllowHtml] + public string BottomDescription { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.Alias")] public string Alias { get; set; } @@ -62,7 +71,6 @@ public CategoryModel() public string SeName { get; set; } [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.Parent")] - // codehint: sm-edit public int? ParentCategoryId { get; set; } [UIHint("Picture")] @@ -159,35 +167,6 @@ public class CategoryProductModel : EntityModelBase public int DisplayOrder1 { get; set; } } - public class AddCategoryProductModel : ModelBase - { - public AddCategoryProductModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableProductTypes = new List(); - } - public GridModel Products { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableProductTypes { get; set; } - - public int CategoryId { get; set; } - - public int[] SelectedProductIds { get; set; } - } - #endregion } @@ -199,9 +178,16 @@ public class CategoryLocalizedModel : ILocalizedModelLocal [AllowHtml] public string Name { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.FullName")] + public string FullName { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.Description")] [AllowHtml] - public string Description {get;set;} + public string Description { get;set; } + + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.BottomDescription")] + [AllowHtml] + public string BottomDescription { get; set; } [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.MetaKeywords")] [AllowHtml] diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryTreeModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryTreeModel.cs new file mode 100644 index 0000000000..d67c00401e --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CategoryTreeModel.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.Catalog +{ + public class CategoryTreeModel : ModelBase + { + public CategoryTreeModel() + { + AvailableStores = new List(); + } + + [SmartResourceDisplayName("Admin.Common.Store.SearchFor")] + public int SearchStoreId { get; set; } + public IList AvailableStores { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CopyProductModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CopyProductModel.cs index 93e0a3c141..74ad84fbb7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CopyProductModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/CopyProductModel.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerListModel.cs index 9fedd4f0bd..81163d6df0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerListModel.cs @@ -1,16 +1,20 @@ -using System.Web.Mvc; +using System.Collections.Generic; +using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; -using Telerik.Web.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { - public class ManufacturerListModel : ModelBase + public class ManufacturerListModel : ModelBase { [SmartResourceDisplayName("Admin.Catalog.Manufacturers.List.SearchManufacturerName")] [AllowHtml] public string SearchManufacturerName { get; set; } - public GridModel Manufacturers { get; set; } + [SmartResourceDisplayName("Admin.Common.Store.SearchFor")] + public int SearchStoreId { get; set; } + public IList AvailableStores { get; set; } + + public int GridPageSize { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerModel.cs index ab06e86043..6ec6dac6ca 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerModel.cs @@ -7,12 +7,11 @@ using SmartStore.Admin.Validators.Catalog; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; -using Telerik.Web.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { - [Validator(typeof(ManufacturerValidator))] + [Validator(typeof(ManufacturerValidator))] public class ManufacturerModel : EntityModelBase, ILocalizedModel { public ManufacturerModel() @@ -21,11 +20,14 @@ public ManufacturerModel() { PageSize = 5; } + Locales = new List(); AvailableManufacturerTemplates = new List(); } - [SmartResourceDisplayName("Admin.Catalog.Manufacturers.Fields.Name")] + public int GridPageSize { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Manufacturers.Fields.Name")] [AllowHtml] public string Name { get; set; } @@ -127,35 +129,6 @@ public class ManufacturerProductModel : EntityModelBase public int DisplayOrder1 { get; set; } } - public class AddManufacturerProductModel : ModelBase - { - public AddManufacturerProductModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableProductTypes = new List(); - } - public GridModel Products { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableProductTypes { get; set; } - - public int ManufacturerId { get; set; } - - public int[] SelectedProductIds { get; set; } - } - #endregion } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/PrintableProductsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/PrintableProductsModel.cs deleted file mode 100644 index a41e920a8b..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/PrintableProductsModel.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel.DataAnnotations; -using System.Web.Mvc; -using AutoMapper; -using FluentValidation.Attributes; -using SmartStore.Admin.Models.Customers; -using SmartStore.Admin.Models.Stores; -using SmartStore.Admin.Validators.Catalog; -using SmartStore.Core; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Discounts; -using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; -using Telerik.Web.Mvc; - -namespace SmartStore.Admin.Models.Catalog -{ - public partial class PrintableProductsModel : EntityModelBase - { - public PrintableProductsModel() - { - MerchantCompanyInfo = new CompanyInformationSettings(); - MerchantContactData = new ContactDataSettings(); - Products = new List(); - } - - public bool PdfMode { get; set; } - public string PrintLogoUrl { get; set; } - public string StoreName { get; set; } - public string StoreUrl { get; set; } - - public CompanyInformationSettings MerchantCompanyInfo { get; set; } - public ContactDataSettings MerchantContactData { get; set; } - public IList Products { get; set; } - } - - public partial class PrintableProductModel : EntityModelBase - { - public PrintableProductModel() - { - SpecificationAttributes = new List(); - BundledItems = new List(); - AssociatedProducts = new List(); - } - - public string Name { get; set; } - public string ShortDescription { get; set; } - public string FullDescription { get; set; } - public string PictureUrl { get; set; } - - public string Sku { get; set; } - public string Price { get; set; } - public string Manufacturer { get; set; } - public string Weight { get; set; } - public string Length { get; set; } - public string Width { get; set; } - public string Height { get; set; } - - public IList SpecificationAttributes { get; set; } - - public ProductType ProductType { get; set; } - public IList BundledItems { get; set; } - public IList AssociatedProducts { get; set; } - } - - public partial class PrintableProductSpecificationModel : ModelBase - { - public int SpecificationAttributeId { get; set; } - - public string SpecificationAttributeName { get; set; } - - public string SpecificationAttributeOption { get; set; } - } -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductAttributeModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductAttributeModel.cs index 85eacf1b51..43ab63861b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductAttributeModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductAttributeModel.cs @@ -4,7 +4,7 @@ using SmartStore.Admin.Validators.Catalog; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductBundleItemModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductBundleItemModel.cs index c48ccd8e3e..2499893d36 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductBundleItemModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductBundleItemModel.cs @@ -4,7 +4,7 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductListModel.cs index 2430107ebb..a93008ad9c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductListModel.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; -using Telerik.Web.Mvc; +using SmartStore.Web.Framework.Modelling; +using Telerik.Web.Mvc; namespace SmartStore.Admin.Models.Catalog { @@ -15,6 +15,7 @@ public ProductListModel() AvailableStores = new List(); AvailableProductTypes = new List(); } + public GridModel Products { get; set; } [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] @@ -24,21 +25,32 @@ public ProductListModel() [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] public int SearchCategoryId { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchWithoutCategories")] + public bool? SearchWithoutCategories { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] public int SearchManufacturerId { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchWithoutManufacturers")] + public bool? SearchWithoutManufacturers { get; set; } + [SmartResourceDisplayName("Admin.Common.Store.SearchFor")] public int SearchStoreId { get; set; } [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] public int SearchProductTypeId { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchIsPublished")] + public bool? SearchIsPublished { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchHomePageProducts")] + public bool? SearchHomePageProducts { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Products.List.GoDirectlyToSku")] [AllowHtml] public string GoDirectlyToSku { get; set; } public bool DisplayProductPictures { get; set; } - public bool DisplayPdfExport { get; set; } public int GridPageSize { get; set; } public IList AvailableCategories { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs index 309911d728..d24020eaea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Web.Mvc; -using AutoMapper; using FluentValidation.Attributes; using SmartStore.Admin.Models.Customers; using SmartStore.Admin.Models.Stores; @@ -10,12 +9,11 @@ using SmartStore.Core.Domain.Discounts; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; -using Telerik.Web.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { - [Validator(typeof(ProductValidator))] + [Validator(typeof(ProductValidator))] public class ProductModel : TabbableModel, ILocalizedModel { public ProductModel() @@ -84,6 +82,9 @@ public ProductModel() [SmartResourceDisplayName("Admin.Catalog.Products.Fields.ShowOnHomePage")] public bool ShowOnHomePage { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Products.Fields.HomePageDisplayOrder")] + public int HomePageDisplayOrder { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Products.Fields.MetaKeywords")] [AllowHtml] public string MetaKeywords { get; set; } @@ -139,7 +140,7 @@ public ProductModel() [SmartResourceDisplayName("Admin.Catalog.Products.Fields.Download")] [UIHint("Download")] - public int DownloadId { get; set; } + public int? DownloadId { get; set; } [SmartResourceDisplayName("Admin.Catalog.Products.Fields.UnlimitedDownloads")] public bool UnlimitedDownloads { get; set; } @@ -158,7 +159,7 @@ public ProductModel() [SmartResourceDisplayName("Admin.Catalog.Products.Fields.SampleDownload")] [UIHint("Download")] - public int SampleDownloadId { get; set; } + public int? SampleDownloadId { get; set; } [SmartResourceDisplayName("Admin.Catalog.Products.Fields.HasUserAgreement")] public bool HasUserAgreement { get; set; } @@ -392,8 +393,7 @@ public AddProductSpecificationAttributeModel() { AvailableAttributes = new List(); AvailableOptions = new List(); - - AllowFiltering = true; // codehint: sm-add + AllowFiltering = true; } [SmartResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.SpecificationAttribute")] @@ -485,43 +485,6 @@ public class RelatedProductModel : EntityModelBase public bool Product2Published { get; set; } } - public class AddRelatedProductModel : ModelBase - { - public AddRelatedProductModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableStores = new List(); - AvailableProductTypes = new List(); - } - public GridModel Products { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] - public int SearchStoreId { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableStores { get; set; } - public IList AvailableProductTypes { get; set; } - - public int ProductId { get; set; } - - public int[] SelectedProductIds { get; set; } - } - public partial class AssociatedProductModel : EntityModelBase { [SmartResourceDisplayName("Admin.Catalog.Products.AssociatedProducts.Fields.Product")] @@ -540,37 +503,6 @@ public partial class AssociatedProductModel : EntityModelBase [SmartResourceDisplayName("Admin.Catalog.Products.Fields.Published")] public bool Published { get; set; } } - public partial class AddAssociatedProductModel : ModelBase - { - public AddAssociatedProductModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableStores = new List(); - AvailableProductTypes = new List(); - } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] - public int SearchStoreId { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableStores { get; set; } - public IList AvailableProductTypes { get; set; } - - public int ProductId { get; set; } - - public int[] SelectedProductIds { get; set; } - } public partial class BundleItemModel : EntityModelBase { @@ -601,39 +533,6 @@ public partial class BundleItemModel : EntityModelBase [SmartResourceDisplayName("Admin.Catalog.Products.BundleItems.Fields.Published")] public bool Published { get; set; } } - public partial class AddBundleItemModel : ModelBase - { - public AddBundleItemModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableStores = new List(); - AvailableProductTypes = new List(); - } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] - public int SearchStoreId { get; set; } - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableStores { get; set; } - public IList AvailableProductTypes { get; set; } - - public int ProductId { get; set; } - public bool IsPerItemPricing { get; set; } - public bool IsPerItemShipping { get; set; } - - public int[] SelectedProductIds { get; set; } - } public class CrossSellProductModel : EntityModelBase { @@ -652,42 +551,6 @@ public class CrossSellProductModel : EntityModelBase [SmartResourceDisplayName("Admin.Catalog.Products.Fields.Published")] public bool Product2Published { get; set; } } - public class AddCrossSellProductModel : ModelBase - { - public AddCrossSellProductModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableStores = new List(); - AvailableProductTypes = new List(); - } - public GridModel Products { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] - public int SearchStoreId { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableStores { get; set; } - public IList AvailableProductTypes { get; set; } - - public int ProductId { get; set; } - - public int[] SelectedProductIds { get; set; } - } public class TierPriceModel : EntityModelBase { @@ -765,6 +628,7 @@ public ProductVariantAttributeValueModel() Locales = new List(); } + public int ProductId { get; set; } public int ProductVariantAttributeId { get; set; } [SmartResourceDisplayName("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.Alias")] @@ -813,41 +677,6 @@ public ProductVariantAttributeValueModel() public string QuantityInfo { get; set; } public IList Locales { get; set; } - - public class AddProductLinkageModel : ModelBase - { - public AddProductLinkageModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableStores = new List(); - AvailableProductTypes = new List(); - } - public GridModel Products { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] - public int SearchStoreId { get; set; } - - [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableStores { get; set; } - public IList AvailableProductTypes { get; set; } - - public int ProductId { get; set; } - } } public class ProductVariantAttributeValueLocalizedModel : ILocalizedModelLocal diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductReviewListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductReviewListModel.cs index 0f8039e536..a1ac8ed85d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductReviewListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductReviewListModel.cs @@ -1,7 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductReviewModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductReviewModel.cs index 84810ee609..52709afd2b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductReviewModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductReviewModel.cs @@ -3,7 +3,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Catalog; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductSpecificationAttributeModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductSpecificationAttributeModel.cs index f192c5b793..caaa625b14 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductSpecificationAttributeModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductSpecificationAttributeModel.cs @@ -4,7 +4,7 @@ using System.Web.Mvc; using SmartStore.Core.Domain.Catalog; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductTagModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductTagModel.cs index 87ed517628..45af857dcd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductTagModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductTagModel.cs @@ -4,7 +4,7 @@ using SmartStore.Admin.Validators.Catalog; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductVariantAttributeCombinationModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductVariantAttributeCombinationModel.cs index 9584941a86..f7b0a9cccc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductVariantAttributeCombinationModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductVariantAttributeCombinationModel.cs @@ -3,7 +3,7 @@ using System.Web.Mvc; using SmartStore.Core.Domain.Catalog; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/SpecificationAttributeModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/SpecificationAttributeModel.cs index b7617cc450..9daefad70a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/SpecificationAttributeModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/SpecificationAttributeModel.cs @@ -4,7 +4,7 @@ using SmartStore.Admin.Validators.Catalog; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { @@ -23,9 +23,10 @@ public SpecificationAttributeModel() [SmartResourceDisplayName("Admin.Catalog.Attributes.SpecificationAttributes.Fields.DisplayOrder")] public int DisplayOrder {get;set;} - public IList Locales { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Attributes.SpecificationAttributes.OptionsCount")] + public int OptionCount { get; set; } } public class SpecificationAttributeLocalizedModel : ILocalizedModelLocal diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/SpecificationAttributeOptionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/SpecificationAttributeOptionModel.cs index 28d2c7a8f7..3057bf6013 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/SpecificationAttributeOptionModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/SpecificationAttributeOptionModel.cs @@ -4,7 +4,7 @@ using SmartStore.Admin.Validators.Catalog; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Cms/RenderWidgetModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Cms/RenderWidgetModel.cs deleted file mode 100644 index 5591035fc2..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Models/Cms/RenderWidgetModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Web.Routing; -using SmartStore.Web.Framework.Mvc; - -namespace SmartStore.Admin.Models.Cms -{ - public class RenderWidgetModel : ModelBase - { - public string ActionName { get; set; } - public string ControllerName { get; set; } - public RouteValueDictionary RouteValues { get; set; } - } -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Cms/WidgetModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Cms/WidgetModel.cs index 42e37f7cef..230e0237c8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Cms/WidgetModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Cms/WidgetModel.cs @@ -1,6 +1,6 @@ using SmartStore.Core; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Plugins; namespace SmartStore.Admin.Models.Cms { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/AddressModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Common/AddressModel.cs index fb022634dc..791583c6da 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/AddressModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Common/AddressModel.cs @@ -3,7 +3,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Common; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/CheckUpdateResult.cs b/src/Presentation/SmartStore.Web/Administration/Models/Common/CheckUpdateResult.cs new file mode 100644 index 0000000000..b41c9d0bed --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Common/CheckUpdateResult.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.Common +{ + [Serializable] + public class CheckUpdateResult : ModelBase + { + public bool UpdateAvailable { get; set; } + public string CurrentVersion { get; set; } + public string LanguageCode { get; set; } + + public string Version { get; set; } + public string FullName { get; set; } + public string ReleaseNotes { get; set; } + public string InfoUrl { get; set; } + public string DownloadUrl { get; set; } + public DateTime ReleaseDateUtc { get; set; } + public bool IsStable { get; set; } + public bool AutoUpdatePossible { get; set; } + public string AutoUpdatePackageUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/FeedItemModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Common/FeedItemModel.cs index 98c7c48b2f..a296975594 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/FeedItemModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Common/FeedItemModel.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Web; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/GenericAttributeModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Common/GenericAttributeModel.cs index 0088b497ab..762cd7e270 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/GenericAttributeModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Common/GenericAttributeModel.cs @@ -2,7 +2,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Localization; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/LanguageSelectorModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Common/LanguageSelectorModel.cs index d6b181ac0d..e843ced741 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/LanguageSelectorModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Common/LanguageSelectorModel.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using SmartStore.Admin.Models.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/MaintenanceModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Common/MaintenanceModel.cs index 17653a87bf..c676e8731a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/MaintenanceModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Common/MaintenanceModel.cs @@ -1,7 +1,6 @@ using System; -using System.ComponentModel.DataAnnotations; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Common { @@ -16,15 +15,11 @@ public MaintenanceModel() public DeleteGuestsModel DeleteGuests { get; set; } public DeleteExportedFilesModel DeleteExportedFiles { get; set; } - - // codehint: sm-add public DeleteImageCacheModel DeleteImageCache { get; set; } - // codehint: sm-add [SmartResourceDisplayName("Admin.System.Maintenance.SqlQuery")] public string SqlQuery { get; set; } - #region Nested classes public class DeleteGuestsModel : ModelBase @@ -50,9 +45,9 @@ public class DeleteExportedFilesModel : ModelBase public DateTime? EndDate { get; set; } public int? NumberOfDeletedFiles { get; set; } + public int? NumberOfDeletedFolders { get; set; } } - // codehint: sm-add public class DeleteImageCacheModel : ModelBase { [SmartResourceDisplayName("Admin.System.Maintenance.DeleteImageCache.FileCount")] @@ -61,6 +56,7 @@ public class DeleteImageCacheModel : ModelBase [SmartResourceDisplayName("Admin.System.Maintenance.DeleteImageCache.TotalSize")] public string TotalSize { get; set; } } + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/SystemInfoModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Common/SystemInfoModel.cs index b33f07677a..4d0cc0fc4d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/SystemInfoModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Common/SystemInfoModel.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Utilities; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Common { @@ -43,12 +44,22 @@ public SystemInfoModel() public IList LoadedAssemblies { get; set; } [SmartResourceDisplayName("Admin.System.SystemInfo.DatabaseSize")] - public double DatabaseSize { get; set; } + public long DatabaseSize { get; set; } public string DatabaseSizeString { get { - return (DatabaseSize == 0.0 ? "" : "{0:0.00} MB".FormatWith(DatabaseSize)); + return (DatabaseSize == 0 ? "" : Prettifier.BytesToString(DatabaseSize)); + } + } + + [SmartResourceDisplayName("Admin.System.SystemInfo.UsedMemorySize")] + public long UsedMemorySize { get; set; } + public string UsedMemorySizeString + { + get + { + return Prettifier.BytesToString(UsedMemorySize); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/SystemWarningModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Common/SystemWarningModel.cs index d06820c9c1..362c9f6aa7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/SystemWarningModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Common/SystemWarningModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordListModel.cs deleted file mode 100644 index 0a26749535..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordListModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Web.Mvc; -using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; - -namespace SmartStore.Admin.Models.Common -{ - public partial class UrlRecordListModel : ModelBase - { - [SmartResourceDisplayName("Admin.System.SeNames.Name")] - [AllowHtml] - public string SeName { get; set; } - } -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordModel.cs deleted file mode 100644 index 6a5a5fb235..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordModel.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Web.Mvc; -using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; - -namespace SmartStore.Admin.Models.Common -{ - public partial class UrlRecordModel : EntityModelBase - { - [SmartResourceDisplayName("Admin.System.SeNames.Name")] - [AllowHtml] - public string Name { get; set; } - - [SmartResourceDisplayName("Admin.System.SeNames.EntityId")] - public int EntityId { get; set; } - - [SmartResourceDisplayName("Admin.System.SeNames.EntityName")] - public string EntityName { get; set; } - - [SmartResourceDisplayName("Admin.System.SeNames.IsActive")] - public bool IsActive { get; set; } - - [SmartResourceDisplayName("Admin.System.SeNames.Language")] - public string Language { get; set; } - } -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Customers/BestCustomerReportLineModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Customers/BestCustomerReportLineModel.cs index 9eaf25efc8..27b8049cd5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Customers/BestCustomerReportLineModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Customers/BestCustomerReportLineModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Customers { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Customers/BestCustomersReportModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Customers/BestCustomersReportModel.cs index d8338e7af6..d31bbb72eb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Customers/BestCustomersReportModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Customers/BestCustomersReportModel.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Customers { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerAddressModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerAddressModel.cs index 6da5397835..9df3e3345a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerAddressModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerAddressModel.cs @@ -1,5 +1,5 @@ using SmartStore.Admin.Models.Common; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Customers { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerListModel.cs index 841561fd2a..7348b9dc14 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerListModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using Telerik.Web.Mvc; namespace SmartStore.Admin.Models.Customers diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerModel.cs index 904a8e30de..e8b2c0a8dc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerModel.cs @@ -5,7 +5,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Customers; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Customers { @@ -62,6 +62,12 @@ public CustomerModel() [AllowHtml] public string Company { get; set; } + public bool CustomerNumberEnabled { get; set; } + [SmartResourceDisplayName("Account.Fields.CustomerNumber")] + [AllowHtml] + public string CustomerNumber { get; set; } + + public bool StreetAddressEnabled { get; set; } [SmartResourceDisplayName("Admin.Customers.Customers.Fields.StreetAddress")] [AllowHtml] @@ -102,12 +108,6 @@ public CustomerModel() [AllowHtml] public string Fax { get; set; } - - - - - - [SmartResourceDisplayName("Admin.Customers.Customers.Fields.AdminComment")] [AllowHtml] public string AdminComment { get; set; } @@ -122,8 +122,6 @@ public CustomerModel() public int AffiliateId { get; set; } public string AffiliateFullName { get; set; } - - //time zone [SmartResourceDisplayName("Admin.Customers.Customers.Fields.TimeZoneId")] [AllowHtml] @@ -133,10 +131,6 @@ public CustomerModel() public IList AvailableTimeZones { get; set; } - - - - //EU VAT [SmartResourceDisplayName("Admin.Customers.Customers.Fields.VatNumber")] [AllowHtml] @@ -146,10 +140,6 @@ public CustomerModel() public bool DisplayVatNumber { get; set; } - - - - //registration date [SmartResourceDisplayName("Admin.Customers.Customers.Fields.CreatedOn")] public DateTime CreatedOn { get; set; } @@ -164,7 +154,6 @@ public CustomerModel() [SmartResourceDisplayName("Admin.Customers.Customers.Fields.LastVisitedPage")] public string LastVisitedPage { get; set; } - //customer roles [SmartResourceDisplayName("Admin.Customers.Customers.Fields.CustomerRoles")] public string CustomerRoleNames { get; set; } @@ -172,11 +161,6 @@ public CustomerModel() public int[] SelectedCustomerRoleIds { get; set; } public bool AllowManagingCustomerRoles { get; set; } - - - - - //reward points history public bool DisplayRewardPointsHistory { get; set; } @@ -222,7 +206,7 @@ public class RewardPointsHistoryModel : EntityModelBase [AllowHtml] public string Message { get; set; } - [SmartResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.Date")] + [SmartResourceDisplayName("Common.CreatedOn")] public DateTime CreatedOn { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerReportsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerReportsModel.cs index 5fd11e6497..cb0acb42aa 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerReportsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerReportsModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Customers { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerRoleModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerRoleModel.cs index c9b0b65876..f614ffd017 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerRoleModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Customers/CustomerRoleModel.cs @@ -4,7 +4,7 @@ using SmartStore.Admin.Validators.Customers; using SmartStore.Core.Domain.Tax; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Customers { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Customers/OnlineCustomerModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Customers/OnlineCustomerModel.cs index 071012c139..559ae53c41 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Customers/OnlineCustomerModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Customers/OnlineCustomerModel.cs @@ -1,6 +1,6 @@ using System; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Customers { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Customers/RegisteredCustomerReportLineModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Customers/RegisteredCustomerReportLineModel.cs index d2b800c9a2..79e192ea8c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Customers/RegisteredCustomerReportLineModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Customers/RegisteredCustomerReportLineModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Customers { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/CsvConfigurationModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/CsvConfigurationModel.cs new file mode 100644 index 0000000000..63aa1aaf29 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/CsvConfigurationModel.cs @@ -0,0 +1,64 @@ +using System; +using System.Text.RegularExpressions; +using FluentValidation.Attributes; +using SmartStore.Admin.Validators.DataExchange; +using SmartStore.Services.DataExchange.Csv; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.DataExchange +{ + [Validator(typeof(CsvConfigurationValidator))] + public class CsvConfigurationModel : ModelBase, ICloneable + { + public CsvConfigurationModel() + { + } + + public CsvConfigurationModel(CsvConfiguration config) + { + QuoteAllFields = config.QuoteAllFields; + TrimValues = config.TrimValues; + SupportsMultiline = config.SupportsMultiline; + Delimiter = Regex.Escape(config.Delimiter.ToString()); + Quote = Regex.Escape(config.Quote.ToString()); + Escape = Regex.Escape(config.Escape.ToString()); + } + + [SmartResourceDisplayName("Admin.DataExchange.Csv.QuoteAllFields")] + public bool QuoteAllFields { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Csv.TrimValues")] + public bool TrimValues { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Csv.SupportsMultiline")] + public bool SupportsMultiline { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Csv.Delimiter")] + public string Delimiter { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Csv.Quote")] + public string Quote { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Csv.Escape")] + public string Escape { get; set; } + + object ICloneable.Clone() + { + return this.Clone(); + } + + public CsvConfiguration Clone() + { + var config = new CsvConfiguration(); + config.QuoteAllFields = QuoteAllFields; + config.TrimValues = TrimValues; + config.SupportsMultiline = SupportsMultiline; + config.Delimiter = Delimiter.ToChar(true); + config.Quote = Quote.ToChar(true); + config.Escape = Escape.ToChar(true); + + return config; + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs new file mode 100644 index 0000000000..f5cb8f82c6 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; +using FluentValidation.Attributes; +using SmartStore.Admin.Validators.DataExchange; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.DataExchange +{ + [Validator(typeof(ExportDeploymentValidator))] + public class ExportDeploymentModel : EntityModelBase + { + public int ProfileId { get; set; } + public bool CreateZip { get; set; } + public string PublicFolderUrl { get; set; } + + [SmartResourceDisplayName("Common.Image")] + public string ThumbnailUrl { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.Name")] + public string Name { get; set; } + + [SmartResourceDisplayName("Common.Enabled")] + public bool Enabled { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.DeploymentType")] + public ExportDeploymentType DeploymentType { get; set; } + public List AvailableDeploymentTypes { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.DeploymentType")] + public string DeploymentTypeName { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.Username")] + public string Username { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.Password")] + [DataType(DataType.Password)] + public string Password { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.Url")] + public string Url { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.HttpTransmissionType")] + public ExportHttpTransmissionType HttpTransmissionType { get; set; } + public List AvailableHttpTransmissionTypes { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.FileSystemPath")] + public string FileSystemPath { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.SubFolder")] + public string SubFolder { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.EmailAddresses")] + public string EmailAddresses { get; set; } + public string SerializedEmailAddresses { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.EmailSubject")] + public string EmailSubject { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.EmailAccountId")] + public int EmailAccountId { get; set; } + public List AvailableEmailAccounts { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.PassiveMode")] + public bool PassiveMode { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.UseSsl")] + public bool UseSsl { get; set; } + + public LastResultInfo LastResult { get; set; } + + public int FileCount { get; set; } + + public class LastResultInfo + { + public DateTime Execution { get; set; } + public string ExecutionPretty { get; set; } + public string Error { get; set; } + + public bool Succeeded + { + get { return Error.IsEmpty(); } + } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs new file mode 100644 index 0000000000..224f6671c6 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Web.Framework; + +namespace SmartStore.Admin.Models.DataExchange +{ + public class ExportFilterModel + { + #region All entity types + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.StoreId")] + public int? StoreId { get; set; } + public List AvailableStores { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.CreatedFrom")] + public DateTime? CreatedFrom { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.CreatedTo")] + public DateTime? CreatedTo { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.CustomerRoleIds")] + public int[] CustomerRoleIds { get; set; } + public List AvailableCustomerRoles { get; set; } + + public List AvailableCountries { get; set; } + + #endregion + + #region Product + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.IsPublished")] + public bool? IsPublished { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.ProductType")] + public ProductType? ProductType { get; set; } + public List AvailableProductTypes { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.IdMinimum")] + public int? IdMinimum { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.IdMaximum")] + public int? IdMaximum { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.PriceMinimum")] + public decimal? PriceMinimum { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.PriceMaximum")] + public decimal? PriceMaximum { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.AvailabilityMinimum")] + public int? AvailabilityMinimum { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.AvailabilityMaximum")] + public int? AvailabilityMaximum { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.CategoryIds")] + public int[] CategoryIds { get; set; } + public List AvailableCategories { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.WithoutCategories")] + public bool? WithoutCategories { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.ManufacturerId")] + public int? ManufacturerId { get; set; } + public List AvailableManufacturers { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.WithoutManufacturers")] + public bool? WithoutManufacturers { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.ProductTagId")] + public int? ProductTagId { get; set; } + public List AvailableProductTags { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.FeaturedProducts")] + public bool? FeaturedProducts { get; set; } + + #endregion + + #region Customer + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.IsActiveCustomer")] + public bool? IsActiveCustomer { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.IsTaxExempt")] + public bool? IsTaxExempt { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.BillingCountryIds")] + public int[] BillingCountryIds { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.ShippingCountryIds")] + public int[] ShippingCountryIds { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.LastActivityFrom")] + public DateTime? LastActivityFrom { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.LastActivityTo")] + public DateTime? LastActivityTo { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.HasSpentAtLeastAmount")] + public decimal? HasSpentAtLeastAmount { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.HasPlacedAtLeastOrders")] + public int? HasPlacedAtLeastOrders { get; set; } + + #endregion + + #region Order + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.OrderStatusIds")] + public int[] OrderStatusIds { get; set; } + public List AvailableOrderStates { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.PaymentStatusIds")] + public int[] PaymentStatusIds { get; set; } + public List AvailablePaymentStates { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.ShippingStatusIds")] + public int[] ShippingStatusIds { get; set; } + public List AvailableShippingStates { get; set; } + + #endregion + + #region Newsletter Subscription + + [SmartResourceDisplayName("Admin.DataExchange.Export.Filter.IsActiveSubscriber")] + public bool? IsActiveSubscriber { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs new file mode 100644 index 0000000000..32f763f134 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs @@ -0,0 +1,151 @@ +using System; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.DataExchange +{ + public class ExportPreviewModel : EntityModelBase + { + public string Name { get; set; } + public string ThumbnailUrl { get; set; } + public int GridPageSize { get; set; } + public int TotalRecords { get; set; } + public ExportEntityType EntityType { get; set; } + public bool LogFileExists { get; set; } + public bool UsernamesEnabled { get; set; } + } + + public class ExportPreviewProductModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.Catalog.Products.Fields.ProductType")] + public int ProductTypeId { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Products.Fields.ProductType")] + public string ProductTypeName { get; set; } + public string ProductTypeLabelHint { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Products.Fields.Name")] + public string Name { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Products.Fields.Sku")] + public string Sku { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Products.Fields.Price")] + public decimal Price { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Products.Fields.StockQuantity")] + public int StockQuantity { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Products.Fields.Published")] + public bool Published { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Products.Fields.AdminComment")] + public string AdminComment { get; set; } + } + + public class ExportPreviewOrderModel : EntityModelBase + { + public bool HasNewPaymentNotification { get; set; } + + [SmartResourceDisplayName("Admin.Orders.Fields.OrderNumber")] + public string OrderNumber { get; set; } + + [SmartResourceDisplayName("Admin.Orders.Fields.OrderStatus")] + public string OrderStatus { get; set; } + + [SmartResourceDisplayName("Admin.Orders.Fields.PaymentStatus")] + public string PaymentStatus { get; set; } + + [SmartResourceDisplayName("Admin.Orders.Fields.ShippingStatus")] + public string ShippingStatus { get; set; } + + [SmartResourceDisplayName("Admin.Orders.Fields.CustomerEmail")] + public string CustomerEmail { get; set; } + + [SmartResourceDisplayName("Admin.Orders.Fields.Store")] + public string StoreName { get; set; } + + [SmartResourceDisplayName("Admin.Orders.Fields.CreatedOn")] + public DateTime CreatedOn { get; set; } + + [SmartResourceDisplayName("Admin.Orders.Fields.OrderTotal")] + public decimal OrderTotal { get; set; } + } + + public class ExportPreviewCategoryModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.Catalog.Products.Categories.Fields.Category")] + public string Breadcrumb { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.FullName")] + public string FullName { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.Alias")] + public string Alias { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.Published")] + public bool Published { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.DisplayOrder")] + public int DisplayOrder { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] + public bool LimitedToStores { get; set; } + } + + public class ExportPreviewManufacturerModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.Catalog.Manufacturers.Fields.Name")] + public string Name { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Manufacturers.Fields.Published")] + public bool Published { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Manufacturers.Fields.DisplayOrder")] + public int DisplayOrder { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] + public bool LimitedToStores { get; set; } + } + + public class ExportPreviewCustomerModel : EntityModelBase + { + public bool UsernamesEnabled { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.Username")] + public string Username { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.FullName")] + public string FullName { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.Email")] + public string Email { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.CustomerRoles")] + public string CustomerRoleNames { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.Active")] + public bool Active { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.CreatedOn")] + public DateTime CreatedOn { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.LastActivityDate")] + public DateTime LastActivityDate { get; set; } + } + + public class ExportPreviewNewsLetterSubscriptionModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.Promotions.NewsLetterSubscriptions.Fields.Email")] + public string Email { get; set; } + + [SmartResourceDisplayName("Admin.Promotions.NewsLetterSubscriptions.Fields.Active")] + public bool Active { get; set; } + + [SmartResourceDisplayName("Admin.Promotions.NewsLetterSubscriptions.Fields.CreatedOn")] + public DateTime CreatedOn { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store")] + public string StoreName { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs new file mode 100644 index 0000000000..5c2e77b6ef --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Web; +using System.Web.Mvc; +using FluentValidation.Attributes; +using SmartStore.Admin.Models.Tasks; +using SmartStore.Admin.Validators.DataExchange; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.DataExchange +{ + [Validator(typeof(ExportProfileValidator))] + public partial class ExportProfileModel : EntityModelBase + { + public int StoreCount { get; set; } + public string AllString { get; set; } + public string UnspecifiedString { get; set; } + public bool LogFileExists { get; set; } + public bool HasActiveProvider { get; set; } + public string[] FileNamePatternDescriptions { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Name")] + public string Name { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.SystemName")] + public string SystemName { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.IsSystemProfile")] + public bool IsSystemProfile { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.ProviderSystemName")] + public string ProviderSystemName { get; set; } + public List AvailableProviders { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.FolderName")] + public string FolderName { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.FileNamePattern")] + public string FileNamePattern { get; set; } + public string FileNamePatternExample { get; set; } + + [SmartResourceDisplayName("Common.Enabled")] + public bool Enabled { get; set; } + + [SmartResourceDisplayName("Common.Execution")] + public int ScheduleTaskId { get; set; } + public string ScheduleTaskName { get; set; } + public bool IsTaskRunning { get; set; } + public bool IsTaskEnabled { get; set; } + + [SmartResourceDisplayName("Admin.Common.RecordsSkip")] + public int Offset { get; set; } + + [SmartResourceDisplayName("Admin.Common.RecordsTake")] + public int? Limit { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.BatchSize")] + public int? BatchSize { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.PerStore")] + public bool PerStore { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.EmailAccountId")] + public int? EmailAccountId { get; set; } + public List AvailableEmailAccounts { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.CompletedEmailAddresses")] + public string CompletedEmailAddresses { get; set; } + public string SerializedCompletedEmailAddresses { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.CreateZipArchive")] + public bool CreateZipArchive { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Cleanup")] + public bool Cleanup { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.CloneProfile")] + public int? CloneProfileId { get; set; } + public List AvailableProfiles { get; set; } + + public ProviderModel Provider { get; set; } + + public ExportFilterModel Filter { get; set; } + + public ExportProjectionModel Projection { get; set; } + + public List Deployments { get; set; } + + public ScheduleTaskModel TaskModel { get; set; } + + public int FileCount { get; set; } + + public class ProviderModel + { + public string ConfigPartialViewName { get; set; } + public Type ConfigDataType { get; set; } + public object ConfigData { get; set; } + + public ExportFeatures Feature { get; set; } + + [SmartResourceDisplayName("Common.Image")] + public string ThumbnailUrl { get; set; } + + [SmartResourceDisplayName("Common.Website")] + public string Url { get; set; } + + [SmartResourceDisplayName("Common.Provider")] + public string FriendlyName { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Plugins.Fields.Author")] + public string Author { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Plugins.Fields.Version")] + public string Version { get; set; } + + [SmartResourceDisplayName("Common.Description")] + [AllowHtml] + public string Description { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.EntityType")] + public ExportEntityType EntityType { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.EntityType")] + public string EntityTypeName { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.FileExtension")] + public string FileExtension { get; set; } + + public bool IsFileBasedExport + { + get { return FileExtension.HasValue(); } + } + + [SmartResourceDisplayName("Admin.DataExchange.Export.SupportedFileTypes")] + public string SupportedFileTypes { get; set; } + } + + public class ProviderSelectItem + { + public int Id { get; set; } + public string SystemName { get; set; } + public string FriendlyName { get; set; } + public string ImageUrl { get; set; } + public string Description { get; set; } + } + } + + + public partial class ExportFileDetailsModel : EntityModelBase + { + public int FileCount + { + get + { + var result = ExportFiles.Count; + + if (result == 0) + result = PublicFiles.Count; + + return result; + } + } + + public List ExportFiles { get; set; } + public List PublicFiles { get; set; } + + public bool IsForDeployment { get; set; } + + public class FileInfo + { + public int StoreId { get; set; } + public string StoreName { get; set; } + + public int DisplayOrder { get; set; } + + public string FilePath { get; set; } + public string FileUrl { get; set; } + + public string FileName { get; set; } + public string FileExtension { get; set; } + + public string FileRootPath + { + get + { + var rootPath = ""; + var appPath = HttpRuntime.AppDomainAppPath; + + if (FilePath.StartsWith(appPath)) + rootPath = FilePath.Replace(appPath, "~/"); + + rootPath = rootPath.Replace('\\', '/'); + + return rootPath; + } + } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs new file mode 100644 index 0000000000..03fb387c53 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Web.Framework; + +namespace SmartStore.Admin.Models.DataExchange +{ + public class ExportProjectionModel + { + public ExportProjectionModel() + { + DescriptionMergingId = (int)ExportDescriptionMerging.Description; + } + + #region All entity types + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.StoreId")] + public int? StoreId { get; set; } + public List AvailableStores { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.LanguageId")] + public int? LanguageId { get; set; } + public List AvailableLanguages { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.CurrencyId")] + public int? CurrencyId { get; set; } + public List AvailableCurrencies { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.CustomerId")] + public int? CustomerId { get; set; } + + #endregion + + #region Product + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.DescriptionMerging")] + public int DescriptionMergingId { get; set; } + public SelectList AvailableDescriptionMergings { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.DescriptionToPlainText")] + public bool DescriptionToPlainText { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.AppendDescriptionText")] + [AllowHtml] + public string AppendDescriptionText { get; set; } + public string SerializedAppendDescriptionText { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.RemoveCriticalCharacters")] + public bool RemoveCriticalCharacters { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.CriticalCharacters")] + public string CriticalCharacters { get; set; } + public string SerializedCriticalCharacters { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.PriceType")] + public PriceDisplayType? PriceType { get; set; } + public IList AvailablePriceTypes { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.ConvertNetToGrossPrices")] + public bool ConvertNetToGrossPrices { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.Brand")] + public string Brand { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.NumberOfPictures")] + public int? NumberOfPictures { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.PictureSize")] + public int PictureSize { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.ShippingTime")] + public string ShippingTime { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.ShippingCosts")] + public decimal? ShippingCosts { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.FreeShippingThreshold")] + public decimal? FreeShippingThreshold { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.AttributeCombinationAsProduct")] + public bool AttributeCombinationAsProduct { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.AttributeCombinationValueMerging")] + public int AttributeCombinationValueMergingId { get; set; } + public SelectList AvailableAttributeCombinationValueMerging { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.NoGroupedProducts")] + public bool NoGroupedProducts { get; set; } + + #endregion + + #region Order + + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.OrderStatusChange")] + public int OrderStatusChangeId { get; set; } + public SelectList AvailableOrderStatusChange { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileListModel.cs new file mode 100644 index 0000000000..af9f7af39e --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileListModel.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.DataExchange +{ + public partial class ImportProfileListModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.Common.Entity")] + public ImportEntityType EntityType { get; set; } + public List AvailableEntityTypes { get; set; } + + public List Profiles { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs new file mode 100644 index 0000000000..5baf801477 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using FluentValidation.Attributes; +using SmartStore.Admin.Models.Tasks; +using SmartStore.Admin.Validators.DataExchange; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Services.DataExchange.Import; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.DataExchange +{ + [Validator(typeof(ImportProfileValidator))] + public partial class ImportProfileModel : EntityModelBase + { + public ImportProfileModel() + { + ExtraData = new ExtraDataModel(); + } + + [SmartResourceDisplayName("Admin.DataExchange.Import.Name")] + public string Name { get; set; } + + [SmartResourceDisplayName("Admin.Common.ImportFiles")] + public List ExistingFileNames { get; set; } + + [SmartResourceDisplayName("Admin.Common.Entity")] + public ImportEntityType EntityType { get; set; } + + [SmartResourceDisplayName("Admin.Common.Entity")] + public string EntityTypeName { get; set; } + + [SmartResourceDisplayName("Common.Enabled")] + public bool Enabled { get; set; } + + [SmartResourceDisplayName("Admin.Common.RecordsSkip")] + public int? Skip { get; set; } + + [SmartResourceDisplayName("Admin.Common.RecordsTake")] + public int? Take { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Import.UpdateOnly")] + public bool UpdateOnly { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Import.KeyFieldNames")] + public string[] KeyFieldNames { get; set; } + public List AvailableKeyFieldNames { get; set; } + + [SmartResourceDisplayName("Common.Execution")] + public int ScheduleTaskId { get; set; } + public string ScheduleTaskName { get; set; } + public bool IsTaskRunning { get; set; } + public bool IsTaskEnabled { get; set; } + + public ScheduleTaskModel TaskModel { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Import.LastImportResult")] + public SerializableImportResult ImportResult { get; set; } + + public bool LogFileExists { get; set; } + public string TempFileName { get; set; } + + public CsvConfigurationModel CsvConfiguration { get; set; } + + public ExtraDataModel ExtraData { get; set; } + + public List ColumnMappings { get; set; } + public List AvailableSourceColumns { get; set; } + public List AvailableEntityProperties { get; set; } + + public int MaxMappingLabelTextLength { get { return 42; } } + + public class ExtraDataModel + { + [SmartResourceDisplayName("Admin.DataExchange.Import.NumberOfPictures")] + public int? NumberOfPictures { get; set; } + } + } + + + public class ColumnMappingItemModel + { + public int Index { get; set; } + + public string Column { get; set; } + public string ColumnWithoutIndex { get; set; } + public string ColumnIndex { get; set; } + + public string Property { get; set; } + public string PropertyDescription { get; set; } + + public string Default { get; set; } + public bool IsDefaultDisabled { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ProfileInfoForProviderModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ProfileInfoForProviderModel.cs new file mode 100644 index 0000000000..1a211ffea4 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ProfileInfoForProviderModel.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.DataExchange +{ + public class ProfileInfoForProviderModel : ModelBase + { + public string SystemName { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.ProfileForProvider")] + public List Profiles { get; set; } + + public string ReturnUrl { get; set; } + + public class ProfileModel : EntityModelBase + { + public int? ScheduleTaskId { get; set; } + + public bool Enabled { get; set; } + + public string Name { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Directory/CountryListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Directory/CountryListModel.cs new file mode 100644 index 0000000000..ece27d8805 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Directory/CountryListModel.cs @@ -0,0 +1,9 @@ +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.Directory +{ + public class CountryListModel : ModelBase + { + public int StoreCount { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Directory/CountryModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Directory/CountryModel.cs index 6df12ca14f..20a7b2b6d6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Directory/CountryModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Directory/CountryModel.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; using System.Web.Mvc; using FluentValidation.Attributes; +using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.Directory; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Directory { @@ -15,6 +16,7 @@ public CountryModel() { Locales = new List(); } + [SmartResourceDisplayName("Admin.Configuration.Countries.Fields.Name")] [AllowHtml] public string Name { get; set; } @@ -45,13 +47,17 @@ public CountryModel() [SmartResourceDisplayName("Admin.Configuration.Countries.Fields.DisplayOrder")] public int DisplayOrder { get; set; } - - - [SmartResourceDisplayName("Admin.Configuration.Countries.Fields.NumberOfStates")] public int NumberOfStates { get; set; } public IList Locales { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] + public bool LimitedToStores { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store.AvailableFor")] + public List AvailableStores { get; set; } + public int[] SelectedStoreIds { get; set; } } public class CountryLocalizedModel : ILocalizedModelLocal diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Directory/CurrencyModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Directory/CurrencyModel.cs index 179fa5bd37..211a562fc0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Directory/CurrencyModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Directory/CurrencyModel.cs @@ -6,7 +6,7 @@ using SmartStore.Admin.Validators.Directory; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Directory { @@ -17,12 +17,12 @@ public CurrencyModel() { Locales = new List(); - AvailableDomainEndings = new List() + AvailableDomainEndings = new List { - new SelectListItem() { Text = ".com", Value = ".com" }, - new SelectListItem() { Text = ".uk", Value = ".uk" }, - new SelectListItem() { Text = ".de", Value = ".de" }, - new SelectListItem() { Text = ".ch", Value = ".ch" } + new SelectListItem { Text = ".com", Value = ".com" }, + new SelectListItem { Text = ".uk", Value = ".uk" }, + new SelectListItem { Text = ".de", Value = ".de" }, + new SelectListItem { Text = ".ch", Value = ".ch" } }; } [SmartResourceDisplayName("Admin.Configuration.Currencies.Fields.Name")] @@ -53,11 +53,14 @@ public CurrencyModel() [SmartResourceDisplayName("Admin.Configuration.Currencies.Fields.CreatedOn")] public DateTime CreatedOn { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Currencies.Fields.IsPrimaryExchangeRateCurrency")] + public bool IsPrimaryStoreCurrency { get; set; } public bool IsPrimaryExchangeRateCurrency { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Currencies.Fields.IsPrimaryStoreCurrency")] - public bool IsPrimaryStoreCurrency { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Currencies.Fields.PrimaryStoreCurrencyStores")] + public IList PrimaryStoreCurrencyStores { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Currencies.Fields.PrimaryExchangeRateCurrencyStores")] + public IList PrimaryExchangeRateCurrencyStores { get; set; } [SmartResourceDisplayName("Admin.Configuration.Currencies.Fields.DomainEndings")] public string DomainEndings { get; set; } @@ -68,6 +71,7 @@ public CurrencyModel() //Store mapping [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] public bool LimitedToStores { get; set; } + [SmartResourceDisplayName("Admin.Common.Store.AvailableFor")] public List AvailableStores { get; set; } public int[] SelectedStoreIds { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Directory/DeliveryTimeModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Directory/DeliveryTimeModel.cs index 987492572a..8b19468006 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Directory/DeliveryTimeModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Directory/DeliveryTimeModel.cs @@ -5,7 +5,7 @@ using SmartStore.Admin.Validators.Directory; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Directory { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Directory/MeasureDimensionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Directory/MeasureDimensionModel.cs index 29884b5b02..2fcbcf8859 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Directory/MeasureDimensionModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Directory/MeasureDimensionModel.cs @@ -3,7 +3,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Directory; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Directory { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Directory/MeasureWeightModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Directory/MeasureWeightModel.cs index a0acb7a438..8f90aa7e01 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Directory/MeasureWeightModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Directory/MeasureWeightModel.cs @@ -3,7 +3,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Directory; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Directory { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Directory/QuantityUnitModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Directory/QuantityUnitModel.cs index 8081234e65..e7a3a25b21 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Directory/QuantityUnitModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Directory/QuantityUnitModel.cs @@ -5,7 +5,7 @@ using SmartStore.Admin.Validators.Directory; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Directory { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Directory/StateProvinceModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Directory/StateProvinceModel.cs index 13411d0299..cd3755b294 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Directory/StateProvinceModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Directory/StateProvinceModel.cs @@ -4,7 +4,7 @@ using SmartStore.Admin.Validators.Directory; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Directory { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Discounts/DiscountModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Discounts/DiscountModel.cs index ec2d642ac8..46b1279a0d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Discounts/DiscountModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Discounts/DiscountModel.cs @@ -5,7 +5,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Discounts; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Discounts { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/ExternalAuthentication/AuthenticationMethodModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/ExternalAuthentication/AuthenticationMethodModel.cs index 060fdfee71..e20b0f4aaa 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/ExternalAuthentication/AuthenticationMethodModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/ExternalAuthentication/AuthenticationMethodModel.cs @@ -2,7 +2,7 @@ using System.Web.Routing; using SmartStore.Core; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Plugins; namespace SmartStore.Admin.Models.ExternalAuthentication { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumGroupModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumGroupModel.cs index 825a2dc718..006da6363e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumGroupModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumGroupModel.cs @@ -2,24 +2,31 @@ using System.Collections.Generic; using System.Web.Mvc; using FluentValidation.Attributes; +using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.Forums; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Localization; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Forums { [Validator(typeof(ForumGroupValidator))] - public class ForumGroupModel : EntityModelBase + public class ForumGroupModel : EntityModelBase, ILocalizedModel { public ForumGroupModel() { ForumModels = new List(); + Locales = new List(); } [SmartResourceDisplayName("Admin.ContentManagement.Forums.ForumGroup.Fields.Name")] [AllowHtml] public string Name { get; set; } + [SmartResourceDisplayName("Admin.ContentManagement.Forums.ForumGroup.Fields.SeName")] + [AllowHtml] + public string SeName { get; set; } + [SmartResourceDisplayName("Admin.ContentManagement.Forums.ForumGroup.Fields.Description")] [AllowHtml] public string Description { get; set; } @@ -29,8 +36,34 @@ public ForumGroupModel() [SmartResourceDisplayName("Admin.ContentManagement.Forums.ForumGroup.Fields.CreatedOn")] public DateTime CreatedOn { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] + public bool LimitedToStores { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store.AvailableFor")] + public List AvailableStores { get; set; } + public int[] SelectedStoreIds { get; set; } //use ForumModel public IList ForumModels { get; set; } + + public IList Locales { get; set; } } + + public class ForumGroupLocalizedModel : ILocalizedModelLocal + { + public int LanguageId { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.Forums.ForumGroup.Fields.Name")] + [AllowHtml] + public string Name { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.Forums.ForumGroup.Fields.SeName")] + [AllowHtml] + public string SeName { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.Forums.ForumGroup.Fields.Description")] + [AllowHtml] + public string Description { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumModel.cs index fdf1d5d2c3..95d6e8bbd1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Forums/ForumModel.cs @@ -4,16 +4,18 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Forums; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Localization; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Forums { [Validator(typeof(ForumValidator))] - public class ForumModel : EntityModelBase + public class ForumModel : EntityModelBase, ILocalizedModel { public ForumModel() { ForumGroups = new List(); + Locales = new List(); } [SmartResourceDisplayName("Admin.ContentManagement.Forums.Forum.Fields.ForumGroupId")] @@ -23,6 +25,10 @@ public ForumModel() [AllowHtml] public string Name { get; set; } + [SmartResourceDisplayName("Admin.ContentManagement.Forums.Forum.Fields.SeName")] + [AllowHtml] + public string SeName { get; set; } + [SmartResourceDisplayName("Admin.ContentManagement.Forums.Forum.Fields.Description")] [AllowHtml] public string Description { get; set; } @@ -34,5 +40,24 @@ public ForumModel() public DateTime CreatedOn { get; set; } public List ForumGroups { get; set; } + + public IList Locales { get; set; } } + + public class ForumLocalizedModel : ILocalizedModelLocal + { + public int LanguageId { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.Forums.Forum.Fields.Name")] + [AllowHtml] + public string Name { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.Forums.Forum.Fields.SeName")] + [AllowHtml] + public string SeName { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.Forums.Forum.Fields.Description")] + [AllowHtml] + public string Description { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageModel.cs index f556c19746..28d3dfaf30 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageModel.cs @@ -4,11 +4,11 @@ using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.Localization; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Localization { - [Validator(typeof(LanguageValidator))] + [Validator(typeof(LanguageValidator))] public class LanguageModel : EntityModelBase { public LanguageModel() @@ -23,18 +23,21 @@ public LanguageModel() [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.LanguageCulture")] [AllowHtml] public string LanguageCulture { get; set; } + public List AvailableCultures { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.UniqueSeoCode")] + [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.UniqueSeoCode")] [AllowHtml] public string UniqueSeoCode { get; set; } + public List AvailableTwoLetterLanguageCodes { get; set; } - //flags - [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.FlagImageFileName")] + //flags + [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.FlagImageFileName")] [AllowHtml] public string FlagImageFileName { get; set; } public IList FlagFileNames { get; set; } + public List AvailableFlags { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.Rtl")] + [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.Rtl")] public bool Rtl { get; set; } [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.Published")] diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageResourceModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageResourceModel.cs index 44c2ec222d..f3ebe36dec 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageResourceModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageResourceModel.cs @@ -2,7 +2,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Localization; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Localization { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogModel.cs index 5f5ef7ce6c..7b5b9a57ac 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogModel.cs @@ -1,6 +1,6 @@ using System; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Logging { @@ -8,13 +8,21 @@ public partial class ActivityLogModel : EntityModelBase { [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.ActivityLogType")] public string ActivityLogTypeName { get; set; } + [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.Customer")] public int CustomerId { get; set; } + [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.Customer")] public string CustomerEmail { get; set; } + [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.Comment")] public string Comment { get; set; } + [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.CreatedOn")] public DateTime CreatedOn { get; set; } - } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.IsSystemAccount")] + public bool IsSystemAccount { get; set; } + public string SystemAccountName { get; set; } + } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogSearchModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogSearchModel.cs index 6cee83a1ca..9e17c5bbcf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogSearchModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogSearchModel.cs @@ -1,19 +1,20 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Logging { - public class ActivityLogSearchModel : ModelBase + public class ActivityLogSearchModel : ModelBase { - public ActivityLogSearchModel() - { - ActivityLogType = new List(); - } - [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.CreatedOnFrom")] + public int GridPageSize { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.ActivityLogType")] + public int ActivityLogTypeId { get; set; } + public IList ActivityLogType { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.CreatedOnFrom")] public DateTime? CreatedOnFrom { get; set; } [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.CreatedOnTo")] @@ -23,10 +24,7 @@ public ActivityLogSearchModel() [AllowHtml] public string CustomerEmail { get; set; } - [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.ActivityLogType")] - public int ActivityLogTypeId { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.ActivityLogType")] - public IList ActivityLogType { get; set; } - } + [SmartResourceDisplayName("Admin.Configuration.ActivityLog.ActivityLog.Fields.CustomerSystemAccount")] + public bool? CustomerSystemAccount { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogTypeModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogTypeModel.cs index adda8ca623..b335237212 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogTypeModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Logging/ActivityLogTypeModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Logging { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Logging/LogListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Logging/LogListModel.cs index cb074e207b..70646ffdc4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Logging/LogListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Logging/LogListModel.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Logging { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Logging/LogModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Logging/LogModel.cs index f138c3749e..4187b3f300 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Logging/LogModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Logging/LogModel.cs @@ -1,7 +1,7 @@ using System; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Logging { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Messages/CampaignModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Messages/CampaignModel.cs index 0a7088e52e..40527cb249 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Messages/CampaignModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Messages/CampaignModel.cs @@ -5,7 +5,7 @@ using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.Messages; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Messages { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Messages/EmailAccountModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Messages/EmailAccountModel.cs index 258696b60e..8a3fa54a2d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Messages/EmailAccountModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Messages/EmailAccountModel.cs @@ -3,7 +3,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Messages; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Messages { @@ -48,5 +48,7 @@ public class EmailAccountModel : EntityModelBase [AllowHtml] public string SendTestEmailTo { get; set; } - } + public string TestEmailShortErrorMessage { get; set; } + public string TestEmailFullErrorMessage { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Messages/MessageTemplateListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Messages/MessageTemplateListModel.cs index 1027e7f9b7..1e0d7f313e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Messages/MessageTemplateListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Messages/MessageTemplateListModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Messages { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Messages/MessageTemplateModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Messages/MessageTemplateModel.cs index 57942cc1fe..9dab2a81ba 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Messages/MessageTemplateModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Messages/MessageTemplateModel.cs @@ -1,14 +1,14 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Web.Mvc; using System.Web.Script.Serialization; using FluentValidation.Attributes; +using Newtonsoft.Json; using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.Messages; using SmartStore.Collections; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Messages { @@ -24,7 +24,7 @@ public MessageTemplateModel() } [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.AllowedTokens")] - [ScriptIgnore] + [ScriptIgnore, JsonIgnore] public TreeNode TokensTree { get; set; } [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.Name")] @@ -43,13 +43,25 @@ public MessageTemplateModel() [AllowHtml] public string Body { get; set; } - [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.IsActive")] + [SmartResourceDisplayName("Common.Active")] [AllowHtml] public bool IsActive { get; set; } [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.EmailAccount")] public int EmailAccountId { get; set; } + [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.SendManually")] + public bool SendManually { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.Attachment1FileId")] + public int? Attachment1FileId { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.Attachment2FileId")] + public int? Attachment2FileId { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.Attachment3FileId")] + public int? Attachment3FileId { get; set; } + //Store mapping [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] public bool LimitedToStores { get; set; } @@ -59,7 +71,6 @@ public MessageTemplateModel() public IList Locales { get; set; } public IList AvailableEmailAccounts { get; set; } - } public class MessageTemplateLocalizedModel : ILocalizedModelLocal @@ -80,5 +91,14 @@ public class MessageTemplateLocalizedModel : ILocalizedModelLocal [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.EmailAccount")] public int EmailAccountId { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.Attachment1FileId")] + public int? Attachment1FileId { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.Attachment2FileId")] + public int? Attachment2FileId { get; set; } + + [SmartResourceDisplayName("Admin.ContentManagement.MessageTemplates.Fields.Attachment3FileId")] + public int? Attachment3FileId { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Messages/NewsLetterSubscriptionListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Messages/NewsLetterSubscriptionListModel.cs index 1ee10842ea..25c13c5543 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Messages/NewsLetterSubscriptionListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Messages/NewsLetterSubscriptionListModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using Telerik.Web.Mvc; namespace SmartStore.Admin.Models.Messages diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Messages/NewsLetterSubscriptionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Messages/NewsLetterSubscriptionModel.cs index 56134fe517..b1334e28e0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Messages/NewsLetterSubscriptionModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Messages/NewsLetterSubscriptionModel.cs @@ -3,7 +3,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Messages; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Messages { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Messages/QueuedEmailListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Messages/QueuedEmailListModel.cs index 2f21d7fbdd..dcadd7eb62 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Messages/QueuedEmailListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Messages/QueuedEmailListModel.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Messages { @@ -31,6 +31,9 @@ public QueuedEmailListModel() [SmartResourceDisplayName("Admin.System.QueuedEmails.List.LoadNotSent")] public bool SearchLoadNotSent { get; set; } + [SmartResourceDisplayName("Admin.System.QueuedEmails.List.SendManually")] + public bool? SearchSendManually { get; set; } + [SmartResourceDisplayName("Admin.System.QueuedEmails.List.MaxSentTries")] public int SearchMaxSentTries { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Messages/QueuedEmailModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Messages/QueuedEmailModel.cs index 4861d293f5..d140ba63ef 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Messages/QueuedEmailModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Messages/QueuedEmailModel.cs @@ -1,17 +1,24 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Admin.Validators.Messages; +using SmartStore.Core.Domain.Messages; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Messages { [Validator(typeof(QueuedEmailValidator))] public class QueuedEmailModel : EntityModelBase { - [SmartResourceDisplayName("Admin.System.QueuedEmails.Fields.Id")] + public QueuedEmailModel() + { + this.Attachments = new List(); + } + + [SmartResourceDisplayName("Admin.System.QueuedEmails.Fields.Id")] public override int Id { get; set; } [SmartResourceDisplayName("Admin.System.QueuedEmails.Fields.Priority")] @@ -56,11 +63,25 @@ public class QueuedEmailModel : EntityModelBase public int SentTries { get; set; } [SmartResourceDisplayName("Admin.System.QueuedEmails.Fields.SentOn")] - [DisplayFormat(DataFormatString = "{0}", NullDisplayText = "n/a")] + //[DisplayFormat(DataFormatString = "{0}", NullDisplayText = "n/a")] public DateTime? SentOn { get; set; } [SmartResourceDisplayName("Admin.System.QueuedEmails.Fields.EmailAccountName")] [AllowHtml] public string EmailAccountName { get; set; } + + [SmartResourceDisplayName("Admin.System.QueuedEmails.Fields.SendManually")] + public bool SendManually { get; set; } + + public int AttachmentsCount { get; set; } + + [SmartResourceDisplayName("Admin.System.QueuedEmails.Fields.Attachments")] + public ICollection Attachments { get; set; } + + public class QueuedEmailAttachmentModel : EntityModelBase + { + public string Name { get; set; } + public string MimeType { get; set; } + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/News/NewsCommentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/News/NewsCommentModel.cs index 0185098e5a..ffce2d00d0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/News/NewsCommentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/News/NewsCommentModel.cs @@ -1,7 +1,7 @@ using System; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.News { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/News/NewsItemListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/News/NewsItemListModel.cs index 7687108ddb..416914f374 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/News/NewsItemListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/News/NewsItemListModel.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.News { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/News/NewsItemModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/News/NewsItemModel.cs index d50ff0f180..feae4bed3d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/News/NewsItemModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/News/NewsItemModel.cs @@ -6,7 +6,7 @@ using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.News; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.News { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/AutoUpdateOrderItemModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/AutoUpdateOrderItemModel.cs index f039bb8166..b9cf6d4611 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/AutoUpdateOrderItemModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/AutoUpdateOrderItemModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/BestsellersReportLineModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/BestsellersReportLineModel.cs index fd40fe637b..03987bc76d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/BestsellersReportLineModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/BestsellersReportLineModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/BestsellersReportModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/BestsellersReportModel.cs index 3ec4b73a66..122d5f2dee 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/BestsellersReportModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/BestsellersReportModel.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeListModel.cs new file mode 100644 index 0000000000..6084bd6210 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeListModel.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.Orders +{ + public class CheckoutAttributeListModel : ModelBase + { + public int GridPageSize { get; set; } + + public IList AvailableStores { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs index 45c1b635b8..6f6a404473 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; using System.Web.Mvc; using FluentValidation.Attributes; +using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.Orders; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { @@ -17,6 +18,9 @@ public CheckoutAttributeModel() AvailableTaxCategories = new List(); } + [SmartResourceDisplayName("Common.IsActive")] + public bool IsActive { get; set; } + [SmartResourceDisplayName("Admin.Catalog.Attributes.CheckoutAttributes.Fields.Name")] [AllowHtml] public string Name { get; set; } @@ -47,12 +51,17 @@ public CheckoutAttributeModel() [SmartResourceDisplayName("Admin.Catalog.Attributes.CheckoutAttributes.Fields.DisplayOrder")] public int DisplayOrder { get; set; } - public IList Locales { get; set; } - } + [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] + public bool LimitedToStores { get; set; } - public class CheckoutAttributeLocalizedModel : ILocalizedModelLocal + [SmartResourceDisplayName("Admin.Common.Store.AvailableFor")] + public List AvailableStores { get; set; } + public int[] SelectedStoreIds { get; set; } + } + + public class CheckoutAttributeLocalizedModel : ILocalizedModelLocal { public int LanguageId { get; set; } @@ -63,6 +72,5 @@ public class CheckoutAttributeLocalizedModel : ILocalizedModelLocal [SmartResourceDisplayName("Admin.Catalog.Attributes.CheckoutAttributes.Fields.TextPrompt")] [AllowHtml] public string TextPrompt { get; set; } - } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeValueModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeValueModel.cs index 9c674b04d6..2a8383ca81 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeValueModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeValueModel.cs @@ -4,7 +4,7 @@ using SmartStore.Admin.Validators.Orders; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/GiftCardListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/GiftCardListModel.cs index 6385c167b5..3ae20d0982 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/GiftCardListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/GiftCardListModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { @@ -12,6 +12,8 @@ public GiftCardListModel() ActivatedList = new List(); } + public int GridPageSize { get; set; } + [SmartResourceDisplayName("Admin.GiftCards.List.CouponCode")] [AllowHtml] public string CouponCode { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/GiftCardModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/GiftCardModel.cs index 7cf9b6c9f8..27c2b7c6d7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/GiftCardModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/GiftCardModel.cs @@ -2,7 +2,7 @@ using System.Web.Mvc; using SmartStore.Core.Domain.Catalog; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/NeverSoldReportLineModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/NeverSoldReportLineModel.cs index d018612caf..2e99a612a9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/NeverSoldReportLineModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/NeverSoldReportLineModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/NeverSoldReportModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/NeverSoldReportModel.cs index 476d9be43a..c0e7523bfd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/NeverSoldReportModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/NeverSoldReportModel.cs @@ -1,7 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderAddressModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderAddressModel.cs index 8adf062937..ed9ddccf4d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderAddressModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderAddressModel.cs @@ -1,5 +1,5 @@ using SmartStore.Admin.Models.Common; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderAverageReportLineSummaryModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderAverageReportLineSummaryModel.cs index 5f1b7e1044..428c941f75 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderAverageReportLineSummaryModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderAverageReportLineSummaryModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderIncompleteReportLineModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderIncompleteReportLineModel.cs index ccc902b810..4714413fc9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderIncompleteReportLineModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderIncompleteReportLineModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderListModel.cs index 4934c2d3f3..ce42e552ae 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderListModel.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { @@ -54,9 +54,9 @@ public OrderListModel() [AllowHtml] public string GoDirectlyToNumber { get; set; } - + public int GridPageSize { get; set; } - public IList AvailableOrderStatuses { get; set; } + public IList AvailableOrderStatuses { get; set; } public IList AvailablePaymentStatuses { get; set; } public IList AvailableShippingStatuses { get; set; } public IList AvailableStores { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs index 00d3da4beb..d40ee84e5e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs @@ -8,7 +8,7 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Tax; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { @@ -37,10 +37,13 @@ public OrderModel() //customer info [SmartResourceDisplayName("Admin.Orders.Fields.Customer")] public int CustomerId { get; set; } + + [SmartResourceDisplayName("Admin.Orders.List.CustomerName")] public string CustomerName { get; set; } [SmartResourceDisplayName("Admin.Orders.Fields.CustomerEmail")] public string CustomerEmail { get; set; } + [SmartResourceDisplayName("Admin.Orders.Fields.CustomerIP")] public string CustomerIp { get; set; } @@ -124,6 +127,8 @@ public OrderModel() public string PaymentMethod { get; set; } public string PaymentMethodSystemName { get; set; } + public bool HasNewPaymentNotification { get; set; } + //credit card info public bool AllowStoringCreditCardNumber { get; set; } [SmartResourceDisplayName("Admin.Orders.Fields.CardType")] @@ -224,7 +229,10 @@ public OrderModel() [SmartResourceDisplayName("Common.UpdatedOn")] public DateTime UpdatedOn { get; set; } - public string CustomerComment { get; set; } + [SmartResourceDisplayName("Admin.Orders.Fields.AcceptThirdPartyEmailHandOver")] + public bool AcceptThirdPartyEmailHandOver { get; set; } + + public string CustomerComment { get; set; } //checkout attributes public string CheckoutAttributeInfo { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/RecurringPaymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/RecurringPaymentModel.cs index 693f4a8fcd..00b2170dac 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/RecurringPaymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/RecurringPaymentModel.cs @@ -3,7 +3,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Orders; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/ReturnRequestListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/ReturnRequestListModel.cs index 29611fa6b4..71ba724507 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/ReturnRequestListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/ReturnRequestListModel.cs @@ -2,7 +2,7 @@ using System.Web.Mvc; using SmartStore.Core.Domain.Orders; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/ReturnRequestModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/ReturnRequestModel.cs index 418088b13e..aad5d0da06 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/ReturnRequestModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/ReturnRequestModel.cs @@ -5,7 +5,7 @@ using SmartStore.Admin.Validators.Orders; using SmartStore.Core.Domain.Orders; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { @@ -55,6 +55,9 @@ public ReturnRequestModel() public string RequestedAction { get; set; } public IList AvailableRequestedAction { get; set; } + [SmartResourceDisplayName("Admin.ReturnRequests.Fields.RequestedActionUpdatedOnUtc")] + public DateTime? RequestedActionUpdated { get; set; } + [AllowHtml] [SmartResourceDisplayName("Admin.ReturnRequests.Fields.CustomerComments")] public string CustomerComments { get; set; } @@ -63,6 +66,9 @@ public ReturnRequestModel() [SmartResourceDisplayName("Admin.ReturnRequests.Fields.StaffNotes")] public string StaffNotes { get; set; } + [SmartResourceDisplayName("Admin.Common.AdminComment")] + public string AdminComment { get; set; } + [SmartResourceDisplayName("Admin.ReturnRequests.Fields.Status")] public int ReturnRequestStatusId { get; set; } [SmartResourceDisplayName("Admin.ReturnRequests.Fields.Status")] @@ -71,6 +77,9 @@ public ReturnRequestModel() [SmartResourceDisplayName("Admin.ReturnRequests.Fields.CreatedOn")] public DateTime CreatedOn { get; set; } + [SmartResourceDisplayName("Common.UpdatedOn")] + public DateTime UpdatedOn { get; set; } + public bool CanAccept { get diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/ShipmentListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/ShipmentListModel.cs index 946d5ceecd..87ccb48225 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/ShipmentListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/ShipmentListModel.cs @@ -1,7 +1,7 @@ using System; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/ShipmentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/ShipmentModel.cs index 7d66a6a676..1bdc151bf6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/ShipmentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/ShipmentModel.cs @@ -2,7 +2,7 @@ using SmartStore.Admin.Models.Common; using SmartStore.Core.Domain.Common; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Orders { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Payments/PaymentMethodEditModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Payments/PaymentMethodEditModel.cs new file mode 100644 index 0000000000..8aa7665ee6 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Payments/PaymentMethodEditModel.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Localization; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.Payments +{ + public class PaymentMethodEditModel : TabbableModel, ILocalizedModel + { + public PaymentMethodEditModel() + { + Locales = new List(); + FilterConfigurationUrls = new List(); + } + + public IList Locales { get; set; } + public string IconUrl { get; set; } + public IList FilterConfigurationUrls { get; set; } + + [SmartResourceDisplayName("Common.SystemName")] + public string SystemName { get; set; } + + [SmartResourceDisplayName("Common.FriendlyName")] + public string FriendlyName { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.ShortDescription")] + [AllowHtml] + public string Description { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.FullDescription")] + [AllowHtml] + public string FullDescription { get; set; } + } + + + public class PaymentMethodLocalizedModel : ILocalizedModelLocal + { + public int LanguageId { get; set; } + + [SmartResourceDisplayName("Common.FriendlyName")] + public string FriendlyName { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.ShortDescription")] + [AllowHtml] + public string Description { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.FullDescription")] + [AllowHtml] + public string FullDescription { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Payments/PaymentMethodModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Payments/PaymentMethodModel.cs index 75e1005149..736d04ca46 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Payments/PaymentMethodModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Payments/PaymentMethodModel.cs @@ -1,13 +1,10 @@ -using System.Web.Mvc; -using System.Web.Routing; -using SmartStore.Core; -using SmartStore.Services.Payments; +using SmartStore.Core; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Plugins; namespace SmartStore.Admin.Models.Payments { - public class PaymentMethodModel : ProviderModel, IActivatable + public class PaymentMethodModel : ProviderModel, IActivatable { public bool IsActive { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Plugins/LicensePluginModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Plugins/LicensePluginModel.cs new file mode 100644 index 0000000000..6a9909c672 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Plugins/LicensePluginModel.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.Plugins +{ + public class LicensePluginModel : ModelBase + { + public string SystemName { get; set; } + public int InvalidDataStoreId { get; set; } + + public List Licenses { get; set; } + + public class LicenseModel + { + [SmartResourceDisplayName("Admin.Configuration.Plugins.LicenseKey")] + public string LicenseKey { get; set; } + + public int StoreId { get; set; } + public string StoreName { get; set; } + public string StoreUrl { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Plugins/LocalPluginsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Plugins/LocalPluginsModel.cs index 08147a0508..453edc21a2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Plugins/LocalPluginsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Plugins/LocalPluginsModel.cs @@ -1,10 +1,8 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Web; -using SmartStore.Web.Framework.Mvc; -using SmartStore.Collections; using SmartStore.Admin.Models.Stores; +using SmartStore.Collections; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Plugins { @@ -21,6 +19,9 @@ public LocalPluginsModel() public Multimap Groups { get; set; } + public bool IsSandbox { get; set; } + public bool IsLocalhost { get; set; } + public ICollection AllPlugins { get diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Plugins/PluginModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Plugins/PluginModel.cs index 61dd53a704..a660f28ebf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Plugins/PluginModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Plugins/PluginModel.cs @@ -1,11 +1,11 @@ using System.Collections.Generic; using System.Web.Mvc; using FluentValidation.Attributes; -using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.Plugins; +using SmartStore.Licensing; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Plugins { @@ -16,6 +16,7 @@ public PluginModel() { Locales = new List(); } + [SmartResourceDisplayName("Admin.Configuration.Plugins.Fields.Group")] [AllowHtml] public string Group { get; set; } @@ -46,9 +47,33 @@ public PluginModel() [SmartResourceDisplayName("Admin.Configuration.Plugins.Fields.Configure")] public string ConfigurationUrl { get; set; } + public string Url { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Plugins.Fields.Installed")] public bool Installed { get; set; } + public string LicenseUrl { get; set; } + public bool IsLicensable { get; set; } + public LicensingState LicenseState { get; set; } + public string TruncatedLicenseKey { get; set; } + public int? RemainingDemoUsageDays { get; set; } + + public string RemainingDemoUsageDaysLabel + { + get + { + if (RemainingDemoUsageDays.HasValue) + { + if (RemainingDemoUsageDays <= 3) + return "label-important"; + + if (RemainingDemoUsageDays <= 6) + return "label-warning"; + } + return "label-success"; + } + } + public bool IsConfigurable { get; set; } public RouteInfo ConfigurationRoute { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Plugins/ProviderModelList.cs b/src/Presentation/SmartStore.Web/Administration/Models/Plugins/ProviderModelList.cs index 0a541bded3..a392054279 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Plugins/ProviderModelList.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Plugins/ProviderModelList.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Web; using System.Web.Mvc; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Plugins; namespace SmartStore.Admin.Models.Plugins { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollAnswerModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollAnswerModel.cs index fbd73dc2c8..9c8dc7b346 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollAnswerModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollAnswerModel.cs @@ -2,7 +2,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Polls; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Polls { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs index a368160db0..e5db97ff08 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Polls/PollModel.cs @@ -6,7 +6,7 @@ using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.Polls; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Polls { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionMappingModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionMappingModel.cs index b4f1d4b3d9..55bbf499c8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionMappingModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionMappingModel.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using SmartStore.Admin.Models.Customers; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Security { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionRecordModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionRecordModel.cs index 3f9d7d86bc..b444eaa04b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionRecordModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionRecordModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Security { @@ -6,5 +6,25 @@ public class PermissionRecordModel : ModelBase { public string Name { get; set; } public string SystemName { get; set; } - } + public string Category { get; set; } + + public string CategoryLabel + { + get + { + switch (Category) + { + case "PublicStore": + case "Standard": + return "label-success"; + + case "Plugin": + return ""; + + default: + return "label-info"; + } + } + } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/BlogSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/BlogSettingsModel.cs index 17b4578b0f..5c7a95c12a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/BlogSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/BlogSettingsModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Settings { @@ -20,6 +20,9 @@ public class BlogSettingsModel : ModelBase [SmartResourceDisplayName("Admin.Configuration.Settings.Blog.NumberOfTags")] public int NumberOfTags { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Blog.MaxAgeInDays")] + public int MaxAgeInDays { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Blog.ShowHeaderRSSUrl")] public bool ShowHeaderRssUrl { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs index a047782099..168e4b0a95 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Web.Mvc; +using SmartStore.Core.Domain.Catalog; using SmartStore.Web.Framework; namespace SmartStore.Admin.Models.Settings @@ -62,14 +63,32 @@ public CatalogSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowDefaultQuantityUnit")] public bool ShowDefaultQuantityUnit { get; set; } - #endregion + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowManufacturersOnHomepage")] + public bool ShowManufacturersOnHomepage { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ManufacturersBlockItemsToDisplay")] + public int ManufacturersBlockItemsToDisplay { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowManufacturerPictures")] + public bool ShowManufacturerPictures { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.HideManufacturerDefaultPictures")] + public bool HideManufacturerDefaultPictures { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.HideCategoryDefaultPictures")] + public bool HideCategoryDefaultPictures { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.HideProductDefaultPictures")] + public bool HideProductDefaultPictures { get; set; } + + #endregion - #region Product lists + #region Product lists - #region Navigation + #region Navigation - //filter - [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowProductsFromSubcategories")] + //filter + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowProductsFromSubcategories")] public bool ShowProductsFromSubcategories { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.IncludeFeaturedProductsInNormalLists")] @@ -93,8 +112,12 @@ public CatalogSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ExpandAllFilterCriteria")] public bool ExpandAllFilterCriteria { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowSubcategoriesAboveProductLists")] - public bool ShowSubcategoriesAboveProductLists { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.SortFilterResultsByMatches")] + public bool SortFilterResultsByMatches { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.SubCategoryDisplayType")] + public SubCategoryDisplayType SubCategoryDisplayType { get; set; } + public SelectList AvailableSubCategoryDisplayTypes { get; set; } #endregion @@ -107,12 +130,20 @@ public CatalogSettingsModel() public string DefaultViewMode { get; set; } public IList AvailableDefaultViewModes { get; private set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.DefaultSortOrderMode")] + public ProductSortingEnum DefaultSortOrder { get; set; } + public SelectList AvailableSortOrderModes { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.AllowProductViewModeChanging")] public bool AllowProductViewModeChanging { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.DefaultPageSizeOptions")] public string DefaultPageSizeOptions { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.PriceDisplayType")] + public PriceDisplayType PriceDisplayType { get; set; } + public SelectList AvailablePriceDisplayTypes { get; set; } + #endregion #region Products @@ -206,7 +237,10 @@ public CatalogSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.DisplayAllImagesNumber")] public int DisplayAllImagesNumber { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowDeliveryTimesInProductDetail")] + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowManufacturerPicturesInProductDetail")] + public bool ShowManufacturerPicturesInProductDetail { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowDeliveryTimesInProductDetail")] public bool ShowDeliveryTimesInProductDetail { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.DeliveryTimeIdForEmptyStock")] @@ -244,13 +278,19 @@ public CatalogSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ProductSearchAutoCompleteEnabled")] public bool ProductSearchAutoCompleteEnabled { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ShowProductImagesInSearchAutoComplete")] + public bool ShowProductImagesInSearchAutoComplete { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ProductSearchAutoCompleteNumberOfProducts")] public int ProductSearchAutoCompleteNumberOfProducts { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.SuppressSkuSearch")] public bool SuppressSkuSearch { get; set; } - #endregion + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.SearchDescriptions")] + public bool SearchDescriptions { get; set; } + + #endregion - } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ContentSliderSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ContentSliderSettingsModel.cs index 47ab3f0d02..e0d904bf4e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ContentSliderSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ContentSliderSettingsModel.cs @@ -1,5 +1,4 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; using SmartStore.Web.Framework.Localization; using System; using System.Collections.Generic; @@ -11,6 +10,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.ContentSlider; using SmartStore.Admin.Models.Stores; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.ContentSlider { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs index 56f374c6a6..f395584d6c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Web.Mvc; +using SmartStore.Core.Domain.Customers; using SmartStore.Web.Framework; namespace SmartStore.Admin.Models.Settings @@ -25,6 +26,15 @@ public partial class CustomerSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.UsernamesEnabled")] public bool UsernamesEnabled { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.CustomerNumberMethod")] + public CustomerNumberMethod CustomerNumberMethod { get; set; } + public SelectList AvailableCustomerNumberMethods { get; set; } + public IList AvailableRegisterCustomerRoles { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.CustomerNumberVisibility")] + public CustomerNumberVisibility CustomerNumberVisibility { get; set; } + public SelectList AvailableCustomerNumberVisibilities { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.AllowUsersToChangeUsernames")] public bool AllowUsersToChangeUsernames { get; set; } @@ -34,7 +44,10 @@ public partial class CustomerSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.UserRegistrationType")] public int UserRegistrationType { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.AllowCustomersToUploadAvatars")] + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.RegisterCustomerRole")] + public int RegisterCustomerRoleId { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.AllowCustomersToUploadAvatars")] public bool AllowCustomersToUploadAvatars { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.DefaultAvatarEnabled")] @@ -76,6 +89,9 @@ public partial class CustomerSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.StoreLastVisitedPage")] public bool StoreLastVisitedPage { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.DisplayPrivacyAgreementOnContactUs")] + public bool DisplayPrivacyAgreementOnContactUs { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.GenderEnabled")] public bool GenderEnabled { get; set; } @@ -122,7 +138,7 @@ public partial class CustomerSettingsModel public bool FaxEnabled { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.FaxRequired")] public bool FaxRequired { get; set; } - } + } public partial class AddressSettingsModel { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/DataExchangeSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/DataExchangeSettingsModel.cs new file mode 100644 index 0000000000..539505d733 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/DataExchangeSettingsModel.cs @@ -0,0 +1,28 @@ +using SmartStore.Web.Framework; + +namespace SmartStore.Admin.Models.Settings +{ + public partial class DataExchangeSettingsModel + { + #region General + + [SmartResourceDisplayName("Admin.Configuration.Settings.DataExchange.MaxFileNameLength")] + public int MaxFileNameLength { get; set; } + + #endregion + + #region Import + + [SmartResourceDisplayName("Admin.Configuration.Settings.DataExchange.ImageImportFolder")] + public string ImageImportFolder { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.DataExchange.ImageDownloadTimeout")] + public int ImageDownloadTimeout { get; set; } + + #endregion + + #region Export + + #endregion + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/GeneralCommonSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/GeneralCommonSettingsModel.cs index 5605695ad8..57332d0726 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/GeneralCommonSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/GeneralCommonSettingsModel.cs @@ -4,10 +4,11 @@ using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Seo; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; using SmartStore.Admin.Validators.Settings; using FluentValidation.Attributes; using SmartStore.Core.Domain.Localization; +using System.Collections; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Settings { @@ -77,14 +78,29 @@ public partial class SeoSettingsModel [AllowHtml] public string DefaultMetaDescription { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.ConvertNonWesternChars")] + [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.MetaRobotsContent")] + public string MetaRobotsContent { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.ConvertNonWesternChars")] public bool ConvertNonWesternChars { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.CanonicalUrlsEnabled")] + [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.AllowUnicodeCharsInUrls")] + public bool AllowUnicodeCharsInUrls { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.SeoNameCharConversion")] + public string SeoNameCharConversion { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.TestSeoNameCreation")] + public string TestSeoNameCreation { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.CanonicalUrlsEnabled")] public bool CanonicalUrlsEnabled { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.CanonicalHostNameRule")] public CanonicalHostNameRule CanonicalHostNameRule { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.ExtraRobotsDisallows")] + public string ExtraRobotsDisallows { get; set; } } public partial class SecuritySettingsModel @@ -153,6 +169,12 @@ public partial class PdfSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.PdfLogo")] [UIHint("Picture")] public int LogoPictureId { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.AttachOrderPdfToOrderPlacedEmail")] + public bool AttachOrderPdfToOrderPlacedEmail { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.GeneralCommon.AttachOrderPdfToOrderCompletedEmail")] + public bool AttachOrderPdfToOrderCompletedEmail { get; set; } } public partial class LocalizationSettingsModel diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/MediaSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/MediaSettingsModel.cs index da09a31233..164949eeff 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/MediaSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/MediaSettingsModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Settings { @@ -27,13 +27,16 @@ public MediaSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Media.ProductThumbPictureSizeOnProductDetailsPage")] public int ProductThumbPictureSizeOnProductDetailsPage { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Media.MessageProductThumbPictureSize")] + public int MessageProductThumbPictureSize { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Media.AssociatedProductPictureSize")] public int AssociatedProductPictureSize { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Media.BundledProductPictureSize")] public int BundledProductPictureSize { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.Media.CategoryThumbPictureSize")] + [SmartResourceDisplayName("Admin.Configuration.Settings.Media.CategoryThumbPictureSize")] public int CategoryThumbPictureSize { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Media.ManufacturerThumbPictureSize")] @@ -51,11 +54,10 @@ public MediaSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Media.MaximumImageSize")] public int MaximumImageSize { get; set; } - // codehint: sm-add [SmartResourceDisplayName("Admin.Configuration.Settings.Media.DefaultPictureZoomEnabled")] public bool DefaultPictureZoomEnabled { get; set; } - // codehint: sm-add (window || inner || lens) + // (window || inner || lens) [SmartResourceDisplayName("Admin.Configuration.Settings.Media.PictureZoomType")] public string PictureZoomType { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/NewsSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/NewsSettingsModel.cs index 4bc18260a2..f7a00fd03e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/NewsSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/NewsSettingsModel.cs @@ -22,6 +22,9 @@ public class NewsSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.News.NewsArchivePageSize")] public int NewsArchivePageSize { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.News.MaxAgeInDays")] + public int MaxAgeInDays { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.News.ShowHeaderRSSUrl")] public bool ShowHeaderRssUrl { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs index db5174dbcc..c0e73f70fb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Admin.Validators.Settings; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Settings { @@ -58,10 +59,17 @@ public OrderSettingsModel() public IList GiftCards_Deactivated_OrderStatuses { get; set; } public string PrimaryStoreCurrencyCode { get; set; } + public int StoreCount { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Order.OrderIdent")] public int? OrderIdent { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Order.DisplayOrdersOfAllStores")] + public bool DisplayOrdersOfAllStores { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Order.OrderListPageSize")] + public int OrderListPageSize { get; set; } + public IList Locales { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/RewardPointsSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/RewardPointsSettingsModel.cs index 57f5669179..1f73a57113 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/RewardPointsSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/RewardPointsSettingsModel.cs @@ -13,7 +13,10 @@ public class RewardPointsSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.RewardPoints.ExchangeRate")] public decimal ExchangeRate { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.RewardPoints.PointsForRegistration")] + [SmartResourceDisplayName("Admin.Configuration.Settings.RewardPoints.RoundDownRewardPoints")] + public bool RoundDownRewardPoints { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.RewardPoints.PointsForRegistration")] public int PointsForRegistration { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.RewardPoints.PointsForProductReview")] diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/SettingModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/SettingModel.cs index bba1b68981..10a11210da 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/SettingModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/SettingModel.cs @@ -2,7 +2,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Settings; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Settings { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs index 34d639051b..72d23ec58f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs @@ -1,9 +1,18 @@ -using SmartStore.Web.Framework; +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Core.Domain.Orders; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Localization; namespace SmartStore.Admin.Models.Settings { - public class ShoppingCartSettingsModel - { + public class ShoppingCartSettingsModel : ILocalizedModel + { + public ShoppingCartSettingsModel() + { + Locales = new List(); + } + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.DisplayCartAfterAddingProduct")] public bool DisplayCartAfterAddingProduct { get; set; } @@ -40,7 +49,10 @@ public class ShoppingCartSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.CrossSellsNumber")] public int CrossSellsNumber { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.EmailWishlistEnabled")] + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.RoundPricesDuringCalculation")] + public bool RoundPricesDuringCalculation { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.EmailWishlistEnabled")] public bool EmailWishlistEnabled { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.AllowAnonymousUsersToEmailWishlist")] @@ -76,8 +88,29 @@ public class ShoppingCartSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.ShowCommentBox")] public bool ShowCommentBox { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.RoundPricesDuringCalculation")] - public bool RoundPricesDuringCalculation { get; set; } - - } + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.ShowEsdRevocationWaiverBox")] + public bool ShowEsdRevocationWaiverBox { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.NewsLetterSubscription")] + public CheckoutNewsLetterSubscription NewsLetterSubscription { get; set; } + public SelectList AvailableNewsLetterSubscriptions { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.ThirdPartyEmailHandOver")] + public CheckoutThirdPartyEmailHandOver ThirdPartyEmailHandOver { get; set; } + public SelectList AvailableThirdPartyEmailHandOver { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.ThirdPartyEmailHandOverLabel")] + public string ThirdPartyEmailHandOverLabel { get; set; } + + public IList Locales { get; set; } + } + + + public class ShoppingCartSettingsLocalizedModel : ILocalizedModelLocal + { + public int LanguageId { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.ThirdPartyEmailHandOverLabel")] + public string ThirdPartyEmailHandOverLabel { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/StoreScopeConfigurationModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/StoreScopeConfigurationModel.cs index 492fda9281..2a4a08ecbc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/StoreScopeConfigurationModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/StoreScopeConfigurationModel.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Web.Mvc; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Settings { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/TaxSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/TaxSettingsModel.cs index f9395d9058..123d08cff6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/TaxSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/TaxSettingsModel.cs @@ -14,6 +14,7 @@ public TaxSettingsModel() ShippingTaxCategories = new List(); EuVatShopCountries = new List(); DefaultTaxAddress = new AddressModel(); + VatRequired = false; } [SmartResourceDisplayName("Admin.Configuration.Settings.Tax.PricesIncludeTax")] @@ -94,5 +95,8 @@ public TaxSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Tax.EuVatEmailAdminWhenNewVatSubmitted")] public bool EuVatEmailAdminWhenNewVatSubmitted { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.Tax.VatRequired")] + public bool VatRequired { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingMethodModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingMethodModel.cs index 134d4af1ea..945d9e0f46 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingMethodModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingMethodModel.cs @@ -4,17 +4,21 @@ using SmartStore.Admin.Validators.Shipping; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Shipping { - [Validator(typeof(ShippingMethodValidator))] - public class ShippingMethodModel : EntityModelBase, ILocalizedModel + [Validator(typeof(ShippingMethodValidator))] + public class ShippingMethodModel : TabbableModel, ILocalizedModel { public ShippingMethodModel() { Locales = new List(); + FilterConfigurationUrls = new List(); } + + public IList FilterConfigurationUrls { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Shipping.Methods.Fields.Name")] [AllowHtml] public string Name { get; set; } @@ -43,6 +47,5 @@ public class ShippingMethodLocalizedModel : ILocalizedModelLocal [SmartResourceDisplayName("Admin.Configuration.Shipping.Methods.Fields.Description")] [AllowHtml] public string Description { get; set; } - } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingMethodRestrictionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingMethodRestrictionModel.cs deleted file mode 100644 index 1fa24bac73..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingMethodRestrictionModel.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using SmartStore.Admin.Models.Directory; -using SmartStore.Web.Framework.Mvc; - -namespace SmartStore.Admin.Models.Shipping -{ - public class ShippingMethodRestrictionModel : ModelBase - { - public ShippingMethodRestrictionModel() - { - AvailableShippingMethods = new List(); - AvailableCountries = new List(); - Restricted = new Dictionary>(); - } - public IList AvailableShippingMethods { get; set; } - public IList AvailableCountries { get; set; } - - //[country id] / [shipping method id] / [restricted] - public IDictionary> Restricted { get; set; } - } -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingRateComputationMethodModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingRateComputationMethodModel.cs index ef087fe849..5e2f33d35a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingRateComputationMethodModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingRateComputationMethodModel.cs @@ -2,7 +2,7 @@ using System.Web.Routing; using SmartStore.Core; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Plugins; namespace SmartStore.Admin.Models.Shipping { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/ShoppingCart/ShoppingCartItemModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/ShoppingCart/ShoppingCartItemModel.cs index 5b9d98cd75..d19bfd7aec 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/ShoppingCart/ShoppingCartItemModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/ShoppingCart/ShoppingCartItemModel.cs @@ -1,6 +1,6 @@ using System; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.ShoppingCart { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/ShoppingCart/ShoppingCartModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/ShoppingCart/ShoppingCartModel.cs index be26f6bcaf..64f51d7b09 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/ShoppingCart/ShoppingCartModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/ShoppingCart/ShoppingCartModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.ShoppingCart { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Stores/StoreModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Stores/StoreModel.cs index f64293b561..cb89aad57a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Stores/StoreModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Stores/StoreModel.cs @@ -1,9 +1,11 @@ -using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Admin.Validators.Stores; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Stores { @@ -42,5 +44,43 @@ public partial class StoreModel : EntityModelBase [SmartResourceDisplayName("Admin.Configuration.Stores.Fields.ContentDeliveryNetwork")] [AllowHtml] public string ContentDeliveryNetwork { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Stores.Fields.PrimaryStoreCurrencyId")] + public int PrimaryStoreCurrencyId { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Stores.Fields.PrimaryStoreCurrencyId")] + public string PrimaryStoreCurrencyName + { + get + { + try + { + return AvailableCurrencies.First(x => x.Value == PrimaryStoreCurrencyId.ToString()).Text; + } + catch { } + + return null; + } + } + + [SmartResourceDisplayName("Admin.Configuration.Stores.Fields.PrimaryExchangeRateCurrencyId")] + public int PrimaryExchangeRateCurrencyId { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Stores.Fields.PrimaryExchangeRateCurrencyId")] + public string PrimaryExchangeRateCurrencyName + { + get + { + try + { + return AvailableCurrencies.First(x => x.Value == PrimaryExchangeRateCurrencyId.ToString()).Text; + } + catch { } + + return null; + } + } + + public List AvailableCurrencies { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Tasks/ScheduleTaskModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Tasks/ScheduleTaskModel.cs index bf8c17662b..82a3fe9cb1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Tasks/ScheduleTaskModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Tasks/ScheduleTaskModel.cs @@ -1,8 +1,9 @@ -using System.Web.Mvc; +using System; +using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Admin.Validators.Tasks; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Tasks { @@ -13,29 +14,47 @@ public partial class ScheduleTaskModel : EntityModelBase [AllowHtml] public string Name { get; set; } - [SmartResourceDisplayName("Admin.System.ScheduleTasks.Seconds")] - public int Seconds { get; set; } + [SmartResourceDisplayName("Admin.System.ScheduleTasks.CronExpression")] + public string CronExpression { get; set; } + public string CronDescription { get; set; } + [SmartResourceDisplayName("Admin.System.ScheduleTasks.Enabled")] public bool Enabled { get; set; } [SmartResourceDisplayName("Admin.System.ScheduleTasks.StopOnError")] public bool StopOnError { get; set; } - [SmartResourceDisplayName("Admin.System.ScheduleTasks.LastStart")] - public string LastStartUtc { get; set; } + [SmartResourceDisplayName("Admin.System.ScheduleTasks.LastStart")] + public DateTime? LastStart { get; set; } + public string LastStartPretty { get; set; } [SmartResourceDisplayName("Admin.System.ScheduleTasks.LastEnd")] - public string LastEndUtc { get; set; } + public DateTime? LastEnd { get; set; } + public string LastEndPretty { get; set; } [SmartResourceDisplayName("Admin.System.ScheduleTasks.LastSuccess")] - public string LastSuccessUtc { get; set; } + public DateTime? LastSuccess { get; set; } + public string LastSuccessPretty { get; set; } + + [SmartResourceDisplayName("Admin.System.ScheduleTasks.NextRun")] + public DateTime? NextRun { get; set; } + public string NextRunPretty { get; set; } + + public bool IsOverdue { get; set; } [SmartResourceDisplayName("Common.Error")] public string LastError { get; set; } + [SmartResourceDisplayName("Common.Duration")] public string Duration { get; set; } public bool IsRunning { get; set; } + public int? ProgressPercent { get; set; } + public string ProgressMessage { get; set; } + public string CancelUrl { get; set; } + public string EditUrl { get; set; } + public string ExecuteUrl { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Tax/TaxCategoryModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Tax/TaxCategoryModel.cs index 53f6076bee..156a3a2b81 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Tax/TaxCategoryModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Tax/TaxCategoryModel.cs @@ -2,7 +2,7 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.Tax; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Tax { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Tax/TaxProviderModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Tax/TaxProviderModel.cs index ecfae1c422..7f689bf4de 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Tax/TaxProviderModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Tax/TaxProviderModel.cs @@ -1,7 +1,7 @@ using System.Web.Mvc; using System.Web.Routing; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Plugins; namespace SmartStore.Admin.Models.Tax { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Themes/ConfigureThemeModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Themes/ConfigureThemeModel.cs index 0d3d6012e3..1770e2fbf1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Themes/ConfigureThemeModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Themes/ConfigureThemeModel.cs @@ -3,7 +3,7 @@ using System.Web.Routing; using SmartStore.Core.Domain.Stores; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Themes { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Themes/ThemeListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Themes/ThemeListModel.cs index 0212eefa36..b33435549b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Themes/ThemeListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Themes/ThemeListModel.cs @@ -4,7 +4,7 @@ using System.Web; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Themes; +using SmartStore.Web.Framework.Theming; using SmartStore.Core.Themes; namespace SmartStore.Admin.Models.Themes @@ -35,6 +35,9 @@ public ThemeListModel() [SmartResourceDisplayName("Admin.Configuration.Themes.Option.AllowCustomerToSelectTheme")] public bool AllowCustomerToSelectTheme { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Themes.Option.SaveThemeChoiceInCookie")] + public bool SaveThemeChoiceInCookie { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Themes.Option.MobileDevicesSupported")] public bool MobileDevicesSupported { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicListModel.cs index ec82ee0bae..fbe00d3d32 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicListModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Topics { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicModel.cs index 43de02db29..a971137e9a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicModel.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; @@ -7,11 +6,11 @@ using SmartStore.Admin.Validators.Topics; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Admin.Models.Topics { - [Validator(typeof(TopicValidator))] + [Validator(typeof(TopicValidator))] public class TopicModel : TabbableModel, ILocalizedModel { #region widget zone names @@ -47,9 +46,9 @@ public TopicModel() AvailableTitleTags.Add(new SelectListItem { Text = "span", Value = "span" }); } - //Store mapping [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] public bool LimitedToStores { get; set; } + [SmartResourceDisplayName("Admin.Common.Store.AvailableFor")] public List AvailableStores { get; set; } public int[] SelectedStoreIds { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs new file mode 100644 index 0000000000..6d39c9577a --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.UrlRecord +{ + public partial class UrlRecordListModel : ModelBase + { + public int GridPageSize { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.Name")] + [AllowHtml] + public string SeName { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.EntityName")] + public string EntityName { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.EntityId")] + public int? EntityId { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.IsActive")] + public bool? IsActive { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.Language")] + public int? LanguageId { get; set; } + public List AvailableLanguages { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs new file mode 100644 index 0000000000..15d9e61359 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.UrlRecord +{ + public partial class UrlRecordModel : EntityModelBase + { + [Required, AllowHtml, SmartResourceDisplayName("Admin.System.SeNames.Name")] + public string Slug { get; set; } + + [Required, SmartResourceDisplayName("Admin.System.SeNames.EntityName")] + public string EntityName { get; set; } + + [Required, SmartResourceDisplayName("Admin.System.SeNames.EntityId")] + public int EntityId { get; set; } + + [SmartResourceDisplayName("Admin.Common.Entity")] + public string EntityUrl { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.IsActive")] + public bool IsActive { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.Language")] + public int LanguageId { get; set; } + public List AvailableLanguages { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.Language")] + public string Language { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.SlugsPerEntity")] + public int SlugsPerEntity { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.common.js b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.common.js index 5451a1ad70..73618d32bd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Scripts/admin.common.js +++ b/src/Presentation/SmartStore.Web/Administration/Scripts/admin.common.js @@ -54,7 +54,71 @@ var Admin = { } } }); - } + }, + + TaskWatcher: (function () { + var interval; + + return { + startWatching: function (opts) { + function poll() { + $.ajax({ + cache: false, + type: 'POST', + global: false, + url: opts.pollUrl, + dataType: 'json', + success: function (data) { + data = data || []; + var runningElements = []; + $.each(data, function (i, task) { + var el = $(opts.elementsSelector + '[data-task-id=' + task.id + ']'); + if (el.length) { + runningElements.push(el[0]); + if (el.data('running') && el.text()) { + // already running + el.find('.text').text(task.message || opts.defaultProgressMessage); + el.find('.percent').text(task.percent >> 0 ? task.percent + ' %' : ""); + } + else { + // new task + var row1 = $('
    ').appendTo(el); + row1.append($('
    ' + (task.message || opts.defaultProgressMessage) + '
    ')); + row1.append($('
    ' + (task.percent >> 0 ? task.percent + ' %' : "") + '
    ')); + var row2 = $('
    ').appendTo(el); + el.attr('data-running', 'true').data('running', true); + if (_.isFunction(opts.onTaskStarted)) { + opts.onTaskStarted(task, el); + } + el.removeClass('hide'); + } + } + }); + + // remove runningElements for finished tasks (the ones currently running but are not in 'runningElements' + var currentlyRunningElements = opts.context.find(opts.elementsSelector + '[data-running=true]'); + $.each(currentlyRunningElements, function (i, el) { + var shouldRun = _.find(runningElements, function (val) { return val == el; }); + if (!shouldRun) { + // restore element to it's init state + var jel = $(el); + jel.addClass('hide').html('').attr('data-running', 'false').data('running', false); + if (_.isFunction(opts.onTaskCompleted)) { + opts.onTaskCompleted(jel.data('task-id'), jel); + } + } + }); + }, + error: function (xhr, ajaxOptions, thrownError) { + window.clearInterval(interval); + } + }); + } + window.setTimeout(poll, 50); + interval = window.setInterval(poll, 2500); + } + } + })() }; diff --git a/src/Presentation/SmartStore.Web/Administration/Scripts/smartstore.download.js b/src/Presentation/SmartStore.Web/Administration/Scripts/smartstore.download.js new file mode 100644 index 0000000000..8a8c9b2052 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Scripts/smartstore.download.js @@ -0,0 +1,141 @@ +; + +// Unobtrusive handler for 'Download' editor template +(function ($, window, document, undefined) { + + var pluginName = 'downloadEditor'; + + function DownloadEditor(element, options) { + var self = this; + + // to access the DOM elem from outside of this constructor + this.element = element; + var el = this.el = $(element); + + // 'this.options' ensures we can reference the merged instance options from outside + var opts = this.options = $.extend({}, options); + + this.togglePanel = function (useUrl) { + if (useUrl && el.hasClass("minimal")) + return; + + if (useUrl == el.data("use-url")) + return; + + var i = el.find('.panel-switcher > .dropdown-toggle > .fa'); + + if (useUrl) { + i.removeClass('fa-upload').addClass('fa-globe'); + el.find('.panel-upload').hide(); + el.find('.panel-url').show(); + el.find('.toggle-file').parent().removeClass("disabled"); + el.find('.toggle-url').parent().addClass("disabled"); + } + else { + i.removeClass('fa-globe').addClass('fa-upload'); + el.find('.panel-url').hide(); + el.find('.panel-upload').show(); + el.find('.toggle-file').parent().addClass("disabled"); + el.find('.toggle-url').parent().removeClass("disabled"); + } + + el.data("use-url", useUrl); + }; + + this.init = function () { + var elHidden = el.find('.hidden'), + elRemove = el.find('.remove'), + elSaveUrl = el.find('.save-download-url'); + + // handle panel switcher buttons + el.find('.toggle-file, .toggle-url').on('click', function (e) { + e.preventDefault(); + self.togglePanel($(this).hasClass('toggle-url')); + }); + + // init file uploader + el.find('.fileupload').fileupload({ + url: el.data('upload-url'), + dataType: 'json', + + done: function (e, data) { + var result = data.result; + if (result.success) { + el.replaceWith($(result.html)); + } + }, + + error: function (jqXHR, textStatus, errorThrown) { + if (errorThrown === 'abort') { + displayNotification('File Upload has been canceled'); + } + } + }); + + el.find('.download-url-value').on('input propertychange paste', function (e) { + var txt = $(this); + var hasVal = !!(txt.val()) && txt.val() != txt.data('value'); + var btn = txt.next(); + btn.attr('disabled', hasVal ? null : "disabled"); + + var i = elSaveUrl.find('.fa'); + i.toggleClass('fa-save', hasVal).toggleClass('fa-check', !hasVal); + + }); + + // Download removal (transient) + elRemove.click(function (e) { + e.preventDefault(); + + $.ajax({ + cache: false, + type: 'POST', + url: el.data('delete-url'), + dataType: 'json', + success: function (data) { + if (data.success) { + // just update editor html (with init state) + el.replaceWith($(data.html)); + } + } + }); + }); + + // Save download url + elSaveUrl.click(function (e) { + e.preventDefault(); + + var url = el.find('.download-url-value').val(); + if (!url) + return false; + + $.ajax({ + cache: false, + type: 'POST', + url: $(this).attr('href'), + data: { "downloadUrl": url }, + dataType: 'json', + success: function (data) { + el.replaceWith($(data.html)); + } + }); + }); + }; + + this.initialized = false; + this.init(); + this.initialized = true; + } + + $[pluginName] = { defaults: { } }; + + $.fn[pluginName] = function (options) { + return this.each(function () { + if (!$.data(this, pluginName)) { + options = $.extend({}, $[pluginName].defaults, options); + $.data(this, pluginName, new DownloadEditor(this, options)); + } + }); + } + +})(jQuery, window, document); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index d345bd4f15..603f623aa1 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -1,5 +1,5 @@  - + Debug @@ -13,7 +13,7 @@ Properties SmartStore.Admin SmartStore.Admin - v4.5 + v4.5.1 false true @@ -38,6 +38,7 @@ true true 1e5b888b + true @@ -63,38 +64,51 @@ False ..\..\..\packages\Antlr.3.5.0.2\lib\Antlr3.Runtime.dll - - False - ..\..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll + + ..\..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + True - False - ..\..\..\packages\Autofac.Mvc5.3.3.1\lib\net45\Autofac.Integration.Mvc.dll + ..\..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll + True - - False - ..\..\..\packages\AutoMapper.3.2.1\lib\net40\AutoMapper.dll + + ..\..\..\packages\AutoMapper.4.1.1\lib\net45\AutoMapper.dll + True - - False - ..\..\..\packages\AutoMapper.3.2.1\lib\net40\AutoMapper.Net4.dll + + ..\..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + True - - False - ..\..\..\packages\FluentValidation.5.0.0.1\lib\Net40\FluentValidation.dll + + ..\..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + True + + + ..\..\..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll + True 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 + + False - ..\..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + ..\..\..\..\lib\SmartStore.Licensing\SmartStore.Licensing.dll + + + + False + ..\..\..\packages\Microsoft.AspNet.WebApi.Client.5.1.2\lib\net45\System.Net.Http.Formatting.dll + - @@ -105,13 +119,14 @@ + False - ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False @@ -119,20 +134,20 @@ False - ..\..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll @@ -166,6 +181,8 @@ + + @@ -197,8 +214,10 @@ + + @@ -209,14 +228,15 @@ + + - @@ -225,23 +245,37 @@ - + - - + + + + + + + + + + + + + + + + @@ -296,7 +330,6 @@ - @@ -349,6 +382,10 @@ + + + + @@ -394,29 +431,268 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - @@ -445,18 +721,22 @@ - + + + + + + - - Always - + + Designer @@ -471,13 +751,46 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -677,15 +990,6 @@ - - - - - - - - - @@ -704,9 +1008,6 @@ - - - @@ -876,9 +1177,6 @@ - - - @@ -984,9 +1282,6 @@ - - - @@ -1074,9 +1369,6 @@ - - - @@ -1114,9 +1406,6 @@ - - - @@ -1180,9 +1469,6 @@ - - - @@ -1195,15 +1481,9 @@ - - - - - - @@ -1269,5 +1549,8 @@ - + + + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/CsvConfigurationValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/CsvConfigurationValidator.cs new file mode 100644 index 0000000000..4910a98192 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/CsvConfigurationValidator.cs @@ -0,0 +1,35 @@ +using System.Linq; +using FluentValidation; +using SmartStore.Admin.Models.DataExchange; +using SmartStore.Services.DataExchange.Csv; +using SmartStore.Services.Localization; + +namespace SmartStore.Admin.Validators.DataExchange +{ + public partial class CsvConfigurationValidator : AbstractValidator + { + public CsvConfigurationValidator(ILocalizationService localization) + { + RuleFor(x => x.Delimiter) + .Must(x => !CsvConfiguration.PresetCharacters.Contains(x.ToChar(true))) + .WithMessage(localization.GetResource("Admin.DataExchange.Csv.Delimiter.Validation")); + + RuleFor(x => x.Quote) + .Must(x => !CsvConfiguration.PresetCharacters.Contains(x.ToChar(true))) + .WithMessage(localization.GetResource("Admin.DataExchange.Csv.Quote.Validation")); + + RuleFor(x => x.Escape) + .Must(x => !CsvConfiguration.PresetCharacters.Contains(x.ToChar(true))) + .WithMessage(localization.GetResource("Admin.DataExchange.Csv.Escape.Validation")); + + + RuleFor(x => x.Escape) + .Must((model, x) => x != model.Delimiter) + .WithMessage(localization.GetResource("Admin.DataExchange.Csv.EscapeDelimiter.Validation")); + + RuleFor(x => x.Quote) + .Must((model, x) => x != model.Delimiter) + .WithMessage(localization.GetResource("Admin.DataExchange.Csv.QuoteDelimiter.Validation")); + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs new file mode 100644 index 0000000000..e23171230b --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs @@ -0,0 +1,37 @@ +using FluentValidation; +using SmartStore.Admin.Models.DataExchange; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Services.Localization; + +namespace SmartStore.Admin.Validators.DataExchange +{ + public partial class ExportDeploymentValidator : AbstractValidator + { + public ExportDeploymentValidator(ILocalizationService localization) + { + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage(localization.GetResource("Admin.Validation.Name")); + + RuleFor(x => x.EmailAddresses) + .NotEmpty() + .When(x => x.DeploymentType == ExportDeploymentType.Email) + .WithMessage(localization.GetResource("Admin.Validation.EmailAddress")); + + RuleFor(x => x.Url) + .NotEmpty() + .When(x => x.DeploymentType == ExportDeploymentType.Http || x.DeploymentType == ExportDeploymentType.Ftp) + .WithMessage(localization.GetResource("Admin.Validation.Url")); + + RuleFor(x => x.Username) + .NotEmpty() + .When(x => x.DeploymentType == ExportDeploymentType.Ftp) + .WithMessage(localization.GetResource("Admin.Validation.UsernamePassword")); + + RuleFor(x => x.Password) + .NotEmpty() + .When(x => x.DeploymentType == ExportDeploymentType.Ftp) + .WithMessage(localization.GetResource("Admin.Validation.UsernamePassword")); + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs new file mode 100644 index 0000000000..dc15762b5c --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs @@ -0,0 +1,36 @@ +using FluentValidation; +using SmartStore.Admin.Models.DataExchange; +using SmartStore.Services.Localization; + +namespace SmartStore.Admin.Validators.DataExchange +{ + public partial class ExportProfileValidator : AbstractValidator + { + public ExportProfileValidator(ILocalizationService localization) + { + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage(localization.GetResource("Admin.Validation.Name")); + + RuleFor(x => x.FolderName) + .Must(x => x.HasValue() && !x.IsCaseInsensitiveEqual("con")) + .WithMessage(localization.GetResource("Admin.DataExchange.Export.FolderName.Validate")); + + RuleFor(x => x.FileNamePattern) + .NotEmpty() + .WithMessage(localization.GetResource("Admin.DataExchange.Export.FileNamePattern.Validate")); + + RuleFor(x => x.Offset) + .GreaterThanOrEqualTo(0) + .WithMessage(localization.GetResource("Admin.DataExchange.Export.Partition.Validate")); + + RuleFor(x => x.Limit) + .GreaterThanOrEqualTo(0) + .WithMessage(localization.GetResource("Admin.DataExchange.Export.Partition.Validate")); + + RuleFor(x => x.BatchSize) + .GreaterThanOrEqualTo(0) + .WithMessage(localization.GetResource("Admin.DataExchange.Export.Partition.Validate")); + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ImportProfileValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ImportProfileValidator.cs new file mode 100644 index 0000000000..95277d0d85 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ImportProfileValidator.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using SmartStore.Admin.Models.DataExchange; +using SmartStore.Services.Localization; + +namespace SmartStore.Admin.Validators.DataExchange +{ + public partial class ImportProfileValidator : AbstractValidator + { + public ImportProfileValidator(ILocalizationService localization) + { + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage(localization.GetResource("Admin.Validation.Name")); + + RuleFor(x => x.KeyFieldNames) + .NotEmpty() + .When(x => x.Id != 0) + .WithMessage(localization.GetResource("Admin.DataExchange.Import.Validate.OneKeyFieldRequired")); + + RuleFor(x => x.Skip) + .GreaterThanOrEqualTo(0) + .WithMessage(localization.GetResource("Admin.Common.SkipAndTakeGreaterThanOrEqualZero")); + + RuleFor(x => x.Take) + .GreaterThanOrEqualTo(0) + .WithMessage(localization.GetResource("Admin.Common.SkipAndTakeGreaterThanOrEqualZero")); + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/Localization/LanguageValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/Localization/LanguageValidator.cs index ea6ba1314e..891c093efc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/Localization/LanguageValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/Localization/LanguageValidator.cs @@ -9,29 +9,32 @@ public partial class LanguageValidator : AbstractValidator { public LanguageValidator(ILocalizationService localizationService) { - RuleFor(x => x.Name).NotEmpty().WithMessage(localizationService.GetResource("Admin.Configuration.Languages.Fields.Name.Required")); + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage(localizationService.GetResource("Admin.Configuration.Languages.Fields.Name.Required")); + RuleFor(x => x.LanguageCulture) .Must(x => - { - try - { - var culture = new CultureInfo(x); - return culture != null; - } - catch - { - return false; - } - }) + { + try + { + var culture = new CultureInfo(x); + return culture != null; + } + catch + { + return false; + } + }) .WithMessage(localizationService.GetResource("Admin.Configuration.Languages.Fields.LanguageCulture.Validation")); RuleFor(x => x.UniqueSeoCode) .NotNull() .WithMessage(localizationService.GetResource("Admin.Configuration.Languages.Fields.UniqueSeoCode.Required")); + RuleFor(x => x.UniqueSeoCode) .Length(2) .WithMessage(localizationService.GetResource("Admin.Configuration.Languages.Fields.UniqueSeoCode.Length")); - } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs index eab92160eb..547872cac6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs @@ -11,8 +11,13 @@ public OrderSettingsValidator(ILocalizationService localizationService) { RuleFor(x => x.GiftCards_Activated_OrderStatusId).NotEqual((int)OrderStatus.Pending) .WithMessage(localizationService.GetResource("Admin.Configuration.Settings.RewardPoints.PointsForPurchases_Awarded.Pending")); + RuleFor(x => x.GiftCards_Deactivated_OrderStatusId).NotEqual((int)OrderStatus.Pending) .WithMessage(localizationService.GetResource("Admin.Configuration.Settings.RewardPoints.PointsForPurchases_Canceled.Pending")); - } + + RuleFor(x => x.OrderListPageSize) + .GreaterThan(0) + .WithMessage(localizationService.GetResource("Admin.Validation.ValueGreaterZero")); + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/Tasks/ScheduleTaskValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/Tasks/ScheduleTaskValidator.cs index 102e3bc050..eb87eba7b4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/Tasks/ScheduleTaskValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/Tasks/ScheduleTaskValidator.cs @@ -2,6 +2,7 @@ using SmartStore.Admin.Models.Directory; using SmartStore.Admin.Models.Tasks; using SmartStore.Services.Localization; +using SmartStore.Services.Tasks; namespace SmartStore.Admin.Validators.Tasks { @@ -9,8 +10,8 @@ public partial class ScheduleTaskValidator : AbstractValidator x.Name).NotNull().WithMessage(localizationService.GetResource("Admin.System.ScheduleTasks.Name.Required")); - RuleFor(x => x.Seconds).GreaterThan(0).WithMessage(localizationService.GetResource("Admin.System.ScheduleTasks.Seconds.Positive")); + RuleFor(x => x.Name).NotEmpty().WithMessage(localizationService.GetResource("Admin.System.ScheduleTasks.Name.Required")); + RuleFor(x => x.CronExpression).Must(x => CronExpression.IsValid(x)).WithMessage(localizationService.GetResource("Admin.System.ScheduleTasks.InvalidCronExpression")); } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml index 08e1b31e6b..dbd3b7329e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ActivityLog/ListLogs.cshtml @@ -1,9 +1,6 @@ @model ActivityLogSearchModel @using Telerik.Web.Mvc; -@{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title +@{ ViewBag.Title = T("Admin.Configuration.ActivityLog.ActivityLog").Text; } @using (Html.BeginForm()) @@ -14,30 +11,42 @@ @T("Admin.Configuration.ActivityLog.ActivityLog")
    - -  @T("Admin.Common.Clear") - + +
    -

    -

    - - - - - - - - - + +

    + +
    - @Html.SmartLabelFor(model => model.CreatedOnFrom) - - @Html.EditorFor(model => model.CreatedOnFrom) -
    - @Html.SmartLabelFor(model => model.CreatedOnTo) - - @Html.EditorFor(model => model.CreatedOnTo) -
    + + + + + + + + + + + + - - - - + + + +
    + @Html.SmartLabelFor(model => model.CreatedOnFrom) + + @Html.EditorFor(model => model.CreatedOnFrom) +
    + @Html.SmartLabelFor(model => model.CreatedOnTo) + + @Html.EditorFor(model => model.CreatedOnTo) +
    + @Html.SmartLabelFor(model => model.ActivityLogTypeId) + + @Html.DropDownListFor(ModelBase => ModelBase.ActivityLogTypeId, Model.ActivityLogType, T("Common.Unspecified")) +
    @Html.SmartLabelFor(model => model.CustomerEmail) @@ -46,14 +55,14 @@ @Html.EditorFor(model => model.CustomerEmail)
    - @Html.SmartLabelFor(model => model.ActivityLogTypeId) - - @Html.DropDownList("ActivityLogTypeId", Model.ActivityLogType) -
    + @Html.SmartLabelFor(model => model.CustomerSystemAccount) + + @Html.EditorFor(model => Model.CustomerSystemAccount) +
      @@ -66,62 +75,145 @@
    +

    +
    @(Html.Telerik().Grid() - .Name("activityLog-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("Id"); - }) - .ClientEvents(events => events.OnDataBinding("onDataBinding")) - .Columns(columns => - { - columns.Bound(x => x.ActivityLogTypeName); - columns.Bound(x => x.CustomerId) - .Template(x => Html.ActionLink(x.CustomerEmail, "Edit", "Customer", new { id = x.CustomerId }, new { })) - .ClientTemplate("\"><#= CustomerEmail #>"); - columns.Bound(x => x.Comment); - columns.Bound(x => x.CreatedOn) - .Width(180); - columns.Command(commands => - { - commands.Delete().Localize(T); - }).Width(90); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("ListLogs", "ActivityLog") - .Delete("AcivityLogDelete", "ActivityLog"); - }) - .EnableCustomBinding(true)) + .Name("activityLog-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .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.ActivityLogTypeName); + columns.Bound(x => x.Comment); + columns.Bound(x => x.CustomerId) + .ClientTemplate("\"><#= CustomerEmail #>"); + columns.Bound(x => x.IsSystemAccount) + .ClientTemplate(" <#= SystemAccountName #>") + .Width(220); + columns.Bound(x => x.CreatedOn) + .Width(200); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("ListActivityLogs", "ActivityLog"); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .PreserveGridState() + .EnableCustomBinding(true))
    + } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/List.cshtml index 04cb11f0cf..08b0f9c35e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/List.cshtml @@ -42,6 +42,7 @@ }) .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Affiliate")) + .PreserveGridState() .EnableCustomBinding(true)) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/_CreateOrUpdate.cshtml index 0f6a803991..36bc46357c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Affiliate/_CreateOrUpdate.cshtml @@ -13,7 +13,7 @@ x.Add().Text(T("Admin.Affiliates.Orders").Text).Content(TabOrders()); } - //generate an event + // generate an event EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "affiliate-edit", this.Html, this.Model)); }) @@ -76,6 +76,7 @@ { dataBinding.Ajax().Select("AffiliatedCustomerList", "Affiliate", new { affiliateId = Model.Id }); }) + .PreserveGridState() .EnableCustomBinding(true)) } @@ -101,5 +102,6 @@ { dataBinding.Ajax().Select("AffiliatedOrderList", "Affiliate", new { affiliateId = Model.Id }); }) + .PreserveGridState() .EnableCustomBinding(true)) } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Blog/Comments.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Blog/Comments.cshtml index b2b279fdba..b54d67dd87 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Blog/Comments.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Blog/Comments.cshtml @@ -63,6 +63,7 @@ settingBuilder = settingBuilder.Delete("CommentDelete", "Blog"); } }) + .PreserveGridState() .EnableCustomBinding(true)) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Blog/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Blog/Edit.cshtml index 948fead30b..2d35a52848 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Blog/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Blog/Edit.cshtml @@ -1,6 +1,5 @@ @model BlogPostModel @{ - //page title ViewBag.Title = T("Admin.ContentManagement.Blog.BlogPosts.EditBlogPostDetails").Text; } @@ -11,9 +10,13 @@ @T("Admin.ContentManagement.Blog.BlogPosts.EditBlogPostDetails") - @Model.Title @Html.ActionLink("(" + T("Admin.ContentManagement.Blog.BlogPosts.BackToList") + ")", "List")
    + @Html.Widget("admin_button_toolbar_before") + + + @Html.Widget("admin_button_toolbar_after")
    @Html.Partial("_CreateOrUpdate", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Blog/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Blog/List.cshtml index ca72a0a438..927508a805 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Blog/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Blog/List.cshtml @@ -12,7 +12,11 @@ @T("Admin.ContentManagement.Blog.BlogPosts")
    + @Html.Widget("admin_button_toolbar_before") +  @T("Admin.Common.AddNew") + + @Html.Widget("admin_button_toolbar_after")
    @@ -39,6 +43,7 @@ }) .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Blog")) + .PreserveGridState() .EnableCustomBinding(true)) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Blog/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Blog/_CreateOrUpdate.cshtml index 71d80f25b4..effce8793b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Blog/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Blog/_CreateOrUpdate.cshtml @@ -126,6 +126,10 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Campaign/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Campaign/List.cshtml index bdb63f8659..f56b5915fa 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Campaign/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Campaign/List.cshtml @@ -1,5 +1,4 @@ -@model Telerik.Web.Mvc.GridModel -@using Telerik.Web.Mvc.UI +@using Telerik.Web.Mvc.UI @{ ViewBag.Title = T("Admin.Promotions.Campaigns").Text; } @@ -15,24 +14,23 @@
    @Html.EditorFor(model => model.SeName) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "BlogPost", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/Create.cshtml index e3a8f40637..d878037e73 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/Create.cshtml @@ -7,7 +7,8 @@ {
    - @T("Admin.Catalog.Categories.AddNew") @Html.ActionLink("(" + T("Admin.Catalog.Categories.BackToList") + ")", "List") + @T("Admin.Catalog.Categories.AddNew") + (@T("Admin.Catalog.Categories.BackToList"))
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/Edit.cshtml index 8f9c47460a..57367cdcfe 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/Edit.cshtml @@ -1,21 +1,25 @@ @model CategoryModel @{ - //page title ViewBag.Title = T("Admin.Catalog.Categories.EditCategoryDetails").Text; } @using (Html.BeginForm(null, null, FormMethod.Post, new { id = "category-form" })) {
    - @T("Admin.Catalog.Categories.EditCategoryDetails") - @Model.Name @Html.ActionLink("(" + T("Admin.Catalog.Categories.BackToList") + ")", "List") + @T("Admin.Catalog.Categories.EditCategoryDetails") - @Model.Name + (@T("Admin.Catalog.Categories.BackToList"))
    + @Html.Widget("admin_button_toolbar_before") + + + @Html.Widget("admin_button_toolbar_after")
    @Html.Partial("_CreateOrUpdate", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml index 19df941614..a57724aeaf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml @@ -1,9 +1,6 @@ @model CategoryListModel @using Telerik.Web.Mvc.UI -@{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title +@{ ViewBag.Title = T("Admin.Catalog.Categories.Manage").Text; }
    @@ -12,12 +9,15 @@ @T("Admin.Catalog.Categories.Manage") @Html.ActionLink("(" + T("Admin.Catalog.Categories.SwitchToTreeView") + ")", "Tree")
    -  @T("Admin.Common.AddNew") - @T("Admin.Common.ExportToXml") - + @Html.Widget("admin_button_toolbar_before") + +  @T("Admin.Common.AddNew") + + @Html.Widget("admin_button_toolbar_after")
    -
    - @(Html.Telerik().Grid(Model.Data) + @(Html.Telerik().Grid() .Name("campaigns-grid") .Columns(columns => { columns.Bound(x => x.Name) - .Width(500) .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) .ClientTemplate("\"><#= Name #>"); columns.Bound(x => x.Subject); columns.Bound(x => x.LimitedToStores) - .Width(180) .Template(item => @Html.SymbolForBool(item.LimitedToStores)) .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden((int)ViewData["StoreCount"] <= 1) .Centered(); - columns.Bound(x => x.CreatedOn) - .Width(180); + columns.Bound(x => x.CreatedOn); }) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Campaign")) + .PreserveGridState() .EnableCustomBinding(true))
    + +
    + @if (Model.AvailableStores.Count > 1) + { + + + + + }
    @Html.SmartLabelFor(model => model.SearchCategoryName) @@ -34,6 +34,17 @@ @Html.EditorFor(model => Model.SearchAlias)
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
      @@ -43,66 +54,125 @@
    +

    +
    - @(Html.Telerik().Grid(Model.Categories.Data) - .Name("categories-grid") - .ClientEvents(events => events - .OnDataBinding("onDataBinding")) - .Columns(columns => - { - columns.Bound(x => x.Breadcrumb).Title(T("Admin.Catalog.Categories.Fields.Name").Text) - - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - .ClientTemplate("\"><#= Name #>"); + @(Html.Telerik().Grid() + .Name("categories-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.Breadcrumb) + .Title(T("Admin.Catalog.Products.Categories.Fields.Category").Text) + .Template(x => Html.ActionLink(x.Breadcrumb, "Edit", new { id = x.Id })) + .ClientTemplate("\"><#= Breadcrumb #>"); + columns.Bound(x => x.FullName); columns.Bound(x => x.Alias); - columns.Bound(x => x.Published) - .Width(100) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(100) - .Centered(); - 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.Categories.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Category")) - .EnableCustomBinding(true)) + columns.Bound(x => x.Published) + .Width(160) + .Template(item => @Html.SymbolForBool(item.Published)) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(160) + .Centered(); + columns.Bound(x => x.LimitedToStores) + .Width(160) + .Template(item => @Html.SymbolForBool(item.LimitedToStores)) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden(Model.AvailableStores.Count <= 1) + .Centered(); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Category")) + .PreserveGridState() + .EnableCustomBinding(true))
    + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/ProductAddPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/ProductAddPopup.cshtml deleted file mode 100644 index e74b83f8f9..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/ProductAddPopup.cshtml +++ /dev/null @@ -1,138 +0,0 @@ -@{ - Layout = "~/Administration/Views/Shared/_AdminPopupLayout.cshtml"; -} - -@model CategoryModel.AddCategoryProductModel - -@{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title - ViewBag.Title = T("Admin.Catalog.Categories.Products.AddNew").Text; -} -@using Telerik.Web.Mvc.UI; -@using (Html.BeginForm()) -{ -
    -
    - @T("Admin.Catalog.Categories.Products.AddNew") -
    -
    - - if (ViewBag.RefreshPage == true) - { - - } - - - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchProductName) - - @Html.EditorFor(model => Model.SearchProductName) -
    - @Html.SmartLabelFor(model => model.SearchCategoryId) - - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchManufacturerId) - - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchProductTypeId) - - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) -
    -   - - -
    -

    -

    - - - - - - - -
    - @(Html.Telerik().Grid(Model.Products.Data) - .Name("products-grid") - .Columns(columns => - { - columns.Bound(x => x.Id) - .Template(x => string.Format("", x.Id)) - .ClientTemplate("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Name) - .Template(x => @Html.LabeledProductName(0, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName(null, "Name")); - columns.Bound(x => x.Sku); - columns.Bound(x => x.Published) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered() - .Width(100); - }) - .Pageable(settings => settings.Total(Model.Products.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("ProductAddPopupList", "Category")) - .ClientEvents(events => events.OnDataBinding("onDataBinding")) - .EnableCustomBinding(true)) -
    -

    - -
    - -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/Tree.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/Tree.cshtml index c9e519ec94..1f3d747077 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/Tree.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/Tree.cshtml @@ -1,10 +1,10 @@ -@model IList +@model CategoryTreeModel @using Telerik.Web.Mvc.UI; @using SmartStore.Services.Catalog; @{ - //page title ViewBag.Title = T("Admin.Catalog.Categories.Manage").Text; } +
    @@ -14,39 +14,82 @@  @T("Admin.Common.AddNew")
    + +@if (Model.AvailableStores.Count > 1) +{ + using (Html.BeginForm()) + { + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    +   + + +
    + } +} + +

    +
    @(Html.Telerik().TreeView() .Name("category-treeview") - .ClientEvents(events => events.OnNodeDropped("onNodeDropped")) + .ClientEvents(events => events.OnNodeDropped("onNodeDropped").OnDataBinding("onDataBinding")) .DataBinding(dataBinding => dataBinding.Ajax().Enabled(true).Select("TreeLoadChildren", "Category")) .ShowLines(true) .DragAndDrop(true)) - -
    + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml index 4388beb09c..f78aa0983e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml @@ -14,8 +14,25 @@ //generate an event EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "category-edit", this.Html, this.Model)); }) -@helper TabInfo() { - +@helper TabInfo() +{ + if (Model.Id != 0) + { + + + + + +
    + @Html.SmartLabelFor(model => model.Id) + + @Html.DisplayFor(model => model.Id) + @Html.ValidationMessageFor(model => model.Id) +
    + +

    + } + @(Html.LocalizedEditor("category-info-localized", @ @@ -27,7 +44,16 @@ @Html.ValidationMessageFor(model => model.Locales[item].Name) - + + + + + @@ -36,6 +62,25 @@ @Html.ValidationMessageFor(model => model.Locales[item].Description) + + + + + + + + + + + + @@ -62,8 +116,28 @@ @Html.ValidationMessageFor(model => model.Description) + + + + + + + +
    + @Html.SmartLabelFor(model => model.Locales[item].FullName) + + @Html.EditorFor(model => Model.Locales[item].FullName) + @Html.ValidationMessageFor(model => model.Locales[item].FullName) +
    @Html.SmartLabelFor(model => model.Locales[item].Description)
    + @Html.SmartLabelFor(model => model.Locales[item].BottomDescription) + + @Html.EditorFor(model => model.Locales[item].BottomDescription, Html.RichEditorFlavor()) + @Html.ValidationMessageFor(model => model.Locales[item].BottomDescription) +
    +   + + +
    @Html.HiddenFor(model => model.Locales[item].LanguageId) @@ -54,6 +99,15 @@
    + @Html.SmartLabelFor(model => model.FullName) + + @Html.EditorFor(model => model.FullName) + @Html.ValidationMessageFor(model => model.FullName) +
    @Html.SmartLabelFor(model => model.Description)
    + @Html.SmartLabelFor(model => model.BottomDescription) + + @Html.EditorFor(x => x.BottomDescription, Html.RichEditorFlavor()) + @Html.ValidationMessageFor(model => model.BottomDescription) +
    +   + + +
    - )) + )) + @@ -106,15 +180,15 @@ @Html.SmartLabelFor(model => model.ParentCategoryId) @@ -178,7 +252,9 @@ }
    @@ -97,7 +171,7 @@ @Html.SmartLabelFor(model => model.PictureId) - @Html.EditorFor(model => model.PictureId) + @Html.EditorFor(model => model.PictureId, new { transientUpload = true }) @Html.ValidationMessageFor(model => model.PictureId)
    - @* codehint: sm-edit *@ - @(Html.HiddenFor(x => x.ParentCategoryId, new { - style = "min-width: 700px", - placeholder = "[" + T("Common.Unspecified") + "]", - data_select = true, - data_select_lazy = true, - data_select_init_text = Model.ParentCategoryBreadcrumb, - data_select_url = Url.Action("Allcategories", "Category") - })) + @(Html.HiddenFor(x => x.ParentCategoryId, new + { + style = "min-width: 700px", + placeholder = "[" + T("Common.Unspecified") + "]", + data_select = true, + data_select_lazy = true, + data_select_init_text = Model.ParentCategoryBreadcrumb, + data_select_url = Url.Action("Allcategories", "Category") + })) @Html.ValidationMessageFor(model => model.ParentCategoryId)
    } -@helper TabSeo() { + +@helper TabSeo() +{ @(Html.LocalizedEditor("category-seo-localized", @ @@ -258,6 +334,10 @@ @@ -292,18 +372,22 @@
    @Html.TextAreaFor(x => x.SeName, new { @class = "input-large" }) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "Category", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
    } -@helper TabProducts() { - if (Model.Id > 0) { - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - - + + + + +
    - @(Html.Telerik().Grid() - .Name("products-grid") - .DataKeys(keys => { + +@helper TabProducts() +{ + if (Model.Id > 0) + { + + + - - - - -
    + @(Html.Telerik().Grid() + .Name("products-grid") + .DataKeys(keys => + { keys.Add(x => x.Id); }) - .DataBinding(dataBinding => { + .DataBinding(dataBinding => + { dataBinding.Ajax() .Select("ProductList", "Category", new { categoryId = Model.Id }) .Update("ProductUpdate", "Category") @@ -312,101 +396,74 @@ .Columns(columns => { columns.Bound(x => x.ProductName) - .ReadOnly() - .Template(x => @Html.LabeledProductName(x.Id, x.ProductName, x.ProductTypeName, x.ProductTypeLabelHint)) + .ReadOnly() + .Width(520) .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); columns.Bound(x => x.Sku) .ReadOnly(); columns.Bound(x => x.Published) .ReadOnly() - .Width(100) - .Template(item => @Html.SymbolForBool(item.Published)) .ClientTemplate(@Html.SymbolForBool("Published")) .Centered(); columns.Bound(x => x.IsFeaturedProduct) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsFeaturedProduct)) .ClientTemplate(@Html.SymbolForBool("IsFeaturedProduct")) .Centered(); columns.Bound(x => x.DisplayOrder1) - .Width(100) .Centered(); columns.Command(commands => { commands.Edit().Localize(T); commands.Delete().Localize(T); }) - .Width(180); + .Width(220); }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .PreserveGridState() .EnableCustomBinding(true)) -
    - - - -
    - } - else { - @T("Admin.Catalog.Categories.Products.SaveBeforeEdit") - } +
    + +
    + } + else + { + @T("Admin.Catalog.Categories.Products.SaveBeforeEdit") + } } -@helper TabDiscounts() { - if (Model.AvailableDiscounts != null && Model.AvailableDiscounts.Count > 0) { - foreach (var discount in Model.AvailableDiscounts) { +@helper TabDiscounts() +{ + if (Model.AvailableDiscounts != null && Model.AvailableDiscounts.Count > 0) + { + foreach (var discount in Model.AvailableDiscounts) + { } } - else { - @T("Admin.Catalog.Categories.Discounts.NoDiscounts") + else + { +
    + @T("Admin.Catalog.Categories.Discounts.NoDiscounts") +
    } } -@helper TabAcl() { - - +@helper TabAcl() +{ - + + } + + + + + + +
    @@ -417,48 +474,49 @@ @Html.ValidationMessageFor(model => model.SubjectToAcl)
    @Html.SmartLabelFor(model => model.AvailableCustomerRoles) - @if (Model.AvailableCustomerRoles != null && Model.AvailableCustomerRoles.Count > 0) { - foreach (var customerRole in Model.AvailableCustomerRoles) { + @if (Model.AvailableCustomerRoles != null && Model.AvailableCustomerRoles.Count > 0) + { + foreach (var customerRole in Model.AvailableCustomerRoles) + { +
    + +
    + } + } + else + {
    - + @T("Admin.Configuration.Acl.NoRolesDefined")
    - } - } - else { -
    No customer roles defined
    - }
     
    +
    + @T("Admin.Configuration.Category.Acl.AssignToSubCategoriesAndProducts.Hint") +
    + +   + @T("Admin.Configuration.Category.Acl.AssignToSubCategoriesAndProducts") + +
    } @helper TabStores() { - - + @@ -478,33 +536,84 @@ { foreach (var store in Model.AvailableStores) { - + } } else {
    @T("Admin.Configuration.Stores.NoStoresDefined")
    - } + } + + + + + + +
    @@ -469,7 +527,7 @@ @Html.ValidationMessageFor(model => model.LimitedToStores)
    @Html.SmartLabelFor(model => model.AvailableStores)
     
    +
    + @T("Admin.Configuration.Category.Stores.AssignToSubCategoriesAndProducts.Hint") +
    + +   + @T("Admin.Configuration.Category.Stores.AssignToSubCategoriesAndProducts") + +
    } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml index aa8dfae18f..b3707ed7fe 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml @@ -1,8 +1,6 @@ -@model Telerik.Web.Mvc.GridModel -@using Telerik.Web.Mvc.UI - +@using Telerik.Web.Mvc.UI +@model CheckoutAttributeListModel @{ - //page title ViewBag.Title = T("Admin.Catalog.Attributes.CheckoutAttributes").Text; }
    @@ -10,39 +8,47 @@ @T("Admin.Catalog.Attributes.CheckoutAttributes")
    +
    - @(Html.Telerik().Grid(Model.Data) - .Name("checkoutattributes-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.AttributeControlTypeName) - .Width(150); - columns.Bound(x => x.IsRequired) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsRequired)) - .ClientTemplate(@Html.SymbolForBool("IsRequired")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(100) - .Centered(); - 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", "CheckoutAttribute")) - .EnableCustomBinding(true)) + @(Html.Telerik().Grid() + .Name("checkoutattributes-grid") + .Columns(columns => + { + columns.Bound(x => x.Name) + .Width(500) + .ClientTemplate("<#= Name #>"); + columns.Bound(x => x.TextPrompt); + columns.Bound(x => x.AttributeControlTypeName); + columns.Bound(x => x.IsActive) + .ClientTemplate(@Html.SymbolForBool("IsActive")) + .Centered(); + columns.Bound(x => x.IsRequired) + .ClientTemplate(@Html.SymbolForBool("IsRequired")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Centered(); + columns.Bound(x => x.LimitedToStores) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden(Model.AvailableStores.Count <= 1) + .Centered(); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "CheckoutAttribute")) + .ClientEvents(events => events.OnRowDataBound("checkoutAttributesGrid_onRowDataBound")) + .EnableCustomBinding(true))
    \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml index b1b9c7c421..be84f43826 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml @@ -1,17 +1,17 @@ -@model CheckoutAttributeModel - -@using SmartStore.Core.Domain.Catalog; +@using SmartStore.Core.Domain.Catalog; @using Telerik.Web.Mvc.UI; +@model CheckoutAttributeModel @Html.ValidationSummary(false) @Html.HiddenFor(model => model.Id) @Html.SmartStore().TabStrip().Name("checkoutattribute-edit").Items(x => { - x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Info").Text).Content(TabInfo()).Selected(true); - x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Values").Text).Content(TabValues()); - - //generate an event - EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "checkoutattribute-edit", this.Html, this.Model)); + x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Info").Text).Content(TabInfo()).Selected(true); + x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Values").Text).Content(TabValues()); + x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); + + //generate an event + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "checkoutattribute-edit", this.Html, this.Model)); }) @helper TabInfo() @@ -83,6 +83,15 @@ )) + + + + - @if (Model.DeleteExportedFiles.NumberOfDeletedFiles.HasValue) + @if (Model.DeleteExportedFiles.NumberOfDeletedFiles.HasValue || Model.DeleteExportedFiles.NumberOfDeletedFolders.HasValue) { @@ -132,7 +139,7 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Country/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Country/List.cshtml index 6395a0a7d0..3b50a54b36 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Country/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Country/List.cshtml @@ -1,7 +1,6 @@ -@model Telerik.Web.Mvc.GridModel +@model CountryListModel @using Telerik.Web.Mvc.UI @{ - //page title ViewBag.Title = T("Admin.Configuration.Countries").Text; }
    @@ -13,62 +12,68 @@  @T("Admin.Common.AddNew")
    +
    + @Html.SmartLabelFor(model => model.IsActive) + + @Html.EditorFor(model => model.IsActive) + @Html.ValidationMessageFor(model => model.IsActive) +
    @Html.SmartLabelFor(model => model.IsRequired) @@ -142,85 +151,131 @@ @helper TabValues() { - if (Model.Id > 0) - { - /*TODO display the following warning if values are not supported by selected attribute control type - 'Values are not required for this attribute control type' - */ - - - + +
    - @(Html.Telerik().Grid() - .Name("checkoutattributevalues-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("valueId"); - x.Add(y => y.CheckoutAttributeId).RouteKey("checkoutAttributeId"); - }) - .Columns(columns => - { - columns.Bound(x => x.Name); - columns.Bound(x => x.PriceAdjustment) - .Format("{0:0.00}") - .RightAlign() - .Width(200); - columns.Bound(x => x.WeightAdjustment) - .Format("{0:0.00}") - .Centered() - .Width(200); - columns.Bound(x => x.IsPreSelected) - .Width(140) - .Template(item => @Html.SymbolForBool(item.IsPreSelected)) - .ClientTemplate(@Html.SymbolForBool("IsPreSelected")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(140) - .Centered(); - columns.Bound(x => x.Id) - .Width(120) - .Centered() - .ClientTemplate("?btnId=btnRefresh&formId=checkoutattribute-form', 800, 300, true); return false;\" class='t-button' />") - .Title(T("Admin.Common.Edit").Text); - columns.Command(commands => - { - commands.Delete().Localize(T); - }) + if (Model.Id > 0) + { + /*TODO display the following warning if values are not supported by selected attribute control type + 'Values are not required for this attribute control type' + */ + + + - - - + + + - -
    + @(Html.Telerik().Grid() + .Name("checkoutattributevalues-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("valueId"); + x.Add(y => y.CheckoutAttributeId).RouteKey("checkoutAttributeId"); + }) + .Columns(columns => + { + columns.Bound(x => x.Name); + columns.Bound(x => x.PriceAdjustment) + .Format("{0:0.00}") + .RightAlign() + .Width(200); + columns.Bound(x => x.WeightAdjustment) + .Format("{0:0.00}") + .Centered() + .Width(200); + columns.Bound(x => x.IsPreSelected) + .Width(140) + .Template(item => @Html.SymbolForBool(item.IsPreSelected)) + .ClientTemplate(@Html.SymbolForBool("IsPreSelected")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(140) + .Centered(); + columns.Bound(x => x.Id) .Width(120) - .Title(T("Admin.Common.Delete").Text); + .Centered() + .ClientTemplate("?btnId=btnRefresh&formId=checkoutattribute-form', 800, 300, true); return false;\" class='t-button' />") + .Title(T("Admin.Common.Edit").Text); + columns.Command(commands => + { + commands.Delete().Localize(T); }) - .DataBinding(dataBinding => - dataBinding.Ajax().Select("ValueList", "CheckoutAttribute", new { checkoutAttributeId = Model.Id }) - .Delete("ValueDelete", "CheckoutAttribute")) - .EnableCustomBinding(true)) -
    - + .Width(120) + .Title(T("Admin.Common.Delete").Text); + }) + .DataBinding(dataBinding => + dataBinding.Ajax().Select("ValueList", "CheckoutAttribute", new { checkoutAttributeId = Model.Id }) + .Delete("ValueDelete", "CheckoutAttribute")) + .EnableCustomBinding(true)) +
    + - + - -
    - } - else - { - @T("Admin.Catalog.Attributes.CheckoutAttributes.Values.SaveBeforeEdit") - } - } \ No newline at end of file + //return false to don't reload a page + return false; + }); + }); + +
    + } + else + { + @T("Admin.Catalog.Attributes.CheckoutAttributes.Values.SaveBeforeEdit") + } +} + +@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")
    + } +
    +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Common/CheckUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Common/CheckUpdate.cshtml new file mode 100644 index 0000000000..2996be7024 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Common/CheckUpdate.cshtml @@ -0,0 +1,69 @@ +@model CheckUpdateResult + +@{ + ViewBag.Title = T("Admin.CheckUpdate"); +} + +
    +
    + + @ViewBag.Title +
    +
    + +
    + @if (!Model.UpdateAvailable) + { +
    +
    @T("Admin.CheckUpdate.YourVersion")
    +
    @Model.CurrentVersion
    +
    +
    + @T("Admin.CheckUpdate.IsUpToDate") +
    + } + else + { +

    @T("Admin.CheckUpdate.UpdateAvailable")!

    +
    +
    @T("Admin.CheckUpdate.YourVersion")
    +
    @Model.CurrentVersion
    +
    @T("Admin.CheckUpdate.CurrentVersion")
    +
    @Model.Version
    +
    @T("Common.Date")
    +
    @Model.ReleaseDateUtc.ToShortDateString()
    +
    + + if (Model.AutoUpdatePossible && Model.AutoUpdatePackageUrl.IsWebUrl()) + { +
    +

    @T("Admin.CheckUpdate.AutoUpdatePossible")

    + @Html.Raw(T("Admin.CheckUpdate.AutoUpdatePossibleInfo")) +

    + + + @T("Admin.CheckUpdate.UpdateNow") + +

    +
    + } + +
    +
    @T("Admin.CheckUpdate.ReleaseNotes")
    +
    @Html.Raw(Model.ReleaseNotes)
    +
    + + + } +
    + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml index d152ec5e21..3689f6e979 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml @@ -103,12 +103,19 @@
    @T("Admin.System.Maintenance.DeleteExportedFiles")
    - @string.Format(T("Admin.System.Maintenance.DeleteExportedFiles.TotalDeleted").Text, Model.DeleteExportedFiles.NumberOfDeletedFiles.Value) + @if (Model.DeleteExportedFiles.NumberOfDeletedFiles.HasValue) + { +
    @T("Admin.Common.FilesDeleted", Model.DeleteExportedFiles.NumberOfDeletedFiles.Value).
    + } + @if (Model.DeleteExportedFiles.NumberOfDeletedFolders.HasValue) + { +
    @T("Admin.Common.FoldersDeleted", Model.DeleteExportedFiles.NumberOfDeletedFolders.Value).
    + }
      - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Common/SeNames.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Common/SeNames.cshtml deleted file mode 100644 index d703ee96ac..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Common/SeNames.cshtml +++ /dev/null @@ -1,178 +0,0 @@ -@model UrlRecordListModel -@using Telerik.Web.Mvc.UI -@{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title - ViewBag.Title = T("Admin.System.SeNames").Text; -} -@using (Html.BeginForm()) -{ -
    -
    - - @T("Admin.System.SeNames") -
    -
    - -
    -
    - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SeName) - - @Html.EditorFor(model => Model.SeName) -
    -   - - -
    - - - - -
    - @(Html.Telerik().Grid() - .Name("sename-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).Width(50); - columns.Bound(x => x.Name); - columns.Bound(x => x.EntityId).Width(50); - columns.Bound(x => x.EntityName).Width(200); - columns.Bound(x => x.IsActive) - .Template(item => @Html.SymbolForBool(item.IsActive)) - .ClientTemplate(@Html.SymbolForBool("IsActive")) - .Centered() - .Width(100); - columns.Bound(x => x.Language).Width(200); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("SeNames", "Common")) - .EnableCustomBinding(true)) -
    - - -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Common/SystemInfo.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Common/SystemInfo.cshtml index 7cad69a0a0..c71c91b5fb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Common/SystemInfo.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Common/SystemInfo.cshtml @@ -21,6 +21,7 @@ } } + @@ -114,6 +116,15 @@ } + + + + @@ -116,24 +120,27 @@ -
    @@ -28,6 +29,7 @@ @Html.DisplayFor(model => model.AppVersion) + @T("Admin.CheckUpdate")
    + @Html.SmartLabelFor(model => model.UsedMemorySize) + + @Model.UsedMemorySizeString.NaIfEmpty() + @T("Admin.System.SystemInfo.GarbageCollect") +
    @Html.SmartLabelFor(model => model.LoadedAssemblies) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/EditSlide.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/EditSlide.cshtml index 73053101d1..c28ecc426b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/EditSlide.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/EditSlide.cshtml @@ -1,7 +1,6 @@ @model ContentSliderSlideModel @using SmartStore.Admin.Models.ContentSlider @{ - //page title ViewBag.Title = T("Admin.Configuration.ContentSlider.EditSlide").Text; } @using (Html.BeginForm()) @@ -11,9 +10,13 @@ @T("Admin.Configuration.ContentSlider.EditSlide") - @Model.Title @Html.ActionLink("(" + @T("Admin.Configuration.ContentSlider.BackToList") + ")", "Index")
    + @Html.Widget("admin_button_toolbar_before") + + + @Html.Widget("admin_button_toolbar_after")
    @Html.Partial("_CreateOrUpdate", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/Index.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/Index.cshtml index 19f4ad462f..e847170de8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/Index.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/Index.cshtml @@ -13,10 +13,14 @@ @T("Admin.Configuration.ContentSlider")
    + @Html.Widget("admin_button_toolbar_before") + + + @Html.Widget("admin_button_toolbar_after")
    @@ -78,7 +82,7 @@ @Html.SmartLabelFor(model => model.BackgroundPictureId)
    - @Html.EditorFor(model => model.BackgroundPictureId, "Picture") + @Html.EditorFor(model => model.BackgroundPictureId, "Picture", new { transientUpload = true }) @Html.ValidationMessageFor(model => model.BackgroundPictureId)
    - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    -   - - -
    + if (Model.AvailableStores.Count > 1) + { + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    +   + + +
    + }
    - @Html.EditorFor(model => model.PictureId, "Picture") + @Html.EditorFor(model => model.PictureId, "Picture", new { transientUpload = true }) @Html.ValidationMessageFor(model => model.PictureId)
    @(Html.Telerik().Grid() - .Name("countries-grid") - .BindTo(Model.Data) - .Columns(columns => - { - columns.Bound(x => x.Name) - .Width(250) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - .ClientTemplate("<#= Name #>"); - columns.Bound(x => x.AllowsBilling) - .Width(50) - .Template(item => @Html.SymbolForBool(item.AllowsBilling)) - .ClientTemplate(@Html.SymbolForBool("AllowsBilling")) - .Centered(); - columns.Bound(x => x.AllowsShipping) - .Width(50) - .Template(item => @Html.SymbolForBool(item.AllowsShipping)) - .ClientTemplate(@Html.SymbolForBool("AllowsShipping")) - .Centered(); - columns.Bound(x => x.TwoLetterIsoCode) - .Width(50) - .Centered(); - columns.Bound(x => x.ThreeLetterIsoCode) - .Width(50) - .Centered(); - columns.Bound(x => x.NumericIsoCode) - .Width(50) - .Centered(); - columns.Bound(x => x.SubjectToVat) - .Width(50) - .Template(item => @Html.SymbolForBool(item.SubjectToVat)) - .ClientTemplate(@Html.SymbolForBool("SubjectToVat")) - .Centered(); - columns.Bound(x => x.NumberOfStates) - .Width(50) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(50) - .Centered(); - columns.Bound(x => x.Published) - .Width(50) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - //columns.Bound(x => x.Id) - // .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id })) - // .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") - // .Width(50) - // .Centered() - // .HeaderTemplate(T("Admin.Common.Edit").Text); - }) - .DataBinding(dataBinding => dataBinding.Ajax().Select("CountryList", "Country")) - .EnableCustomBinding(true)) + .Name("countries-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .Editable(x => + { + x.Mode(GridEditMode.InLine); + }) + .Columns(columns => + { + columns.Bound(x => x.Name) + .ClientTemplate("<#= Name #>"); + columns.Bound(x => x.AllowsBilling) + .Width(50) + .ClientTemplate(@Html.SymbolForBool("AllowsBilling")) + .Centered(); + columns.Bound(x => x.AllowsShipping) + .ClientTemplate(@Html.SymbolForBool("AllowsShipping")) + .Centered(); + columns.Bound(x => x.TwoLetterIsoCode) + .Hidden(true); + columns.Bound(x => x.ThreeLetterIsoCode) + .Hidden(true); + columns.Bound(x => x.NumericIsoCode) + .Hidden(true); + columns.Bound(x => x.SubjectToVat) + .ClientTemplate(@Html.SymbolForBool("SubjectToVat")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Centered(); + columns.Bound(x => x.LimitedToStores) + .Hidden(true); + columns.Bound(x => x.Published) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }); + }) + .DataBinding(dataBinding => dataBinding.Ajax() + .Select("CountryList", "Country") + .Update("CountryUpdate", "Country") + .Delete("CountryDelete", "Country") + ) + .ClientEvents(x => x.OnError("CountryGrid_onError")) + .PreserveGridState() + .EnableCustomBinding(true))
    + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Country/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Country/_CreateOrUpdate.cshtml index dbd98e66ab..990ee0124b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Country/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Country/_CreateOrUpdate.cshtml @@ -6,44 +6,46 @@ @Html.SmartStore().TabStrip().Name("country-edit").Items(x => { x.Add().Text(T("Admin.Configuration.Countries.Info").Text).Content(TabInfo()).Selected(true); - x.Add().Text(T("Admin.Configuration.Countries.States").Text).Content(TabStates()); + x.Add().Text("{0} ({1})".FormatInvariant(T("Admin.Configuration.Countries.States"), Model.NumberOfStates)).Content(TabStates()); + x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); //generate an event EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "country-edit", this.Html, this.Model)); }) + @helper TabInfo() - { +{ @(Html.LocalizedEditor("country-info-localized", - @ - - - - - - - -
    - @Html.SmartLabelFor(model => model.Locales[item].Name) - - @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.Name) - - @Html.EditorFor(model => model.Name) - @Html.ValidationMessageFor(model => model.Name) -
    - )) - + @
    + + + + + + + +
    + @Html.SmartLabelFor(model => model.Locales[item].Name) + + @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.Name) + + @Html.EditorFor(model => model.Name) + @Html.ValidationMessageFor(model => model.Name) +
    + )) +
    @Html.SmartLabelFor(model => model.AllowsBilling) @@ -118,84 +120,128 @@
    } -@helper TabStates() - { - if (Model.Id > 0) - { - - - - - - - - -
    - @(Html.Telerik().Grid() - .Name("states-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("Id"); - }) - .Columns(columns => - { - columns.Bound(x => x.Name).Width(250); - //TODO display localized values here - columns.Bound(x => x.Abbreviation).Width(100); - columns.Bound(x => x.Published) - .Width(100) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.DisplayOrder1).Width(100); - columns.Bound(x => x.Id) - .Width(50) - .Centered() - .ClientTemplate("?btnId=btnRefresh&formId=country-form', 800, 250, true); return false;\" class='t-button' />") - .Title(T("Admin.Common.Edit").Text); - columns.Command(commands => - { - commands.Delete().Localize(T); - }).Width(50); - - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("States", "Country", new { countryId = Model.Id }) - .Delete("StateDelete", "Country"); - }) - .ClientEvents(x => x.OnError("grid_onError")) - .EnableCustomBinding(true)) -
    - - - -
    - +@helper TabStates() +{ + if (Model.Id > 0) + { + + + + + + + +
    + @(Html.Telerik().Grid() + .Name("states-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("States", "Country", new { countryId = Model.Id }) + .Delete("StateDelete", "Country"); + }) + .Columns(columns => + { + columns.Bound(x => x.Name); + //TODO display localized values here + columns.Bound(x => x.Abbreviation) + .Centered(); + columns.Bound(x => x.Published) + .Template(item => @Html.SymbolForBool(item.Published)) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.DisplayOrder1) + .Centered(); + columns.Bound(x => x.Id) + .Centered() + .Width(120) + .ClientTemplate("?btnId=btnRefresh&formId=country-form', 800, 250, true); return false;\" class='t-button' />") + .Title(""); + columns.Command(commands => + { + commands.Delete().Localize(T); + }).Width(120); + }) + .ClientEvents(x => x.OnError("grid_onError")) + .EnableCustomBinding(true)) +
    + + + +
    + + + } + else + { + @T("Admin.Configuration.Countries.States.SaveBeforeEdit") + } +} - } - else - { - @T("Admin.Configuration.Countries.States.SaveBeforeEdit") - } +@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")
    + } +
    } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Currency/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Currency/List.cshtml index f659ace41d..029108bc38 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Currency/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Currency/List.cshtml @@ -3,9 +3,10 @@ @{ List rates = ViewBag.Rates; bool autoUpdateEnabled = ViewBag.AutoUpdateEnabled ?? false; - //page title + ViewBag.Title = T("Admin.Configuration.Currencies").Text; } +
    @@ -26,6 +27,7 @@
    + @using (Html.BeginForm("Save", "Currency", FormMethod.Post, new { name = "CurrencySettings" })) { @@ -48,58 +50,41 @@
    } +
    @(Html.Telerik().Grid() - .Name("currencies-grid") - .BindTo(Model.Data) - .Columns(columns => - { - columns.Bound(x => x.Name) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })); - columns.Bound(x => x.CurrencyCode) - .Centered(); - columns.Bound(x => x.Rate) - .RightAlign() - .Format("{0:0.00000000}") - .Width(180); - columns.Bound(x => x.DomainEndings); - columns.Bound(x => x.IsPrimaryExchangeRateCurrency) - .Centered() - .Template( - @ - @if (item.IsPrimaryExchangeRateCurrency) - { - - } - @T("Admin.Telerik.GridLocalization.Select") - - ) - .Width(100); - columns.Bound(x => x.IsPrimaryStoreCurrency) - .Centered() - .Template( - @ - @if (item.IsPrimaryStoreCurrency) - { - - } - @T("Admin.Telerik.GridLocalization.Select") - - ) - .Width(100); - columns.Bound(x => x.Published) - .Template(item => Html.SymbolForBool(item.Published)) - .Centered() - .Width(100); - }) -) + .Name("currencies-grid") + .BindTo(Model.Data) + .Columns(columns => + { + columns.Bound(x => x.Name) + .Template(x => Html.LabeledCurrencyName(x.Id, x.Name, x.IsPrimaryStoreCurrency, x.IsPrimaryExchangeRateCurrency)); + columns.Bound(x => x.CurrencyCode) + .Centered() + .Width(160); + columns.Bound(x => x.Published) + .Template(item => Html.SymbolForBool(item.Published)) + .Centered() + .Width(160); + columns.Bound(x => x.Rate) + .RightAlign() + .Format("{0:0.00000000}") + .Width(160); + columns.Bound(x => x.LimitedToStores) + .Template(item => Html.SymbolForBool(item.LimitedToStores)) + .Centered() + .Width(160); + columns.Bound(x => x.DomainEndings); + }) + )
    -

    -

    + +

    + @if (rates != null) {

    @T("Admin.Configuration.Currencies.LiveRates")

    @@ -129,7 +114,7 @@ }) .EnableCustomBinding(true) - ) + ) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Currency/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Currency/_CreateOrUpdate.cshtml index 270d2f982b..2e86287c4b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Currency/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Currency/_CreateOrUpdate.cshtml @@ -141,6 +141,42 @@ } + + + @Html.SmartLabelFor(model => model.PrimaryStoreCurrencyStores) + + + @if (Model.PrimaryStoreCurrencyStores != null && Model.PrimaryStoreCurrencyStores.Count > 0) + { + foreach (var store in Model.PrimaryStoreCurrencyStores) + { + @store.Text.NaIfEmpty()
    + } + } + else + { + @T("Common.Unspecified") + } + + + + + @Html.SmartLabelFor(model => model.PrimaryExchangeRateCurrencyStores) + + + @if (Model.PrimaryExchangeRateCurrencyStores != null && Model.PrimaryExchangeRateCurrencyStores.Count > 0) + { + foreach (var store in Model.PrimaryExchangeRateCurrencyStores) + { + @store.Text.NaIfEmpty()
    + } + } + else + { + @T("Common.Unspecified") + } + + } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/Create.cshtml index f2cc83e6fe..6d5fc4e9ce 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/Create.cshtml @@ -7,7 +7,8 @@ {
    - @T("Admin.Customers.Customers.AddNew") @Html.ActionLink("(" + T("Admin.Customers.Customers.BackToList") + ")", "List") + @T("Admin.Customers.Customers.AddNew") + (@T("Admin.Customers.Customers.BackToList"))
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/Edit.cshtml index 75ff1fa449..f1f4b13941 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/Edit.cshtml @@ -1,6 +1,5 @@ @model CustomerModel @{ - //page title ViewBag.Title = T("Admin.Customers.Customers.EditCustomerDetails").Text; } @using Telerik.Web.Mvc.UI @@ -9,9 +8,11 @@
    @T("Admin.Customers.Customers.EditCustomerDetails") - @Model.LastName @Model.FirstName - @Html.ActionLink("(" + T("Admin.Customers.Customers.BackToList") + ")", "List") + (@T("Admin.Customers.Customers.BackToList"))
    + @Html.Widget("admin_button_toolbar_before") + @@ -22,6 +23,8 @@  @T("Admin.Customers.Customers.SendPM") + + @Html.Widget("admin_button_toolbar_after")
    @Html.Partial("_CreateOrUpdate", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/List.cshtml index 27215e685a..db8444dd68 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/List.cshtml @@ -4,7 +4,6 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - //page title ViewBag.Title = T("Admin.Customers.Customers").Text; } @using (Html.BeginForm()) @@ -15,24 +14,17 @@ @T("Admin.Customers.Customers")
    - + +
    @Html.SmartLabelFor(model => model.SearchCustomerRoleIds) @@ -94,18 +86,17 @@ @{ - var monthOfBirthValues = new List(); - //monthOfBirthValues.Add(new SelectListItem() { Text = T("Admin.Customers.Customers.List.SearchDateOfBirth.Month").Text, Value = "0" }); - for (int i = 1; i <= 12; i++) - { - monthOfBirthValues.Add(new SelectListItem() { Text = i.ToString(CultureInfo.InvariantCulture), Value = i.ToString(CultureInfo.InvariantCulture) }); - } - var dayOfBirthValues = new List(); - //dayOfBirthValues.Add(new SelectListItem() { Text = T("Admin.Customers.Customers.List.SearchDateOfBirth.Day").Text, Value = "0" }); - for (int i = 1; i <= 31; i++) - { - dayOfBirthValues.Add(new SelectListItem() { Text = i.ToString(CultureInfo.InvariantCulture), Value = i.ToString(CultureInfo.InvariantCulture) }); - } + var monthOfBirthValues = new List(); + for (int i = 1; i <= 12; i++) + { + monthOfBirthValues.Add(new SelectListItem { Text = i.ToString(CultureInfo.InvariantCulture), Value = i.ToString(CultureInfo.InvariantCulture) }); + } + + var dayOfBirthValues = new List(); + for (int i = 1; i <= 31; i++) + { + dayOfBirthValues.Add(new SelectListItem { Text = i.ToString(CultureInfo.InvariantCulture), Value = i.ToString(CultureInfo.InvariantCulture) }); + } } @Html.DropDownListFor(model => Model.SearchMonthOfBirth, monthOfBirthValues, T("Admin.Customers.Customers.List.SearchDateOfBirth.Month").Text, new { @class = "autowidth" }) @Html.DropDownListFor(model => Model.SearchDayOfBirth, dayOfBirthValues, T("Admin.Customers.Customers.List.SearchDateOfBirth.Day").Text, new { @class = "autowidth" }) @@ -155,8 +146,9 @@
    -

    -

    + +

    +
    @@ -168,48 +160,32 @@ .Columns(columns => { columns.Bound(x => x.Id) - .Template(x => string.Format("", x.Id)) - .ClientTemplate("") - .Title("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); - - columns.Bound(x => x.Id) - .Width(50); - //I don't know why but the customer list does not have an 'Edit' column in the grid on some machines (maybe because we are inside Html.BeginForm()). - //That's why the 'Email' column is clickable. + .Template(x => string.Format("", x.Id)) + .ClientTemplate("") + .Title("") + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id); columns.Bound(x => x.Email) .Template(x => Html.ActionLink(x.Email, "Edit", "Customer", new { id = x.Id }, new { })) - .ClientTemplate("\"><#= Email #>") - .Width(150); + .ClientTemplate("\"><#= Email #>"); columns.Bound(x => x.Username) - .Width(150) .Visible(Model.UsernamesEnabled); - columns.Bound(x => x.FullName) - .Width(200); - columns.Bound(x => x.CustomerRoleNames) - .Width(200); + columns.Bound(x => x.FullName); + columns.Bound(x => x.CustomerRoleNames); columns.Bound(x => x.Company) - .Width(150) .Visible(Model.CompanyEnabled); columns.Bound(x => x.Phone) - .Width(100) .Visible(Model.PhoneEnabled); columns.Bound(x => x.ZipPostalCode) - .Width(75) .Visible(Model.ZipPostalCodeEnabled); columns.Bound(x => x.Active) - .Width(100) .Template(item => @Html.SymbolForBool(item.Active)) .ClientTemplate(@Html.SymbolForBool("Active")) .Centered(); - columns.Bound(x => x.CreatedOn) - .Width(100); - columns.Bound(x => x.LastActivityDate) - .Width(100); + columns.Bound(x => x.CreatedOn); + columns.Bound(x => x.LastActivityDate); 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 + "") @@ -217,148 +193,133 @@ }) .Pageable(settings => settings.Total(Model.Customers.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("CustomerList", "Customer")) + .PreserveGridState() .EnableCustomBinding(true))
    } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml index d0e1381bce..a6ddb41d85 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml @@ -36,7 +36,7 @@ EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "customer-edit", this.Html, this.Model)); }) @helper TabInfo() - { +{ if (Model.CountryEnabled && Model.StateProvinceEnabled) { } + @if (Model.Id != 0) + { + + + + + } @if (Model.UsernamesEnabled) { if (Model.Id == 0 || Model.AllowUsersToChangeUsernames) @@ -200,6 +212,20 @@ } + + @if (Model.CustomerNumberEnabled) + { + + + + + } + @if (Model.StreetAddressEnabled) { @@ -393,14 +419,14 @@ } - @if (Model.Id > 0 && !String.IsNullOrEmpty(Model.LastVisitedPage)) + @if (Model.Id > 0 && Model.LastVisitedPage.HasValue()) { } @@ -459,6 +485,7 @@ { dataBinding.Ajax().Select("OrderList", "Customer", new { customerId = Model.Id }); }) + .PreserveGridState() .EnableCustomBinding(true)) } @@ -480,13 +507,16 @@ columns.Bound(x => x.Message); columns.Bound(x => x.CreatedOn).ReadOnly(); })) -
    -
    +
    -
    -

    @T("Admin.Customers.Customers.RewardPoints.AddTitle")

    -
    + @Html.SmartLabelFor(model => model.Id) + + @Html.DisplayFor(model => model.Id) + @Html.ValidationMessageFor(model => model.Id) +
    + @Html.SmartLabelFor(model => model.CustomerNumber) + + @Html.EditorFor(model => model.CustomerNumber) + @Html.ValidationMessageFor(model => model.CustomerNumber) +
    @Html.SmartLabelFor(model => model.LastVisitedPage) - @Html.DisplayFor(model => model.LastVisitedPage) + @Model.LastVisitedPage.Truncate(128, "...")
    + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml index 98165844ae..919a1af379 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml @@ -1,59 +1,59 @@ @model BestCustomersReportModel - @using Telerik.Web.Mvc.UI -
    +
    +
    @T("Admin.Customers.Customers.RewardPoints.AddTitle")
    +
    +
    @Html.SmartLabelFor(model => model.AddRewardPointsValue) @@ -501,7 +531,7 @@ @Html.SmartLabelFor(model => model.AddRewardPointsMessage) - @Html.EditorFor(model => model.AddRewardPointsMessage) + @Html.TextAreaFor(model => model.AddRewardPointsMessage) @Html.ValidationMessageFor(model => model.AddRewardPointsMessage)
    - - - - - - - - - - - - - - - - - - - - - - - - -
    - @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) -
    - @Html.SmartLabelFor(model => model.PaymentStatusId) - - @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses) -
    - @Html.SmartLabelFor(model => model.ShippingStatusId) - - @Html.DropDownList("ShippingStatusId", Model.AvailableShippingStatuses) -
    -   - - -
    -

    -

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @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.ShippingStatusId) + + @Html.DropDownList("ShippingStatusId", Model.AvailableShippingStatuses, T("Admin.Common.All")) +
    +   + + +
    + +

    + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddProductToOrder.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddProductToOrder.cshtml index c77de58d5d..2c99ed1a86 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddProductToOrder.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddProductToOrder.cshtml @@ -2,7 +2,6 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - //page title ViewBag.Title = string.Format(T("Admin.Orders.Products.AddNew.Title1").Text, Model.OrderId); } @using Telerik.Web.Mvc.UI; @@ -19,7 +18,7 @@ @T("Admin.Orders.Products.AddNew.Note1") -
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml index 13c0f4570f..af0e21e9bb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml @@ -4,7 +4,7 @@ @using (Html.BeginForm()) { - +
    @@ -34,7 +34,7 @@ @Html.SmartLabelFor(model => model.PaymentStatusId) @@ -42,7 +42,7 @@ @Html.SmartLabelFor(model => model.ShippingStatusId) @@ -54,8 +54,9 @@
    @Html.SmartLabelFor(model => model.StartDate) @@ -26,7 +26,7 @@ @Html.SmartLabelFor(model => model.OrderStatusId) - @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses) + @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses, T("Admin.Common.All"))
    - @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses) + @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses, T("Admin.Common.All"))
    - @Html.DropDownList("ShippingStatusId", Model.AvailableShippingStatuses) + @Html.DropDownList("ShippingStatusId", Model.AvailableShippingStatuses, T("Admin.Common.All"))
    -

    -

    + +

    + - - - - - - - - - - - - +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CustomerRole/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CustomerRole/List.cshtml index 2e2c603a07..ae06ccaff5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CustomerRole/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CustomerRole/List.cshtml @@ -50,7 +50,7 @@ .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") .Width(50) .Centered() - .HeaderTemplate(T("Admin.Common.Edit").Text) + .HeaderTemplate("{0}".FormatWith(T("Admin.Common.Edit").Text)) .Filterable(false); }) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "CustomerRole")) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Discount/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Discount/Edit.cshtml index 6d5b135aa2..4f0de79114 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Discount/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Discount/Edit.cshtml @@ -1,6 +1,5 @@ @model DiscountModel @{ - //page title ViewBag.Title = T("Admin.Promotions.Discounts.EditDiscountDetails").Text; } @using (Html.BeginForm()) @@ -10,9 +9,13 @@ @T("Admin.Promotions.Discounts.EditDiscountDetails") - @Model.Name @Html.ActionLink("(" + T("Admin.Promotions.Discounts.BackToList") + ")", "List")
    + @Html.Widget("admin_button_toolbar_before") + + + @Html.Widget("admin_button_toolbar_after")
    @Html.Partial("_CreateOrUpdate", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Discount/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Discount/List.cshtml index f99d8e8bef..20b84e716d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Discount/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Discount/List.cshtml @@ -1,7 +1,6 @@ @model Telerik.Web.Mvc.GridModel @using Telerik.Web.Mvc.UI @{ - //page title ViewBag.Title = T("Admin.Promotions.Discounts").Text; }
    @@ -9,9 +8,14 @@ @T("Admin.Promotions.Discounts")
    + @Html.Widget("admin_button_toolbar_before") +  @T("Admin.Common.AddNew") + + @Html.Widget("admin_button_toolbar_after")
    + - } + }
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Discount/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Discount/_CreateOrUpdate.cshtml index 4a57f98722..6298771348 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Discount/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Discount/_CreateOrUpdate.cshtml @@ -462,5 +462,6 @@ dataBinding.Ajax().Select("UsageHistoryList", "Discount", new { discountId = Model.Id }) .Delete("UsageHistoryDelete", "Discount"); }) + .PreserveGridState() .EnableCustomBinding(true)) } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/_CreateOrUpdate.cshtml index 9028265175..64d56e6030 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/_CreateOrUpdate.cshtml @@ -100,6 +100,7 @@
    +  
    + +@if (Model.TestEmailShortErrorMessage.HasValue()) +{ +

    +

    @Model.TestEmailShortErrorMessage
    +
    @Model.TestEmailFullErrorMessage
    +

    +} + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml new file mode 100644 index 0000000000..ffe062fd5b --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml @@ -0,0 +1,61 @@ +@using SmartStore.Admin.Models.DataExchange +@model ExportProfileModel +@{ + Layout = null; +} + +
    + @if(Model.AvailableProviders.Count > 0) + { +
    + + + + + + @if(Model.AvailableProfiles.Count > 0) + { + + + + + } + + + + +
    + @Html.SmartLabelFor(model => model.ProviderSystemName) + + +
    + @Html.SmartLabelFor(model => model.CloneProfileId) + + +
    +   + + @foreach (var item in Model.AvailableProviders) + { + @item.Description + } +
    +
    + } + else + { + @T("Admin.DataExchange.Export.NoExportProvider") + } +
    \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/CreateDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/CreateDeployment.cshtml new file mode 100644 index 0000000000..875a7268f5 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/CreateDeployment.cshtml @@ -0,0 +1,22 @@ +@model ExportDeploymentModel +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; +@{ + var title = string.Concat(T("Common.Publishing"), " - ", T("Common.AddNew")); + ViewBag.Title = title; +} + +@using (Html.BeginForm()) +{ +
    +
    + + @title @Html.ActionLink("(" + T("Common.Back") + ")", "Edit", new { id = Model.ProfileId }) +
    +
    + + +
    +
    + @Html.Partial("_CreateOrUpdate.Deployment", Model) +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml new file mode 100644 index 0000000000..f47ae86481 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -0,0 +1,398 @@ +@model ExportProfileModel +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; +@{ + var title = string.Concat(T("Common.Profile"), " - ", T("Admin.Common.Edit")); + ViewBag.Title = title; +} + +@using (Html.BeginForm()) +{ +
    + @Html.HiddenFor(model => model.Id) + +
    +
    + + @title - @Model.Name @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List") +
    +
    + @if (Model.HasActiveProvider) + { + if (Model.Id != 0) + { + + } + + +  @T("Admin.Common.Preview") + + } + @if (Model.LogFileExists) + { + +  @T("Admin.Configuration.ActivityLog") + + } + + + @if (!Model.IsSystemProfile) + { + + } +
    +
    + + @Html.ValidationSummary(false) + + @Html.SmartStore().TabStrip().Name("export-profile-edit").Items(x => + { + x.Add().Text(T("Common.General").Text).Content(TabGeneral()).Selected(true); + + if (Model.HasActiveProvider) + { + x.Add().Text(T("Common.Partition").Text).Content(TabPartition()); + x.Add().Text(T("Common.Filter").Text).Content(TabFilter()); + x.Add().Text(T("Common.Projection").Text).Content(TabProjection()); + x.Add().Text(T("Admin.Configuration").Text).Content(TabConfiguration()); + x.Add().Text(T("Common.Deployment").Text).Content(TabDeployment()); + } + + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "export-profile-edit", this.Html, this.Model)); + }) +
    +} + +@helper TabGeneral() +{ + + + + + + + + + + + + + + + + + + @if (Model.ScheduleTaskId > 0) + { + + + + + } +
    + @Html.SmartLabelFor(x => x.Name) + + @Html.EditorFor(x => x.Name) + @Html.ValidationMessageFor(x => x.Name) +
    + @Html.SmartLabelFor(x => x.SystemName) + +
    @(Model.SystemName.NaIfEmpty())
    +
    + @Html.SmartLabelFor(x => x.IsSystemProfile) + + @T(Model.IsSystemProfile ? "Admin.Common.Yes" : "Admin.Common.No") +
    + @Html.SmartLabelFor(x => x.Enabled) + + @Html.EditorFor(x => x.Enabled) + @Html.ValidationMessageFor(x => x.Enabled) +
    + @Html.SmartLabelFor(x => x.ScheduleTaskId) + + @Html.Action("MinimalTask", "ScheduleTask", new { taskId = Model.ScheduleTaskId, returnUrl = Request.RawUrl, cancellable = true, area = "admin" }) +
    + +

     

    + + if (Model.HasActiveProvider) + { + + + + + + @if (Model.Provider.Description.HasValue()) + { + + + + + } + @if (Model.Provider.Url.HasValue()) + { + + + + + } + @if (Model.Provider.Author.HasValue()) + { + + + + + } + @if (Model.Provider.Version.HasValue()) + { + + + + + } + + + + + + + + +
    + @Html.SmartLabelFor(x => x.Provider.FriendlyName) + +
    @Model.Provider.FriendlyName.NaIfEmpty()
    +
    @(Model.ProviderSystemName.NaIfEmpty())
    +
    + @Html.SmartLabelFor(x => x.Provider.Description) + + @Html.Raw(Model.Provider.Description) +
    + @Html.SmartLabelFor(x => x.Provider.Url) + + @Model.Provider.Url +
    + @Html.SmartLabelFor(x => x.Provider.Author) + + @Html.DisplayFor(x => x.Provider.Author) +
    + @Html.SmartLabelFor(x => x.Provider.Version) + + @Html.DisplayFor(x => x.Provider.Version) +
    + @Html.SmartLabelFor(x => x.Provider.EntityTypeName) + + @Html.DisplayFor(x => x.Provider.EntityTypeName) +
    + @Html.SmartLabelFor(x => x.Provider.FileExtension) + + @Html.IconForFileExtension(Model.Provider.FileExtension, true) +
    + } + else + { +
    + @T("Admin.Common.ProviderNotLoaded", Model.ProviderSystemName) +
    + } +} + +@helper TabPartition() +{ +
    + + @Html.Raw(T("Admin.DataExchange.Export.Partition.Note")) +
    + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(x => x.Offset) + + @Html.EditorFor(x => x.Offset) + @Html.ValidationMessageFor(x => x.Offset) +
    + @Html.SmartLabelFor(x => x.Limit) + + @Html.EditorFor(x => x.Limit) + @Html.ValidationMessageFor(x => x.Limit) +
    + @Html.SmartLabelFor(x => x.BatchSize) + + @Html.EditorFor(x => x.BatchSize) + @Html.ValidationMessageFor(x => x.BatchSize) +
    + @Html.SmartLabelFor(x => x.PerStore) + + @Html.EditorFor(x => x.PerStore) + @Html.ValidationMessageFor(x => x.PerStore) +
    +} + +@helper TabFilter() +{ + if (Model.Provider.EntityType == ExportEntityType.Product || + Model.Provider.EntityType == ExportEntityType.Customer || + Model.Provider.EntityType == ExportEntityType.Order || + Model.Provider.EntityType == ExportEntityType.NewsLetterSubscription) + { +
    @Html.Raw(T("Admin.DataExchange.Export.Filter.Note"))
    + @Html.Partial("_Tab.Filter", Model) + } + else + { +
    @T("Admin.DataExchange.Export.NoFiltering")
    + } +} + +@helper TabProjection() +{ + if (Model.Provider.EntityType == ExportEntityType.Product || Model.Provider.EntityType == ExportEntityType.Order) + { +
    @Html.Raw(T("Admin.DataExchange.Export.Projection.Note"))
    + @Html.Partial("_Tab.Projection", Model) + } + else + { +
    @T("Admin.DataExchange.Export.NoProjection")
    + } +} + +@helper TabConfiguration() +{ +
    +
    + @if (Model.Provider.ConfigPartialViewName.HasValue()) + { +
    + + @Html.Raw(T("Admin.DataExchange.Export.Configuration.Note")) +
    + + var viewData = new ViewDataDictionary(); + viewData.TemplateInfo.HtmlFieldPrefix = "CustomProperties[ProviderConfigData]"; + viewData.ModelState.Merge(ViewData.ModelState); + @Html.Hidden("CustomProperties[ProviderConfigData].__Type__", Model.Provider.ConfigDataType.AssemblyQualifiedName) + + try + { + @Html.Partial(Model.Provider.ConfigPartialViewName, Model.Provider.ConfigData, viewData) + } + catch (Exception exception) + { +
    + @T("Admin.Configuration.Plugins.UnknownError") +
    +
    @exception.ToString()
    + } + } + else + { +
    + @Html.Raw(T("Admin.DataExchange.Export.Configuration.NotRequired", Model.Provider.FriendlyName ?? Model.ProviderSystemName)) +
    + } +
    +
    +} + +@helper TabDeployment() +{ + @Html.Partial("_Tab.Deployment", Model) +} + +@Html.DeleteConfirmation("export-profile-delete") + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml new file mode 100644 index 0000000000..7e6a01c1ca --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml @@ -0,0 +1,27 @@ +@model ExportDeploymentModel +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; +@{ + var title = string.Concat(T("Common.Publishing"), " - ", T("Admin.Common.Edit")); + ViewBag.Title = title; +} + +@using (Html.BeginForm()) +{ +
    +
    + + @title - @Model.Name @Html.ActionLink("(" + T("Common.Back") + ")", "Edit", new { id = Model.ProfileId }) +
    +
    + + + +
    +
    + @Html.Partial("_CreateOrUpdate.Deployment", Model) +} + +@Html.DeleteConfirmation("DeleteDeployment", "export-deployment-delete") \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml new file mode 100644 index 0000000000..912809feb9 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml @@ -0,0 +1,41 @@ +@using SmartStore.Admin.Models.DataExchange +@model ProfileInfoForProviderModel +@{ + Layout = null; +} + + @if (Model.Profiles.Count == 0) + { + + + + } + @foreach (var profile in Model.Profiles) + { + + + + + if (profile.ScheduleTaskId.HasValue) + { + + + + + } + } +
    +
    + @Html.Raw(T("Admin.DataExchange.Export.NoProfilesForProvider", Model.SystemName, Url.Action("List", "Export", new { area = "Admin" }))) +
    +
    + @Html.SmartLabelFor(m => m.Profiles) + + + @profile.Name + +
    + @Html.SmartLabel("ProfileScheduleTask" + Model.Profiles.IndexOf(profile).ToString(), T("Common.Execution")) + + @Html.Action("MinimalTask", "ScheduleTask", new { taskId = profile.ScheduleTaskId.Value, returnUrl = Model.ReturnUrl, cancellable = true, area = "admin" }) +
    \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml new file mode 100644 index 0000000000..05bd582199 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -0,0 +1,242 @@ +@using SmartStore.Admin.Models.DataExchange; +@model List +@{ + ViewBag.Title = T("Common.Export").Text; + + var hasUserProfiles = Model.Any(x => !x.IsSystemProfile); + var hasSystemProfiles = Model.Any(x => x.IsSystemProfile); +} + +
    +
    +  @T("Common.Export") +
    +
    + +
    + @Html.Partial("_ProfileList", Model.Where(x => !x.IsSystemProfile), new ViewDataDictionary { { "IsSystemProfile", false } }) + @Html.Partial("_ProfileList", Model.Where(x => x.IsSystemProfile), new ViewDataDictionary { { "IsSystemProfile", true } }) +
    + + + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml new file mode 100644 index 0000000000..c2547a9426 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml @@ -0,0 +1,315 @@ +@using SmartStore.Admin.Models.DataExchange +@using SmartStore.Core.Domain.DataExchange +@model ExportPreviewModel +@{ + var title = string.Concat(T("Common.Export"), " - ", T("Admin.Common.Preview")); + ViewBag.Title = title; +} + +
    +
    + + @title - @Model.Name @Html.ActionLink("(" + T("Common.Back") + ")", "Edit", new { id = Model.Id }) +
    +
    +
    + + + + @if (Model.LogFileExists) + { + +  @T("Admin.Configuration.ActivityLog") + + } + + +
    +
    +
    + + + +
    + @if (Model.EntityType == ExportEntityType.Product) + { + @(Html.Telerik().Grid() + .Name("export-grid-product") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .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(80) + .Centered(); + columns.Bound(x => x.Name) + .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.Published) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.AdminComment); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else if (Model.EntityType == ExportEntityType.Order) + { + @(Html.Telerik().Grid() + .Name("export-grid-order") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .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(80) + .Centered(); + 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.CustomerEmail); + columns.Bound(x => x.StoreName); + columns.Bound(x => x.CreatedOn); + columns.Bound(x => x.OrderTotal) + .Format("{0:0.00}") + .RightAlign(); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else if (Model.EntityType == ExportEntityType.Category) + { + @(Html.Telerik().Grid() + .Name("export-grid-order") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .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(80) + .Centered(); + columns.Bound(x => x.Breadcrumb); + columns.Bound(x => x.FullName); + columns.Bound(x => x.Alias); + columns.Bound(x => x.Published) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(160) + .Centered(); + columns.Bound(x => x.LimitedToStores) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Centered(); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else if (Model.EntityType == ExportEntityType.Manufacturer) + { + @(Html.Telerik().Grid() + .Name("export-grid-order") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .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(80) + .Centered(); + columns.Bound(x => x.Name); + columns.Bound(x => x.Published) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(160) + .Centered(); + columns.Bound(x => x.LimitedToStores) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Centered(); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else if (Model.EntityType == ExportEntityType.Customer) + { + @(Html.Telerik().Grid() + .Name("export-grid-order") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .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(80) + .Centered(); + columns.Bound(x => x.Email); + columns.Bound(x => x.Username) + .Visible(Model.UsernamesEnabled); + columns.Bound(x => x.FullName); + columns.Bound(x => x.CustomerRoleNames); + columns.Bound(x => x.Active) + .ClientTemplate(@Html.SymbolForBool("Active")) + .Width(160) + .Centered(); + columns.Bound(x => x.CreatedOn); + columns.Bound(x => x.LastActivityDate); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else if (Model.EntityType == ExportEntityType.NewsLetterSubscription) + { + @(Html.Telerik().Grid() + .Name("export-grid-order") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .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(80) + .Centered(); + columns.Bound(x => x.Email); + columns.Bound(x => x.Active) + .ClientTemplate(@Html.SymbolForBool("Active")) + .Centered() + .Width(160); + columns.Bound(x => x.StoreName); + columns.Bound(x => x.CreatedOn); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else + { +
    @T("Admin.DataExchange.Export.NoPreview")
    + } +
    \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileCount.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileCount.cshtml new file mode 100644 index 0000000000..1465f01567 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileCount.cshtml @@ -0,0 +1,9 @@ +@model int +@if (Model > 0) +{ + @Model +} +else +{ + 0 +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml new file mode 100644 index 0000000000..e1aaa83f01 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml @@ -0,0 +1,75 @@ +@using SmartStore.Admin.Models.DataExchange; +@using SmartStore.Web.Framework; +@model ExportFileDetailsModel +@{ + ViewBag.DownloadString = T("Common.Download").Text; + ViewBag.AllStoresString = T("Admin.Common.StoresAll").Text; +} +@if (Model.ExportFiles.Any() || Model.PublicFiles.Any()) +{ +
    + @foreach (var grp in Model.ExportFiles.OrderBy(x => x.DisplayOrder).GroupBy(x => x.DisplayOrder)) + { + foreach (var file in grp) + { +
    + @Html.IconForFileExtension(file.FileExtension, false) + @file.FileRootPath +
    + } +

     

    + } + + @foreach (var grp in Model.PublicFiles.OrderBy(x => x.StoreId).GroupBy(x => x.StoreId)) + { + var storeName = grp.First().StoreName; +
    +
    + @T("Admin.Common.PublicFiles") + (@(storeName.HasValue() ? storeName : ViewBag.AllStoresString)) +
    + + @foreach (var file in grp) + { +
    + + @file.FileUrl +
    + } +
    + } +
    +} +else +{ +
    @T("Common.NoFilesAvailable")
    +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml new file mode 100644 index 0000000000..b4ad8eece7 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml @@ -0,0 +1,203 @@ +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Services.DataExchange.Export +@using SmartStore.Admin.Models.DataExchange +@model ExportDeploymentModel + +@Html.ValidationSummary(false) + +@Html.HiddenFor(model => model.Id) +@Html.HiddenFor(model => model.ProfileId) + +@if (!Model.CreateZip) +{ +
    + + @Html.Raw(T("Admin.DataExchange.Export.Deployment.ZipUsageNote")) +
    +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(x => x.DeploymentType) + + + @Html.ValidationMessageFor(x => x.DeploymentType) +
    + @Html.SmartLabelFor(x => x.Name) + + @Html.EditorFor(x => x.Name) + @Html.ValidationMessageFor(x => x.Name) +
    + @Html.SmartLabelFor(x => x.Enabled) + + @Html.EditorFor(x => x.Enabled) + @Html.ValidationMessageFor(x => x.Enabled) +
    + @Html.SmartLabelFor(x => x.FileSystemPath) + + @Html.EditorFor(x => x.FileSystemPath) + @Html.ValidationMessageFor(x => x.FileSystemPath) +
    + @Html.SmartLabelFor(x => x.SubFolder) + + @Html.EditorFor(x => x.SubFolder) + @Html.ValidationMessageFor(x => x.SubFolder) +
    + @Html.SmartLabelFor(x => x.EmailAccountId) + + @Html.DropDownListFor(x => x.EmailAccountId, Model.AvailableEmailAccounts) + @Html.ValidationMessageFor(x => x.EmailAccountId) +
    + @Html.SmartLabelFor(x => x.EmailAddresses) + + @Html.HiddenFor(x => x.EmailAddresses) + @Html.ValidationMessageFor(x => x.EmailAddresses) +
    + @Html.SmartLabelFor(x => x.EmailSubject) + + @Html.EditorFor(x => x.EmailSubject) + @Html.ValidationMessageFor(x => x.EmailSubject) +
    + @Html.SmartLabelFor(x => x.Url) + + @Html.EditorFor(x => x.Url) + @Html.ValidationMessageFor(x => x.Url) +
    + @Html.SmartLabelFor(x => x.HttpTransmissionType) + + @Html.DropDownListFor(x => x.HttpTransmissionType, Model.AvailableHttpTransmissionTypes) + @Html.ValidationMessageFor(x => x.HttpTransmissionType) +
    + @Html.SmartLabelFor(x => x.Username) + + @Html.TextBoxFor(x => x.Username, new { autocomplete = "off", @class = "text-box single-line" }) + @Html.ValidationMessageFor(x => x.Username) +
    + @Html.SmartLabelFor(x => x.Password) + + @Html.TextBoxFor(x => x.Password, new { autocomplete = "off", type = "password", @class = "text-box single-line password" }) + @Html.ValidationMessageFor(x => x.Password) +
    + @Html.SmartLabelFor(x => x.PassiveMode) + + @Html.EditorFor(x => x.PassiveMode) + @Html.ValidationMessageFor(x => x.PassiveMode) +
    + @Html.SmartLabelFor(x => x.UseSsl) + + @Html.EditorFor(x => x.UseSsl) + @Html.ValidationMessageFor(x => x.UseSsl) +
    + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml new file mode 100644 index 0000000000..7ae123c0a9 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml @@ -0,0 +1,102 @@ +@model IEnumerable +@using SmartStore.Admin.Models.DataExchange; +@{ + var isSystemProfiles = (bool)ViewData["IsSystemProfile"]; + var titleResKey = isSystemProfiles ? "Admin.DataExchange.Export.SystemProfilesTitle" : "Admin.DataExchange.Export.UserProfilesTitle"; +} + +
    +
    +
    @T(titleResKey)
    +
    + + @if (isSystemProfiles) + { +
    + + @Html.Raw(T("Admin.DataExchange.Export.SystemProfileNote")) +
    + } + + @if (Model.Any()) + { + + + + + + + + + + + + + + + @foreach (var profile in Model.OrderBy(x => x.Name)) + { + + + + + + + + + + + } + +
    @T("Admin.DataExchange.Export.Name")@T("Admin.DataExchange.Export.EntityType")@T("Admin.DataExchange.Export.FileExtension")@T("Admin.DataExchange.Export.ExportFiles")@T("Admin.System.ScheduleTasks.LastStart")@T("Admin.System.ScheduleTasks.NextRun")@T("Admin.Common.Actions")
    + @profile.Provider.FriendlyName + + @profile.Name +
    @profile.ProviderSystemName
    +
    + @profile.Provider.EntityTypeName.NaIfEmpty() + + @Html.IconForFileExtension(profile.Provider.FileExtension, true) + + @Html.Partial("ProfileFileCount", profile.FileCount) + +
    + @Html.Partial("~/Administration/Views/ScheduleTask/_LastRun.cshtml", profile.TaskModel) +
    +
    +
    + @Html.Partial("~/Administration/Views/ScheduleTask/_NextRun.cshtml", profile.TaskModel) +
    +
    +
    + + + + @T("Common.Cancel") + + + +   @T("Admin.Configuration.ActivityLog") + +
    + } + else + { +
    + + @T("Admin.DataExchange.Export.NoProfiles") +
    + } + + @if (!isSystemProfiles) + { + +   @T("Admin.DataExchange.AddNewProfile") + + } +
    \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml new file mode 100644 index 0000000000..d77bc3cf54 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -0,0 +1,297 @@ +@using SmartStore.Core.Domain.DataExchange +@using SmartStore.Services.DataExchange.Export +@using SmartStore.Admin.Models.DataExchange +@model ExportProfileModel + + + + + + + @if (Model.Provider.IsFileBasedExport) + { + + + + + + + + + + + + + + + + + + + + + + + + + } +
    + @Html.SmartLabelFor(x => x.FolderName) + + @Html.TextBoxFor(x => x.FolderName, new { @class = "input-large" }) + @Html.ValidationMessageFor(x => x.FolderName) +
    + @Html.SmartLabelFor(x => x.FileNamePattern) + +
    + @Html.TextBoxFor(x => x.FileNamePattern, new { @class = "input-large" }) + @Html.ValidationMessageFor(x => x.FileNamePattern) +
    +
    +   + +
    + @T("Admin.Common.Show")… +
    + @FileNamePatternDescription() +
    +
    + +
    +
    + +    +
    @Model.FileNamePatternExample
    +
    +
    + @Html.SmartLabelFor(x => x.CreateZipArchive) + + @Html.EditorFor(x => x.CreateZipArchive) + @Html.ValidationMessageFor(x => x.CreateZipArchive) +
    + @Html.SmartLabelFor(x => x.EmailAccountId) + + @Html.DropDownListFor(x => x.EmailAccountId, Model.AvailableEmailAccounts, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.EmailAccountId) +
    + @Html.SmartLabelFor(x => x.CompletedEmailAddresses) + + @Html.HiddenFor(model => model.CompletedEmailAddresses) + @Html.ValidationMessageFor(x => x.CompletedEmailAddresses) +
    + @Html.SmartLabelFor(x => x.Cleanup) + + @Html.EditorFor(x => x.Cleanup) + @Html.ValidationMessageFor(x => x.Cleanup) +
    + +

     

    + +@if (Model.Provider.IsFileBasedExport) +{ +
    +
    @T("Admin.DataExchange.Export.Deployment.ProfilesTitle")
    +
    + +
    + + @Html.Raw(T("Admin.DataExchange.Export.Deployment.Note")) +
    + + if (!Model.Deployments.Any()) + { +
    + + @T("Admin.DataExchange.Export.Deployment.NoProfiles") +
    + } + + if (Model.Deployments.Count > 0) + { +
    + + + + + + + + + + + + + @foreach (var deployment in Model.Deployments) + { + var result = deployment.LastResult; + + + + + + + + + } + +
    @T("Admin.DataExchange.Export.Deployment.Name")@T("Admin.DataExchange.Export.Deployment.PublishingTarget")@T("Common.Enabled")@T("Admin.DataExchange.Export.ExportFiles")@T("Admin.Common.LastRun")@T("Admin.Common.Actions")
    + +
    +  @deployment.DeploymentTypeName +
    +
    + @if (deployment.DeploymentType == ExportDeploymentType.FileSystem) + { + @deployment.FileSystemPath.NaIfEmpty() + } + else if (deployment.DeploymentType == ExportDeploymentType.Email) + { + @deployment.EmailAddresses.NaIfEmpty() + } + else if (deployment.DeploymentType == ExportDeploymentType.Http || deployment.DeploymentType == ExportDeploymentType.Ftp) + { + @deployment.Url.NaIfEmpty() + } + else if (deployment.DeploymentType == ExportDeploymentType.PublicFolder) + { + @deployment.PublicFolderUrl.NaIfEmpty() + } + + @Html.SymbolForBool(deployment.Enabled) + + @Html.Partial("ProfileFileCount", deployment.FileCount) + + @if (result == null) + { +
    @T("Common.Never")
    + } + else if (result.Succeeded) + { +
    @(T("Admin.Common.SuccessfulOn")): @result.Execution.ToString("g")
    +
    @result.ExecutionPretty
    + } + else + { +
    @result.Execution.ToString("g")
    +
    @T("Common.Error"): @result.Error
    + } +
    + +
    +
    + } + + +   @T("Admin.DataExchange.AddNewProfile") + +} +else +{ +
    + @Html.Raw(T("Admin.DataExchange.Export.NonFileBasedExport.Note")) +
    +} + + + +@helper FileNamePatternDescription() +{ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @T("Admin.Common.Placeholder")@T("Common.Description")
    %Profile.Id%@Model.FileNamePatternDescriptions.SafeGet(0)
    %Profile.FolderName%@Model.FileNamePatternDescriptions.SafeGet(1)
    %Profile.SeoName%@Model.FileNamePatternDescriptions.SafeGet(2)
    %Store.Id%@Model.FileNamePatternDescriptions.SafeGet(3)
    %Store.SeoName%@Model.FileNamePatternDescriptions.SafeGet(4)
    %File.Index%@Model.FileNamePatternDescriptions.SafeGet(5)
    %Random.Number%@Model.FileNamePatternDescriptions.SafeGet(6)
    %Timestamp%@Model.FileNamePatternDescriptions.SafeGet(7)
    +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml new file mode 100644 index 0000000000..eac0b67326 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml @@ -0,0 +1,334 @@ +@model ExportProfileModel +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; + + + @if (Model.Provider.EntityType == ExportEntityType.Product) + { + if (Model.StoreCount > 1) + { + + + + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + else if (Model.Provider.EntityType == ExportEntityType.Customer) + { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + else if (Model.Provider.EntityType == ExportEntityType.Order) + { + if (Model.StoreCount > 1) + { + + + + + } + + + + + + + + + + + + + + + + + + + + + } + else if (Model.Provider.EntityType == ExportEntityType.NewsLetterSubscription) + { + + + + + + + + + + + + + } +
    + @Html.SmartLabelFor(x => x.Filter.StoreId) + + @Html.DropDownListFor(x => x.Filter.StoreId, Model.Filter.AvailableStores, Model.AllString) + @Html.ValidationMessageFor(x => x.Filter.StoreId) +
    + @Html.SmartLabelFor(x => x.Filter.ProductType) + + @Html.DropDownListFor(x => x.Filter.ProductType, Model.Filter.AvailableProductTypes, Model.AllString) +
    + @Html.SmartLabelFor(x => x.Filter.IsPublished) + + @Html.EditorFor(x => x.Filter.IsPublished) + @Html.ValidationMessageFor(x => x.Filter.IsPublished) +
    + @Html.SmartLabelFor(x => x.Filter.IdMinimum) + + @Html.EditorFor(x => x.Filter.IdMinimum) + @Html.ValidationMessageFor(x => x.Filter.IdMinimum) +
    + @Html.SmartLabelFor(x => x.Filter.IdMaximum) + + @Html.EditorFor(x => x.Filter.IdMaximum) + @Html.ValidationMessageFor(x => x.Filter.IdMaximum) +
    + @Html.SmartLabelFor(x => x.Filter.PriceMinimum) + + @Html.EditorFor(x => x.Filter.PriceMinimum) + @Html.ValidationMessageFor(x => x.Filter.PriceMinimum) +
    + @Html.SmartLabelFor(x => x.Filter.PriceMaximum) + + @Html.EditorFor(x => x.Filter.PriceMaximum) + @Html.ValidationMessageFor(x => x.Filter.PriceMaximum) +
    + @Html.SmartLabelFor(x => x.Filter.AvailabilityMinimum) + + @Html.EditorFor(x => x.Filter.AvailabilityMinimum) + @Html.ValidationMessageFor(x => x.Filter.AvailabilityMinimum) +
    + @Html.SmartLabelFor(x => x.Filter.AvailabilityMaximum) + + @Html.EditorFor(x => x.Filter.AvailabilityMaximum) + @Html.ValidationMessageFor(x => x.Filter.AvailabilityMaximum) +
    + @Html.SmartLabelFor(x => x.Filter.WithoutCategories) + + @Html.EditorFor(x => x.Filter.WithoutCategories) + @Html.ValidationMessageFor(x => x.Filter.WithoutCategories) +
    + @Html.SmartLabelFor(x => x.Filter.CategoryIds) + + @Html.ListBoxFor(x => x.Filter.CategoryIds, + new MultiSelectList(Model.Filter.AvailableCategories, "Value", "Text"), + new { multiple = "multiple" }) + @Html.ValidationMessageFor(x => x.Filter.CategoryIds) +
    + @Html.SmartLabelFor(x => x.Filter.WithoutManufacturers) + + @Html.EditorFor(x => x.Filter.WithoutManufacturers) + @Html.ValidationMessageFor(x => x.Filter.WithoutManufacturers) +
    + @Html.SmartLabelFor(x => x.Filter.ManufacturerId) + + @Html.DropDownListFor(x => x.Filter.ManufacturerId, Model.Filter.AvailableManufacturers, Model.AllString) + @Html.ValidationMessageFor(x => x.Filter.ManufacturerId) +
    + @Html.SmartLabelFor(x => x.Filter.ProductTagId) + + @Html.DropDownListFor(x => x.Filter.ProductTagId, Model.Filter.AvailableProductTags, Model.AllString) + @Html.ValidationMessageFor(x => x.Filter.ProductTagId) +
    + @Html.SmartLabelFor(x => x.Filter.FeaturedProducts) + + @Html.EditorFor(x => x.Filter.FeaturedProducts) + @Html.ValidationMessageFor(x => x.Filter.FeaturedProducts) +
    + @Html.SmartLabelFor(x => x.Filter.IsActiveCustomer) + + @Html.EditorFor(x => x.Filter.IsActiveCustomer) + @Html.ValidationMessageFor(x => x.Filter.IsActiveCustomer) +
    + @Html.SmartLabelFor(x => x.Filter.IsTaxExempt) + + @Html.EditorFor(x => x.Filter.IsTaxExempt) + @Html.ValidationMessageFor(x => x.Filter.IsTaxExempt) +
    + @Html.SmartLabelFor(x => x.Filter.CustomerRoleIds) + + @Html.ListBoxFor(x => x.Filter.CustomerRoleIds, + new MultiSelectList(Model.Filter.AvailableCustomerRoles, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.Filter.CustomerRoleIds) +
    + @Html.SmartLabelFor(x => x.Filter.BillingCountryIds) + + @Html.ListBoxFor(x => x.Filter.BillingCountryIds, + new MultiSelectList(Model.Filter.AvailableCountries, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.Filter.BillingCountryIds) +
    + @Html.SmartLabelFor(x => x.Filter.ShippingCountryIds) + + @Html.ListBoxFor(x => x.Filter.ShippingCountryIds, + new MultiSelectList(Model.Filter.AvailableCountries, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.Filter.ShippingCountryIds) +
    + @Html.SmartLabelFor(x => x.Filter.LastActivityFrom) + + @Html.EditorFor(x => x.Filter.LastActivityFrom) + @Html.ValidationMessageFor(x => x.Filter.LastActivityFrom) +
    + @Html.SmartLabelFor(x => x.Filter.LastActivityTo) + + @Html.EditorFor(x => x.Filter.LastActivityTo) + @Html.ValidationMessageFor(x => x.Filter.LastActivityTo) +
    + @Html.SmartLabelFor(x => x.Filter.HasSpentAtLeastAmount) + + @Html.EditorFor(x => x.Filter.HasSpentAtLeastAmount) + @Html.ValidationMessageFor(x => x.Filter.HasSpentAtLeastAmount) +
    + @Html.SmartLabelFor(x => x.Filter.HasPlacedAtLeastOrders) + + @Html.EditorFor(x => x.Filter.HasPlacedAtLeastOrders) + @Html.ValidationMessageFor(x => x.Filter.HasPlacedAtLeastOrders) +
    + @Html.SmartLabelFor(x => x.Filter.StoreId) + + @Html.DropDownListFor(x => x.Filter.StoreId, Model.Filter.AvailableStores, Model.AllString) + @Html.ValidationMessageFor(x => x.Filter.StoreId) +
    + @Html.SmartLabelFor(x => x.Filter.CreatedFrom) + + @Html.EditorFor(x => x.Filter.CreatedFrom) + @Html.ValidationMessageFor(x => x.Filter.CreatedFrom) +
    + @Html.SmartLabelFor(x => x.Filter.CreatedTo) + + @Html.EditorFor(x => x.Filter.CreatedTo) + @Html.ValidationMessageFor(x => x.Filter.CreatedTo) +
    + @Html.SmartLabelFor(x => x.Filter.OrderStatusIds) + + @Html.ListBoxFor(x => x.Filter.OrderStatusIds, + new MultiSelectList(Model.Filter.AvailableOrderStates, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.Filter.OrderStatusIds) +
    + @Html.SmartLabelFor(x => x.Filter.PaymentStatusIds) + + @Html.ListBoxFor(x => x.Filter.PaymentStatusIds, + new MultiSelectList(Model.Filter.AvailablePaymentStates, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.Filter.PaymentStatusIds) +
    + @Html.SmartLabelFor(x => x.Filter.ShippingStatusIds) + + @Html.ListBoxFor(x => x.Filter.ShippingStatusIds, + new MultiSelectList(Model.Filter.AvailableShippingStates, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.Filter.ShippingStatusIds) +
    + @Html.SmartLabelFor(x => x.Filter.IsActiveSubscriber) + + @Html.EditorFor(x => x.Filter.IsActiveSubscriber) + @Html.ValidationMessageFor(x => x.Filter.IsActiveSubscriber) +
    + @Html.SmartLabelFor(x => x.Filter.CreatedFrom) + + @Html.EditorFor(x => x.Filter.CreatedFrom) + @Html.ValidationMessageFor(x => x.Filter.CreatedFrom) +
    + @Html.SmartLabelFor(x => x.Filter.CreatedTo) + + @Html.EditorFor(x => x.Filter.CreatedTo) + @Html.ValidationMessageFor(x => x.Filter.CreatedTo) +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml new file mode 100644 index 0000000000..ebe842977e --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml @@ -0,0 +1,252 @@ +@model ExportProfileModel +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; + + + + + + + + + + + + + + + + + + + + @if (Model.Provider.EntityType == ExportEntityType.Product) + { + if (Model.Provider.Feature.HasFlag(ExportFeatures.CanProjectAttributeCombinations)) + { + + + + + + + + + + + + } + + if (Model.Provider.Feature.HasFlag(ExportFeatures.CanOmitGroupedProducts)) + { + + + + + + + + } + + + + + + + + + + + if (Model.Provider.Feature.HasFlag(ExportFeatures.OffersShippingTimeFallback)) + { + + + + + } + + if (Model.Provider.Feature.HasFlag(ExportFeatures.OffersShippingCostsFallback)) + { + + + + + + + + + } + + + + + + + if (Model.Provider.Feature.HasFlag(ExportFeatures.CanIncludeMainPicture)) + { + + + + + } + + if (Model.Provider.Feature.HasFlag(ExportFeatures.OffersBrandFallback)) + { + + + + + } + + if (Model.Provider.Feature.HasFlag(ExportFeatures.CanProjectDescription)) + { + + + + + + + + + + + + + + + + + + + + + + + + } + } + else if (Model.Provider.EntityType == ExportEntityType.Order) + { + + + + + } +
    + @Html.SmartLabelFor(x => x.Projection.StoreId) + + @Html.DropDownListFor(x => x.Projection.StoreId, Model.Projection.AvailableStores, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.Projection.StoreId) +
    + @Html.SmartLabelFor(x => x.Projection.LanguageId) + + @Html.DropDownListFor(x => x.Projection.LanguageId, Model.Projection.AvailableLanguages, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.Projection.LanguageId) +
    + @Html.SmartLabelFor(x => x.Projection.CurrencyId) + + @Html.DropDownListFor(x => x.Projection.CurrencyId, Model.Projection.AvailableCurrencies, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.Projection.CurrencyId) +
    + @Html.SmartLabelFor(x => x.Projection.CustomerId) + + @Html.EditorFor(x => x.Projection.CustomerId) + @Html.ValidationMessageFor(x => x.Projection.CustomerId) +
    + @Html.SmartLabelFor(x => x.Projection.AttributeCombinationAsProduct) + + @Html.EditorFor(x => x.Projection.AttributeCombinationAsProduct) + @Html.ValidationMessageFor(x => x.Projection.AttributeCombinationAsProduct) +
    + @Html.SmartLabelFor(x => x.Projection.AttributeCombinationValueMergingId) + + @Html.DropDownListFor(x => x.Projection.AttributeCombinationValueMergingId, Model.Projection.AvailableAttributeCombinationValueMerging) + @Html.ValidationMessageFor(x => x.Projection.AttributeCombinationValueMergingId) +
    +
    + @T("Admin.DataExchange.Export.NotPreviewCompatible") +
    +
    + @Html.SmartLabelFor(x => x.Projection.NoGroupedProducts) + + @Html.EditorFor(x => x.Projection.NoGroupedProducts) + @Html.ValidationMessageFor(x => x.Projection.NoGroupedProducts) +
    +
    + @T("Admin.DataExchange.Export.NotPreviewCompatible") +
    +
    + @Html.SmartLabelFor(x => x.Projection.PriceType) + + @Html.DropDownListFor(x => x.Projection.PriceType, Model.Projection.AvailablePriceTypes, T("Admin.Catalog.Products.Fields.Price")) + @Html.ValidationMessageFor(x => x.Projection.PriceType) +
    + @Html.SmartLabelFor(x => x.Projection.ConvertNetToGrossPrices) + + @Html.EditorFor(x => x.Projection.ConvertNetToGrossPrices) + @Html.ValidationMessageFor(x => x.Projection.ConvertNetToGrossPrices) +
    + @Html.SmartLabelFor(x => x.Projection.ShippingTime) + + @Html.EditorFor(x => x.Projection.ShippingTime) + @Html.ValidationMessageFor(x => x.Projection.ShippingTime) +
    + @Html.SmartLabelFor(x => x.Projection.ShippingCosts) + + @Html.EditorFor(x => x.Projection.ShippingCosts) + @Html.ValidationMessageFor(x => x.Projection.ShippingCosts) +
    + @Html.SmartLabelFor(x => x.Projection.FreeShippingThreshold) + + @Html.EditorFor(x => x.Projection.FreeShippingThreshold) + @Html.ValidationMessageFor(x => x.Projection.FreeShippingThreshold) +
    + @Html.SmartLabelFor(x => x.Projection.NumberOfPictures) + + @Html.EditorFor(x => x.Projection.NumberOfPictures) + @Html.ValidationMessageFor(x => x.Projection.NumberOfPictures) +
    + @Html.SmartLabelFor(x => x.Projection.PictureSize) + + @Html.EditorFor(x => x.Projection.PictureSize) + @Html.ValidationMessageFor(x => x.Projection.PictureSize) +
    + @Html.SmartLabelFor(x => x.Projection.Brand) + + @Html.EditorFor(x => x.Projection.Brand) + @Html.ValidationMessageFor(x => x.Projection.Brand) +
    +
    +
    @T("Common.DetailDescription")
    +
    +
    + @Html.SmartLabelFor(x => x.Projection.DescriptionMergingId) + + @Html.DropDownListFor(x => x.Projection.DescriptionMergingId, Model.Projection.AvailableDescriptionMergings) + @Html.ValidationMessageFor(x => x.Projection.DescriptionMergingId) +
    + @Html.SmartLabelFor(x => x.Projection.AppendDescriptionText) + + @Html.HiddenFor(x => x.Projection.AppendDescriptionText) + @Html.ValidationMessageFor(x => x.Projection.AppendDescriptionText) +
    + @Html.SmartLabelFor(x => x.Projection.RemoveCriticalCharacters) + + @Html.EditorFor(x => x.Projection.RemoveCriticalCharacters) + @Html.ValidationMessageFor(x => x.Projection.RemoveCriticalCharacters) +
    + @Html.SmartLabelFor(x => x.Projection.CriticalCharacters) + + @Html.HiddenFor(x => x.Projection.CriticalCharacters) + @Html.ValidationMessageFor(x => x.Projection.CriticalCharacters) +
    + @Html.SmartLabelFor(x => x.Projection.DescriptionToPlainText) + + @Html.EditorFor(x => x.Projection.DescriptionToPlainText) + @Html.ValidationMessageFor(x => x.Projection.DescriptionToPlainText) +
    + @Html.SmartLabelFor(x => x.Projection.OrderStatusChangeId) + + @Html.DropDownListFor(x => x.Projection.OrderStatusChangeId, Model.Projection.AvailableOrderStatusChange) + @Html.ValidationMessageFor(x => x.Projection.OrderStatusChangeId) +
    \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForum.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForum.cshtml index fee65aa9b4..9a23660e2e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForum.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForum.cshtml @@ -1,6 +1,5 @@ @model ForumModel @{ - //page title ViewBag.Title = T("Admin.ContentManagement.Forums.Forum.EditForum").Text; } @using (Html.BeginForm()) @@ -11,9 +10,13 @@ @T("Admin.ContentManagement.Forums.Forum.EditForum") - @Model.Name @Html.ActionLink(String.Format("({0})", T("Admin.ContentManagement.Forums.Forum.BackToList")), "List")
    + @Html.Widget("admin_button_toolbar_before") + + + @Html.Widget("admin_button_toolbar_after")
    @Html.Partial("_CreateOrUpdateForum", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForumGroup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForumGroup.cshtml index 86e7511073..10b69041aa 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForumGroup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Forum/EditForumGroup.cshtml @@ -1,6 +1,5 @@ @model ForumGroupModel @{ - //page title ViewBag.Title = T("Admin.ContentManagement.Forums.ForumGroup.EditForumGroup").Text; } @using (Html.BeginForm()) @@ -11,9 +10,13 @@ @T("Admin.ContentManagement.Forums.ForumGroup.EditForumGroup") - @Model.Name @Html.ActionLink(String.Format("({0})", T("Admin.ContentManagement.Forums.ForumGroup.BackToList")), "List")
    + @Html.Widget("admin_button_toolbar_before") + + + @Html.Widget("admin_button_toolbar_after")
    @Html.Partial("_CreateOrUpdateForumGroup", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Forum/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Forum/List.cshtml index 7609a604b3..18d9977572 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Forum/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Forum/List.cshtml @@ -1,7 +1,6 @@ @model IEnumerable @using Telerik.Web.Mvc.UI; @{ - //page title ViewBag.Title = T("Admin.ContentManagement.Forums.Manage").Text; }
    @@ -9,11 +8,17 @@ @T("Admin.ContentManagement.Forums.Manage")
    +
    + @Html.Widget("admin_button_toolbar_before") + + @Html.Widget("admin_button_toolbar_after") +
    + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Language/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Language/_CreateOrUpdate.cshtml index 4c01f7f071..7f393dd8c5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Language/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Language/_CreateOrUpdate.cshtml @@ -1,9 +1,7 @@ -@model LanguageModel +@using SmartStore.Web.Framework.UI; +@model LanguageModel -@using Telerik.Web.Mvc.UI; -@using SmartStore.Web.Framework.UI; - -@Html.ValidationSummary(true) +@Html.ValidationSummary(false) @Html.HiddenFor(model => model.Id) @Html.SmartStore().TabStrip().Name("language-edit").Style(TabsStyle.Tabs).Position(TabsPosition.Top).Items(x => { @@ -16,20 +14,7 @@ @helper TabInfo() { - var cultures = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.SpecificCultures).ToList(); - - -
    - @{ Html.Telerik().Grid(Model) + @{Html.Telerik().Grid(Model) .Name("forumgroup-grid") .ToolBar(commands => { @@ -26,8 +31,17 @@ .Template(x => Html.ActionLink(x.Name, "EditForumGroup", new { id = x.Id })) .ClientTemplate("\"><#= Name #>"); - columns.Bound(x => x.DisplayOrder).Width(150).Centered(); - columns.Bound(x => x.CreatedOn).Width(200); + columns.Bound(x => x.DisplayOrder) + .Width(150) + .Centered(); + columns.Bound(x => x.LimitedToStores) + .Template(item => @Html.SymbolForBool(item.LimitedToStores)) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Centered() + .Width(100) + .Hidden(ViewBag.StoreCount <= 1); + columns.Bound(x => x.CreatedOn) + .Width(200); columns.Bound(x => x.Id) .Width(50) .Centered() diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForum.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForum.cshtml index 8f2a0c04d6..08b4cea0c7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForum.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForum.cshtml @@ -3,42 +3,95 @@ @using Telerik.Web.Mvc.UI; @Html.ValidationSummary(true) @Html.HiddenFor(model => model.Id) + +@(Html.LocalizedEditor("forumgroup-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].SeName) + + @Html.EditorFor(model => model.Locales[item].SeName) + @Html.ValidationMessageFor(model => model.Locales[item].SeName) +
    + @Html.SmartLabelFor(model => model.Locales[item].Description) + + @Html.TextAreaFor(model => model.Locales[item].Description, new { @class = "input-large" }) + @Html.ValidationMessageFor(model => model.Locales[item].Description) +
    + @Html.HiddenFor(model => model.Locales[item].LanguageId) +
    + , + @ + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.Name) + + @Html.EditorFor(model => model.Name) + @Html.ValidationMessageFor(model => model.Name) +
    + @Html.SmartLabelFor(model => model.SeName) + + @Html.EditorFor(x => x.SeName) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "Forum", entityId = @Model.Id }) + } + @Html.ValidationMessageFor(model => model.SeName) +
    + @Html.SmartLabelFor(model => model.Description) + + @Html.TextAreaFor(model => model.Description, new { @class = "input-large" }) + @Html.ValidationMessageFor(model => model.Description) +
    +)) + - - - - - - - - - - - - - - - - + + + + + + + +
    - @Html.SmartLabelFor(model => model.ForumGroupId) - - @Html.DropDownListFor(model => model.ForumGroupId, Model.ForumGroups.Select(x => - new SelectListItem { Value = x.Id.ToString(), Text = x.Name })) - @Html.ValidationMessageFor(model => model.ForumGroupId) -
    - @Html.SmartLabelFor(model => model.Name) - - @Html.EditorFor(model => model.Name) - @Html.ValidationMessageFor(model => model.Name) -
    - @Html.SmartLabelFor(model => model.Description) - - @Html.TextAreaFor(model => model.Description) - @Html.ValidationMessageFor(model => model.Description) -
    - @Html.SmartLabelFor(model => model.DisplayOrder) - - @Html.EditorFor(model => model.DisplayOrder) - @Html.ValidationMessageFor(model => model.DisplayOrder) -
    + @Html.SmartLabelFor(model => model.ForumGroupId) + + @Html.DropDownListFor(model => model.ForumGroupId, Model.ForumGroups.Select(x => new SelectListItem { Value = x.Id.ToString(), Text = x.Name })) + @Html.ValidationMessageFor(model => model.ForumGroupId) +
    + @Html.SmartLabelFor(model => model.DisplayOrder) + + @Html.EditorFor(model => model.DisplayOrder) + @Html.ValidationMessageFor(model => model.DisplayOrder) +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml index 376faad25c..8aaf21cf5e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml @@ -4,32 +4,142 @@ @Html.ValidationSummary(true) @Html.HiddenFor(model => model.Id) - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.Name) - - @Html.EditorFor(model => model.Name) - @Html.ValidationMessageFor(model => model.Name) -
    - @Html.SmartLabelFor(model => model.Description) - - @Html.TextAreaFor(model => model.Description) - @Html.ValidationMessageFor(model => model.Description) -
    - @Html.SmartLabelFor(model => model.DisplayOrder) - - @Html.EditorFor(model => model.DisplayOrder) - @Html.ValidationMessageFor(model => model.DisplayOrder) -
    + +@Html.SmartStore().TabStrip().Name("forumgroup-edit").Items(x => +{ + x.Add().Text(T("Admin.Common.General").Text).Content(TabInfo()).Selected(true); + x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); + + //generate an event + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "forumgroup-edit", this.Html, this.Model)); +}) + +@helper TabInfo() +{ + @(Html.LocalizedEditor("forumgroup-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].SeName) + + @Html.EditorFor(model => model.Locales[item].SeName) + @Html.ValidationMessageFor(model => model.Locales[item].SeName) +
    + @Html.SmartLabelFor(model => model.Locales[item].Description) + + @Html.TextAreaFor(model => model.Locales[item].Description, new { @class = "input-large" }) + @Html.ValidationMessageFor(model => model.Locales[item].Description) +
    + @Html.HiddenFor(model => model.Locales[item].LanguageId) +
    + , + @ + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.Name) + + @Html.EditorFor(model => model.Name) + @Html.ValidationMessageFor(model => model.Name) +
    + @Html.SmartLabelFor(model => model.SeName) + + @Html.EditorFor(x => x.SeName) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "ForumGroup", entityId = @Model.Id }) + } + @Html.ValidationMessageFor(model => model.SeName) +
    + @Html.SmartLabelFor(model => model.Description) + + @Html.TextAreaFor(model => model.Description, new { @class = "input-large" }) + @Html.ValidationMessageFor(model => model.Description) +
    + )) + + + + + + +
    + @Html.SmartLabelFor(model => model.DisplayOrder) + + @Html.EditorFor(model => model.DisplayOrder) + @Html.ValidationMessageFor(model => model.DisplayOrder) +
    +} + +@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")
    + } +
    +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml index eaa1ba2ad2..96727f6ecb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml @@ -1,9 +1,6 @@ @model GiftCardListModel @using Telerik.Web.Mvc.UI @{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title ViewBag.Title = T("Admin.GiftCards").Text; } @using (Html.BeginForm()) @@ -20,34 +17,36 @@ - - - - - - - - - - - + +
    - @Html.SmartLabelFor(model => model.ActivatedId) - - @Html.DropDownListFor(model => model.ActivatedId, Model.ActivatedList) -
    - @Html.SmartLabelFor(model => model.CouponCode) - - @Html.EditorFor(model => model.CouponCode) -
    + + + + + + + + + + + + - -
    + @Html.SmartLabelFor(model => model.ActivatedId) + + @Html.DropDownListFor(model => model.ActivatedId, Model.ActivatedList, T("Admin.Common.All")) +
    + @Html.SmartLabelFor(model => model.CouponCode) + + @Html.EditorFor(model => model.CouponCode) +
      - -
    -

    -

    + +
    + +

    + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/_CreateOrUpdate.cshtml index d4f07648b0..77250e5af5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/_CreateOrUpdate.cshtml @@ -213,6 +213,7 @@ { dataBinding.Ajax().Select("UsageHistoryList", "GiftCard", new { giftCardId = Model.Id }); }) + .PreserveGridState() .EnableCustomBinding(true)) } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Home/About.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Home/About.cshtml index 789f109d5a..d8f6f806a1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Home/About.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Home/About.cshtml @@ -1,12 +1,12 @@ @{ - ViewBag.Title = T("Admin.Common.About").Text + " SmartStore.NET"; + ViewBag.Title = T("Admin.Common.About").Text; string license = T("Admin.Common.License").Text; }
    - @T("Admin.Common.About") SmartStore.NET + @T("Admin.Common.About")
    @@ -29,17 +29,6 @@

    - \ No newline at end of file + + +@helper MPL20License() +{ +
    + @T("Admin.Common.License"): Mozilla Public License 2.0 (MPL-2.0) +
    +} + +@helper BSDLicense() +{ +
    + @T("Admin.Common.License"): BSD +
    +} + +@helper MVCEULA() +{ +
    + @T("Admin.Common.License"): MICROSOFT ASP.NET MODEL VIEW CONTROLLER 3 EULA +
    +} + +@helper MITLicense() +{ +
    + @T("Admin.Common.License"): MIT +
    +} + +@helper Apache2License() +{ +
    + @T("Admin.Common.License"): Apache License 2.0 +
    +} + +@helper MSPLLicense() +{ +
    + @T("Admin.Common.License"): MS-PL +
    +} + +@helper LGPLLicense() +{ +
    + @T("Admin.Common.License"): GNU Lesser General Public License (LGPL) +
    +} + +@helper FreedomLicense() +{ +
    + @T("Admin.Common.License"): Freedom License +
    +} + +@helper zlibLicense() +{ +
    + @T("Admin.Common.License"): Derived from zlib +
    +} + +@helper GPLv3License() +{ +
    + @T("Admin.Common.License"): GPL 3.0 +
    +} + +@helper CreativeCommonsLicense() +{ + +} + +@helper SqlCeEULA() +{ +
    + @T("Admin.Common.License"): Custom EULA +
    +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Home/Index.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Home/Index.cshtml index cc01d61e3e..8842c7648b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Home/Index.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Home/Index.cshtml @@ -11,7 +11,7 @@
    - @Html.Action("WidgetsByZone", "Widget", new { widgetZone = "admin_dashboard_top" }) + @Html.Widget("admin_dashboard_top")
    @@ -39,6 +39,6 @@
    - @Html.Action("WidgetsByZone", "Widget", new { widgetZone = "admin_dashboard_bottom" }) + @Html.Widget("admin_dashboard_bottom")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Home/MarketplaceFeed.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Home/MarketplaceFeed.cshtml index a2bde39bcb..8b0c7061da 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Home/MarketplaceFeed.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Home/MarketplaceFeed.cshtml @@ -45,7 +45,7 @@ } - + @T("Admin.Marketplace.Visit") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Home/UaTester.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Home/UaTester.cshtml new file mode 100644 index 0000000000..285780b2e6 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Home/UaTester.cshtml @@ -0,0 +1,40 @@ +@model IUserAgent +@using SmartStore.Services.Common +@{ + //page title + ViewBag.Title = "User Agent Tester"; + var ua = Model; +} + +
    +
    + + User Agent Tester +
    +
    + +
    + @using (Html.BeginForm(null, null, FormMethod.Get)) + { +
    + @Html.TextArea("ua", ua.RawValue, new { style = "width: 800px; display: block" }) + +
    + +
    + OS: @ua.OS.Family (@ua.OS)
    + User Agent: @ua.UserAgent.Family, (@ua.UserAgent)
    + Device: @ua.Device.Family, (@ua.Device)
    +
    + IsBot: @ua.IsBot
    + IsMobileDevice: @ua.IsMobileDevice
    + IsTablet: @ua.IsTablet
    + IsPdfConverter: @ua.IsPdfConverter
    +
    + } +
    + + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/Edit.cshtml new file mode 100644 index 0000000000..1adf73df3b --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/Edit.cshtml @@ -0,0 +1,43 @@ +@using SmartStore.Admin.Models.DataExchange; +@model ImportProfileModel +@{ + var title = string.Concat(T("Common.Profile"), " - ", T("Admin.Common.Edit")); + ViewBag.Title = title; +} + +@using (Html.BeginForm()) +{ +
    +
    +
    + + @title - @Model.Name @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List") +
    +
    + @if (Model.Id != 0) + { + + } + @if (Model.LogFileExists) + { + +  @T("Admin.Configuration.ActivityLog") + + } + + + +
    +
    + @Html.Partial("_Update", Model) +
    +} +@Html.DeleteConfirmation("import-profile-delete") \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml new file mode 100644 index 0000000000..d97745db61 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml @@ -0,0 +1,256 @@ +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; +@model ImportProfileListModel +@{ + Html.AddScriptParts("~/bundles/fileupload"); + Html.AddCssFileParts("~/css/fileupload"); + + ViewBag.Title = T("Common.Import").Text; +} + +
    +
    + + @T("Common.Import") +
    + +
    + +@if(Model.Profiles.Any()) +{ +
    @@ -72,11 +71,12 @@ .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") .Width(50) .Centered() - .HeaderTemplate(T("Admin.Common.Edit").Text) + .HeaderTemplate("{0}".FormatWith(T("Admin.Common.Edit").Text)) .Filterable(false); }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("GiftCardList", "GiftCard", Model)) + .PreserveGridState() .EnableCustomBinding(true))
    + + + + + + + + + + + + + @foreach (var profile in Model.Profiles) + { + + + + + + + + + + } + +
    @T("Admin.DataExchange.Import.Name")@T("Admin.Common.Entity")@T("Admin.Common.ImportFiles")@T("Admin.DataExchange.Import.LastImportResult")@T("Admin.System.ScheduleTasks.LastStart")@T("Admin.System.ScheduleTasks.NextRun")@T("Admin.Common.Actions")
    + @profile.Name + + @profile.EntityTypeName + + @foreach (var fileName in profile.ExistingFileNames) + { +
      +
    • + @Html.IconForFileExtension(Path.GetExtension(fileName), "fa-li", false) + @fileName +
    • +
    + } +
    + @if (profile.ImportResult != null) + { + @Html.Partial("ProfileImportResult", profile.ImportResult) + } + else + { + @("".NaIfEmpty()) + } + +
    + @Html.Partial("~/Administration/Views/ScheduleTask/_LastRun.cshtml", profile.TaskModel) +
    +
    +
    + @Html.Partial("~/Administration/Views/ScheduleTask/_NextRun.cshtml", profile.TaskModel) +
    +
    +
    + + + + @T("Common.Cancel") + + + +  @T("Admin.Configuration.ActivityLog") + +
    +} +else +{ +
    + + @T("Admin.DataExchange.Import.NoProfiles") +
    +} + +
    + +
    + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/ProfileImportResult.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/ProfileImportResult.cshtml new file mode 100644 index 0000000000..03d0192fab --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/ProfileImportResult.cshtml @@ -0,0 +1,17 @@ +@using SmartStore.Services.DataExchange.Import; +@model SerializableImportResult +@{ + Layout = null; +} +
    + @T("Admin.Common.TotalRows"): @Model.TotalRecords · + @T("Admin.Common.Skipped"): @Model.SkippedRecords +
    +
    + @T("Admin.Common.NewRecords"): @Model.NewRecords · + @T("Admin.Common.Updated"): @Model.ModifiedRecords +
    +
    + @T("Admin.Common.Errors"): @Model.Errors · + @T("Admin.Common.Warnings"): @Model.Warnings +
    \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml new file mode 100644 index 0000000000..be30b9c8e5 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -0,0 +1,180 @@ +@using SmartStore.Admin.Models.DataExchange; +@model ImportProfileModel +@{ + var delete = T("Common.Delete"); + var ignore = @T("Admin.Common.Ignore"); + var selectTargetProperty = T("Admin.DataExchange.Import.SelectTargetProperty"); +} + +
    + + + + + + + +
    +
    +
    @T("Admin.DataExchange.Import.ColumnMapping")
    +
    +
    +
    + + @Html.Raw(T("Admin.DataExchange.ColumnMapping.Note")) +
    + @if (Model.ColumnMappings.Any()) + { + + } +
    +
    + +
    +
    + + + +
    +
    + +
    +
    + + + +
    +
    + +
    + + + + + + + + + +
    +
    + +
    + + + + + +
    + +
    +
    +
    + +
    + +
    + +
    +
    + + + + +
    +
    + +
    + + + + + + + + + + + @foreach (var mapping in Model.ColumnMappings) + { + var index = Model.ColumnMappings.IndexOf(mapping) + 1; + + + + + + + } + +
    @T("Admin.DataExchange.ColumnMapping.EntityProperty")@T("Admin.DataExchange.ColumnMapping.ImportField")@T("Admin.DataExchange.ColumnMapping.DefaultValue") 
    +
    + +
    + + + + @if (mapping.PropertyDescription.Length > Model.MaxMappingLabelTextLength) + { + @(mapping.PropertyDescription.Substring(0, Model.MaxMappingLabelTextLength))… + } + else + { + @mapping.PropertyDescription + } + @mapping.Property +
    +
    +
    +
    + +
    + + @if (mapping.Column.HasValue()) + { + @mapping.Column + } + else + { + @ignore + } + +
    +
    +
    + + + +
    +
    \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml new file mode 100644 index 0000000000..e15b6534fe --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml @@ -0,0 +1,534 @@ +@using SmartStore.Admin.Models.DataExchange; +@using SmartStore.Core.Domain.DataExchange; +@model ImportProfileModel +@{ + Html.AddScriptParts("~/bundles/fileupload"); + Html.AddCssFileParts("~/css/fileupload"); +} + +@Html.ValidationSummary(false) +@Html.HiddenFor(model => model.Id) +@Html.HiddenFor(model => model.TempFileName) +@Html.HiddenFor(model => model.EntityType) + +@if (Model.ExistingFileNames.Count > 1) +{ +
    + + @T("Admin.DataExchange.Import.MultipleFilesSameFileTypeNote") +
    +} + +
    + + + + + + + + + + @if (Model.ScheduleTaskId > 0) + { + + + + + } + @if (Model.ImportResult != null) + { + + + + + } + + + + + + + + + + + + + + + + + + + + @if (Model.KeyFieldNames != null && Model.KeyFieldNames.Contains("Id", StringComparer.OrdinalIgnoreCase)) + { + + + + } + + + + + + + + + + + + + @if (Model.EntityType == ImportEntityType.Product) + { + + + + + } +
    +   + + +
    + + + @T(Model.ExistingFileNames.Any() ? "Admin.DataExchange.Import.AddAnotherFile" : "Admin.DataExchange.Import.FileUpload") + + + + + +
    +
    +
    +
    +
     
    +
    +
    + +
    +   + + @foreach (var fileName in Model.ExistingFileNames) + { +

    + @Html.IconForFileExtension(Path.GetExtension(fileName), false) + @fileName + + +

    + } +
    + @Html.SmartLabelFor(x => x.ScheduleTaskId) + + @Html.Action("MinimalTask", "ScheduleTask", new { taskId = Model.ScheduleTaskId, returnUrl = Request.RawUrl, cancellable = true, reloadPage = true, area = "admin" }) +
    + @Html.SmartLabelFor(x => x.ImportResult) + + @Html.Partial("ProfileImportResult", Model.ImportResult) +
     
    + @Html.SmartLabelFor(x => x.Enabled) + + @Html.EditorFor(x => x.Enabled) + @Html.ValidationMessageFor(x => x.Enabled) +
    + @Html.SmartLabelFor(x => x.EntityTypeName) + + @Html.DisplayFor(x => x.EntityTypeName) +
    + @Html.SmartLabelFor(x => x.Name) + + @Html.EditorFor(x => x.Name) + @Html.ValidationMessageFor(x => x.Name) +
    + @Html.SmartLabelFor(model => model.KeyFieldNames) + + @*ListBoxFor is insufficient here because the user cannot influence the stored order*@ + + @Html.ValidationMessageFor(model => model.KeyFieldNames) +
    +
    + @T("Admin.DataExchange.Import.KeyFieldNames.Note") +
    +
    + @Html.SmartLabelFor(x => x.Skip) + + @Html.EditorFor(x => x.Skip) + @Html.ValidationMessageFor(x => x.Skip) +
    + @Html.SmartLabelFor(x => x.Take) + + @Html.EditorFor(x => x.Take) + @Html.ValidationMessageFor(x => x.Take) +
    + @Html.SmartLabelFor(x => x.UpdateOnly) + + @Html.EditorFor(x => x.UpdateOnly) + @Html.ValidationMessageFor(x => x.UpdateOnly) +
    + @Html.SmartLabelFor(x => x.ExtraData.NumberOfPictures) + + @Html.EditorFor(x => x.ExtraData.NumberOfPictures) + @Html.ValidationMessageFor(x => x.ExtraData.NumberOfPictures) +
    +
    + +@if (Model.CsvConfiguration != null) +{ + var viewData = new ViewDataDictionary(); + viewData.Add("ShowGroupCaption", true); + viewData.TemplateInfo.HtmlFieldPrefix = "CsvConfiguration"; + viewData.ModelState.Merge(ViewData.ModelState); + @Html.Partial("CsvConfiguration", Model.CsvConfiguration, viewData) +} + +@if (Model.ExistingFileNames.Any()) +{ + @Html.Partial("_ColumnMappings") +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml index 362dd5f5a6..91c1fe8140 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml @@ -1,7 +1,6 @@ @model Telerik.Web.Mvc.GridModel @using Telerik.Web.Mvc.UI @{ - //page title ViewBag.Title = T("Admin.Configuration.Languages").Text; }
    @@ -24,7 +23,7 @@ columns.Bound(x => x.Name) .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })); columns.Bound(x => x.LanguageCulture) - .Width(150) + .Width(200) .Centered(); columns.Template( @
    @@ -34,10 +33,10 @@ .Centered() .Title(T("Admin.Configuration.Languages.Resources.View").Text); columns.Bound(x => x.DisplayOrder) - .Width(100) + .Width(200) .Centered(); columns.Bound(x => x.Published) - .Width(100) + .Width(200) .Template(item => @Html.SymbolForBool(item.Published)) .ClientTemplate(@Html.SymbolForBool("Published")) .Centered(); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Language/Resources.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Language/Resources.cshtml index 5b1470dcb1..9e27c6c860 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Language/Resources.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Language/Resources.cshtml @@ -79,6 +79,7 @@ .Insert("ResourceAdd", "Language", new { id = ViewBag.LanguageId }); }) .EnableCustomBinding(true) + .PreserveGridState() .ClientEvents(x => x.OnEdit("onResourceEdit")) .ClientEvents(x => x.OnError("Grid_onError")))
    +
    @@ -55,7 +38,7 @@ @Html.SmartLabelFor(model => model.UniqueSeoCode) @@ -64,20 +47,15 @@ @Html.SmartLabelFor(model => model.FlagImageFileName) @@ -119,9 +97,40 @@ toggleStoreMapping(); - $("#pnlAllFlags a.flag").click(function () { - $("#FlagImageFileName").val($(this).data("filename")); + // init flags select + $('#LanguageEditTable').find('select[name=FlagImageFileName]').select2({ + allowClear: true, + minimumResultsForSearch: 16, + formatResult: flagSelectItemFormatting, + formatSelection: flagSelectItemFormatting }); + + function flagSelectItemFormatting(item) { + var flagUrl = '@Url.Content("~/Content/Images/flags/")'; + + try { + if (item.text.length > 0) { + var option = $(item.element), + html = '
    '; + + // item.element is undefined for selection formatting + if (option.length === 0) { + option = $('select[name="FlagImageFileName"]').find('option[value="' + item.id + '"]'); + } + + if (option.length !== 0) { + html += '
    '; + } + + html += '' + item.text + '
    '; + + return html; + } + } + catch (e) { } + + return item.text; + } }); @@ -133,8 +142,8 @@ $('#pnl-available-stores').hide(); } } - +
    @Html.SmartLabelFor(model => model.Name) @@ -44,9 +29,7 @@ @Html.SmartLabelFor(model => model.LanguageCulture) - @* codehint: sm-edit *@ - @Html.DropDownListFor(x => x.LanguageCulture, cultures.Select(x => - new SelectListItem { Value = x.IetfLanguageTag, Text = string.Format("{0} [{1}]", x.DisplayName, x.IetfLanguageTag) })) + @Html.DropDownListFor(x => x.LanguageCulture, Model.AvailableCultures) @Html.ValidationMessageFor(model => model.LanguageCulture)
    - @Html.EditorFor(model => model.UniqueSeoCode) + @Html.DropDownListFor(x => x.UniqueSeoCode, Model.AvailableTwoLetterLanguageCodes) @Html.ValidationMessageFor(model => model.UniqueSeoCode)
    - @Html.EditorFor(model => model.FlagImageFileName) + @Html.ValidationMessageFor(model => model.FlagImageFileName) -
    - @T("Admin.Common.Show") -
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml index 1bc2a901d6..aefa777216 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml @@ -1,6 +1,5 @@ @model NewsItemModel @{ - //page title ViewBag.Title = T("Admin.ContentManagement.News.NewsItems.EditNewsItemDetails").Text; } @using (Html.BeginForm()) @@ -10,9 +9,13 @@ @T("Admin.ContentManagement.News.NewsItems.EditNewsItemDetails") - @Model.Title @Html.ActionLink("(" + T("Admin.ContentManagement.News.NewsItems.BackToList") + ")", "List")
    + @Html.Widget("admin_button_toolbar_before") + + + @Html.Widget("admin_button_toolbar_after")
    @Html.Partial("_CreateOrUpdate", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml index d3c390356b..aa82033bf7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml @@ -4,40 +4,53 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - //page title ViewBag.Title = T("Admin.ContentManagement.News.NewsItems").Text; } +
    @T("Admin.ContentManagement.News.NewsItems")
    + @Html.Widget("admin_button_toolbar_before") + + +  @T("Admin.Common.AddNew") + + @Html.Widget("admin_button_toolbar_after")
    -
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml index ebb0992ed8..05d898f309 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml @@ -21,7 +21,8 @@ - + +
    @@ -101,6 +102,7 @@ }) .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("LogList", "Log")) + .PreserveGridState() .EnableCustomBinding(true)) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Create.cshtml index 245681f3cc..a87aef6072 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Create.cshtml @@ -7,7 +7,8 @@ {
    - @T("Admin.Catalog.Manufacturers.AddNew") @Html.ActionLink("(" + T("Admin.Catalog.Manufacturers.BackToList") + ")", "List") + @T("Admin.Catalog.Manufacturers.AddNew") + (@T("Admin.Catalog.Manufacturers.BackToList"))
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml index 2a127b102f..f0c58719b0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/Edit.cshtml @@ -1,15 +1,17 @@ @model ManufacturerModel @{ - //page title ViewBag.Title = T("Admin.Catalog.Manufacturers.EditManufacturerDetails").Text; } @using (Html.BeginForm(null, null, FormMethod.Post, new { id = "manufacturer-form" })) {
    - @T("Admin.Catalog.Manufacturers.EditManufacturerDetails") - @Model.Name @Html.ActionLink("(" + T("Admin.Catalog.Manufacturers.BackToList") + ")", "List") + @T("Admin.Catalog.Manufacturers.EditManufacturerDetails") - @Model.Name + (@T("Admin.Catalog.Manufacturers.BackToList"))
    + @Html.Widget("admin_button_toolbar_before") + + + @Html.Widget("admin_button_toolbar_after")
    @Html.Partial("_CreateOrUpdate", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml index af7b6e9ad7..eeb3816f1c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml @@ -1,9 +1,6 @@ @model ManufacturerListModel @using Telerik.Web.Mvc.UI -@{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title +@{ ViewBag.Title = T("Admin.Catalog.Manufacturers").Text; }
    @@ -12,95 +9,156 @@ @T("Admin.Catalog.Manufacturers")
    + @Html.Widget("admin_button_toolbar_before") + @T("Admin.Common.AddNew") - - - @T("Admin.Common.ExportToXml") - + + @Html.Widget("admin_button_toolbar_after")
    -
    @Html.SmartLabelFor(model => model.CreatedOnFrom) @@ -51,7 +52,7 @@ @Html.SmartLabelFor(model => model.LogLevelId) - @Html.DropDownList("LogLevelId", Model.AvailableLogLevels) + @Html.DropDownList("LogLevelId", Model.AvailableLogLevels, T("Admin.Common.All"))
    - - - - - + +
    - @Html.SmartLabelFor(model => model.SearchManufacturerName) - - @Html.EditorFor(model => Model.SearchManufacturerName) -
    + + + + + @if (Model.AvailableStores.Count > 1) + { + + + + + } + - - + +
    + @Html.SmartLabelFor(model => model.SearchManufacturerName) + + @Html.EditorFor(model => Model.SearchManufacturerName) +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores, T("Admin.Common.All")) +
      - -
    + +
    +

    +
    - @(Html.Telerik().Grid(Model.Manufacturers.Data) - .Name("manufacturers-grid") - .ClientEvents(events => events - .OnDataBinding("onDataBinding")) - .Columns(columns => - { - columns.Bound(x => x.Name).Width(300) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - .ClientTemplate("\"><#= Name #>"); - - // .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - //.ClientTemplate("'><#= Name #>"); - - columns.Bound(x => x.Published) - .Width(100) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(100) - .Centered(); - 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.Manufacturers.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Manufacturer")) - .EnableCustomBinding(true)) + @(Html.Telerik().Grid() + .Name("manufacturers-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.Name) + .ClientTemplate("\"><#= Name #>"); + columns.Bound(x => x.Published) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(160) + .Centered(); + columns.Bound(x => x.LimitedToStores) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden(Model.AvailableStores.Count <= 1) + .Centered(); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Manufacturer")) + .PreserveGridState() + .EnableCustomBinding(true))
    + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/ProductAddPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/ProductAddPopup.cshtml deleted file mode 100644 index 15185b0812..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/ProductAddPopup.cshtml +++ /dev/null @@ -1,136 +0,0 @@ -@{ - Layout = "~/Administration/Views/Shared/_AdminPopupLayout.cshtml"; -} -@model ManufacturerModel.AddManufacturerProductModel -@{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title - ViewBag.Title = T("Admin.Catalog.Manufacturers.Products.AddNew").Text; -} -@using Telerik.Web.Mvc.UI; -@using (Html.BeginForm()) -{ -
    -
    - @T("Admin.Catalog.Manufacturers.Products.AddNew") -
    -
    - - if (ViewBag.RefreshPage == true) - { - - } - - - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchProductName) - - @Html.EditorFor(model => Model.SearchProductName) -
    - @Html.SmartLabelFor(model => model.SearchCategoryId) - - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchManufacturerId) - - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchProductTypeId) - - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) -
    -   - - -
    -

    -

    - - - - - - - -
    - @(Html.Telerik().Grid(Model.Products.Data) - .Name("products-grid") - .Columns(columns => - { - columns.Bound(x => x.Id) - .Template(x => string.Format("", x.Id)) - .ClientTemplate("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Name) - .Template(x => @Html.LabeledProductName(0, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName(null, "Name")); - columns.Bound(x => x.Sku); - columns.Bound(x => x.Published) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered() - .Width(100); - }) - .Pageable(settings => settings.Total(Model.Products.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("ProductAddPopupList", "Manufacturer")) - .ClientEvents(events => events.OnDataBinding("onDataBinding")) - .EnableCustomBinding(true)) -
    -

    - -
    - -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/_CreateOrUpdate.cshtml index c91bf46e8d..41f3f1b8c8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/_CreateOrUpdate.cshtml @@ -15,7 +15,24 @@ EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "manufacturer-edit", this.Html, this.Model)); }) @helper TabInfo() - { +{ + if (Model.Id != 0) + { + + + + + +
    + @Html.SmartLabelFor(model => model.Id) + + @Html.DisplayFor(model => model.Id) + @Html.ValidationMessageFor(model => model.Id) +
    + +

    + } + @(Html.LocalizedEditor("manufacturer-info-localized", @ @@ -63,7 +80,8 @@
    - )) +)) + @@ -215,6 +233,10 @@ @@ -250,82 +272,63 @@
    @@ -79,7 +97,7 @@ @Html.SmartLabelFor(model => model.PictureId) - @Html.EditorFor(model => model.PictureId) + @Html.EditorFor(model => model.PictureId, new { transientUpload = true }) @Html.ValidationMessageFor(model => model.PictureId)
    @Html.TextAreaFor(x => x.SeName) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "Manufacturer", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
    } @helper TabProducts() - { +{ if (Model.Id > 0) { - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - - - - - - -
    - @(Html.Telerik().Grid() - .Name("products-grid") - .DataKeys(keys => - { - keys.Add(x => x.Id); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("ProductList", "Manufacturer", new { manufacturerId = Model.Id }) - .Update("ProductUpdate", "Manufacturer") - .Delete("ProductDelete", "Manufacturer"); - }) - .Columns(columns => - { - columns.Bound(x => x.ProductName) - .ReadOnly() - .Template(x => @Html.LabeledProductName(x.ProductId, x.ProductName, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); - columns.Bound(x => x.Sku) - .ReadOnly(); - columns.Bound(x => x.Published) - .ReadOnly() - .Width(100) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.IsFeaturedProduct) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsFeaturedProduct)) - .ClientTemplate(@Html.SymbolForBool("IsFeaturedProduct")) - .Centered(); - columns.Bound(x => x.DisplayOrder1) - .Width(100) - .Centered(); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }) - .Width(180); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .EnableCustomBinding(true)) -
    - - - -
    + + + + + + + +
    + @(Html.Telerik().Grid() + .Name("products-grid") + .DataKeys(keys => + { + keys.Add(x => x.Id); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("ProductList", "Manufacturer", new { manufacturerId = Model.Id }) + .Update("ProductUpdate", "Manufacturer") + .Delete("ProductDelete", "Manufacturer"); + }) + .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.Published) + .ReadOnly() + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.IsFeaturedProduct) + .ClientTemplate(@Html.SymbolForBool("IsFeaturedProduct")) + .Centered(); + columns.Bound(x => x.DisplayOrder1) + .Centered(); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }) + .Width(220); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .PreserveGridState() + .EnableCustomBinding(true)) +
    + +
    } else { @@ -339,7 +342,6 @@ $(document).ready(function () { $("#@Html.FieldIdFor(model => model.LimitedToStores)").click(toggleStoreMapping); - toggleStoreMapping(); }); @@ -352,8 +354,8 @@ $('#pnl-available-stores').hide(); } } - + + + + +
    @@ -388,9 +390,36 @@ } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/_CreateOrUpdate.cshtml index 3800e46a27..f65fccf73a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/_CreateOrUpdate.cshtml @@ -13,7 +13,7 @@ x.Add().Text(T("Admin.Common.Info").Text).Content(TabInfo()).Selected(true); x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); - //generate an event + // generate an event EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "template-edit", this.Html, this.Model)); }) @@ -87,6 +87,15 @@ @Html.ValidationMessageFor(model => model.IsActive)
    + @Html.SmartLabelFor(model => model.SendManually) + + @Html.EditorFor(model => model.SendManually) + @Html.ValidationMessageFor(model => model.SendManually) +
    @(Html.LocalizedEditor("messagetemplate-localized", @ @@ -117,6 +126,33 @@ @Html.ValidationMessageFor(model => model.Locales[item].Subject) + + + + + + + + + + + + + + + + + + + + + + + + + } +
    + @Html.SmartLabelFor(model => model.Locales[item].Attachment1FileId) + + @Html.EditorFor(model => Model.Locales[item].Attachment1FileId, "Download", new { minimalMode = true }) + @Html.ValidationMessageFor(model => model.Locales[item].Attachment1FileId) +
    + @Html.SmartLabelFor(model => model.Locales[item].Attachment2FileId) + + @Html.EditorFor(model => Model.Locales[item].Attachment2FileId, "Download", new { minimalMode = true }) + @Html.ValidationMessageFor(model => model.Locales[item].Attachment2FileId) +
    + @Html.SmartLabelFor(model => model.Locales[item].Attachment3FileId) + + @Html.EditorFor(model => Model.Locales[item].Attachment3FileId, "Download", new { minimalMode = true }) + @Html.ValidationMessageFor(model => model.Locales[item].Attachment3FileId) +
    @Html.SmartLabelFor(model => model.Locales[item].Body) @@ -161,6 +197,33 @@ @Html.ValidationMessageFor(model => model.Subject)
    + @Html.SmartLabelFor(model => model.Attachment1FileId) + + @Html.EditorFor(model => Model.Attachment1FileId, "Download", new { minimalMode = true }) + @Html.ValidationMessageFor(model => model.Attachment1FileId) +
    + @Html.SmartLabelFor(model => model.Attachment2FileId) + + @Html.EditorFor(model => Model.Attachment2FileId, "Download", new { minimalMode = true }) + @Html.ValidationMessageFor(model => model.Attachment2FileId) +
    + @Html.SmartLabelFor(model => model.Attachment3FileId) + + @Html.EditorFor(model => Model.Attachment3FileId, "Download", new { minimalMode = true }) + @Html.ValidationMessageFor(model => model.Attachment3FileId) +
    @Html.SmartLabelFor(model => model.Body) @@ -237,7 +300,8 @@ else {
    @T("Admin.Configuration.Stores.NoStoresDefined")
    - }
    } \ 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 ef5efcd34e..79347bed2a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/Comments.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/Comments.cshtml @@ -64,6 +64,7 @@ settingBuilder = settingBuilder.Delete("CommentDelete", "News"); } }) + .PreserveGridState() .EnableCustomBinding(true))
    - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    -   - - -
    -

    -

    + +@if (Model.AvailableStores.Count > 1) +{ + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    +   + + +
    +} + +

    +
    @@ -45,10 +58,19 @@ .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( @@ -59,28 +81,24 @@ .ClientTemplate(""); columns.Bound(x => x.StartDate); columns.Bound(x => x.EndDate); - columns.Bound(x => x.Published) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .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.Id) - .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id })) - .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") - .Width(50) - .Centered() - .HeaderTemplate(T("Admin.Common.Edit").Text) - .Filterable(false); }) .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "News")) - .ClientEvents(events => events.OnDataBinding("onDataBinding")) + .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/News/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/_CreateOrUpdate.cshtml index 91b863fc59..84562a94b1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/_CreateOrUpdate.cshtml @@ -9,6 +9,7 @@ @Html.SmartStore().TabStrip().Name("news-edit").Style(TabsStyle.Tabs).Position(TabsPosition.Top).Items(x => { x.Add().Text(T("Admin.Common.Info").Text).Content(TabInfo()).Selected(true); + x.Add().Text(T("Admin.Common.SEO").Text).Content(TabSeo()); x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); //generate an event @@ -88,44 +89,55 @@ @Html.ValidationMessageFor(model => model.Published)
    - @Html.SmartLabelFor(model => model.MetaKeywords) - - @Html.EditorFor(x => x.MetaKeywords) - @Html.ValidationMessageFor(model => model.MetaKeywords) -
    - @Html.SmartLabelFor(model => model.MetaDescription) - - @Html.EditorFor(x => x.MetaDescription) - @Html.ValidationMessageFor(model => model.MetaDescription) -
    - @Html.SmartLabelFor(model => model.MetaTitle) - - @Html.EditorFor(x => x.MetaTitle) - @Html.ValidationMessageFor(model => model.MetaTitle) -
    +} + +@helper TabSeo() +{ + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.MetaKeywords) + + @Html.EditorFor(x => x.MetaKeywords) + @Html.ValidationMessageFor(model => model.MetaKeywords) +
    + @Html.SmartLabelFor(model => model.MetaDescription) + + @Html.EditorFor(x => x.MetaDescription) + @Html.ValidationMessageFor(model => model.MetaDescription) +
    + @Html.SmartLabelFor(model => model.MetaTitle) + + @Html.EditorFor(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 = "NewsItem", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
    } + @helper TabStores() { - -@*import emails form*@ -@{Html.SmartStore().Window() - .Name("importcsv-window") - .Title(T("Admin.Common.ImportFromCsv").Text) - .Content(@ - @using (Html.BeginForm("ImportCsv", "NewsLetterSubscription", FormMethod.Post, new { enctype = "multipart/form-data" })) - { -
    - @T("Admin.Common.CsvFile"): -
    - } -
    ) - .FooterContent(@ - - ) - .Width(400) - .Modal(true) - .Visible(false) - .Render(); -} - - + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/OnlineCustomer/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/OnlineCustomer/List.cshtml index eb84641c46..a14713a9d5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/OnlineCustomer/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/OnlineCustomer/List.cshtml @@ -34,6 +34,7 @@ }) .Pageable(settings => settings.Total(Model.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "OnlineCustomer")) + .PreserveGridState() .EnableCustomBinding(true))
    +
    @@ -41,7 +40,7 @@ @Html.SmartLabelFor(model => model.SearchManufacturerId) @@ -49,7 +48,7 @@ @Html.SmartLabelFor(model => model.SearchProductTypeId) @@ -63,7 +62,9 @@
    @Html.SmartLabelFor(model => model.SearchProductName) @@ -33,7 +32,7 @@ @Html.SmartLabelFor(model => model.SearchCategoryId) - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) + @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All"))
    - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) + @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All"))
    - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) + @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, T("Admin.Common.All"))
    +

    + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml index 9003cade34..0deedb6ebd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml @@ -2,7 +2,6 @@ @using SmartStore.Core.Domain.Tax; @using SmartStore.Core.Domain.Catalog; @{ - //page title ViewBag.Title = string.Format(T("Admin.Orders.Shipments.AddNew.Title").Text, Model.OrderId); } @using (Html.BeginForm()) @@ -15,8 +14,9 @@ - + @Html.ValidationSummary(false) +
    @@ -82,6 +83,7 @@ .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("AddProductToOrder", "Order")) .ClientEvents(events => events.OnDataBinding("onDataBinding").OnRowSelect("onRowSelected")) + .PreserveGridState() .EnableCustomBinding(true))
    @@ -27,78 +27,70 @@
    + - +
    - @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.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 + + +
    - - - - - - - - - - - - - - - - - - @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/BestsellersReport.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersReport.cshtml index c6fb84b696..e035acfa1e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersReport.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersReport.cshtml @@ -1,7 +1,6 @@ @model BestsellersReportModel @using Telerik.Web.Mvc.UI @{ - //page title ViewBag.Title = T("Admin.SalesReport.Bestsellers").Text; } @using (Html.BeginForm()) @@ -14,7 +13,8 @@
    - + +
    @@ -44,7 +44,7 @@ @Html.SmartLabelFor(model => model.PaymentStatusId) @@ -56,8 +56,9 @@
    @Html.SmartLabelFor(model => model.StartDate) @@ -36,7 +36,7 @@ @Html.SmartLabelFor(model => model.OrderStatusId) - @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses) + @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses, T("Admin.Common.All"))
    - @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses) + @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses, T("Admin.Common.All"))
    -

    -

    + +

    +
    @@ -106,5 +107,4 @@ } - } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml index 3b1641e49c..75fefead92 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml @@ -9,13 +9,21 @@ {
    - @T("Admin.Orders.EditOrderDetails") - @Model.OrderNumber @Html.ActionLink("(" + T("Admin.Orders.BackToList") + ")", "List") + @T("Admin.Orders.EditOrderDetails") - @Model.OrderNumber + (@T("Admin.Orders.BackToList"))
    + @Html.Widget("admin_button_toolbar_before") + + + + @T("Order.Print") + @if (Model.DisplayPdfInvoice) { - -  @T("Admin.Orders.PdfInvoice") + + + @T("Admin.Orders.PdfInvoice") } + + @Html.Widget("admin_button_toolbar_after")
    @@ -149,6 +159,9 @@ } + + @Html.Widget("order_edit_top") + + + + + @@ -1216,17 +1236,13 @@ }) .Columns(columns => { - columns.Bound(x => x.Id).Width(50); + 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); - 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(T("Admin.Common.View").Text); }) .EnableCustomBinding(true)) @@ -1370,47 +1386,42 @@
    @@ -814,25 +827,25 @@ } @if (Model.CanCapture) { - } @if (Model.CanRefund) - { - - } + } @if (Model.CanRefundOffline) { + onclick="return confirm('@T("Admin.Common.AreYouSure")');" title="@T("Admin.Orders.Fields.RefundOffline.Hint")" /> } @if (Model.CanPartiallyRefund) - { - - } + } @if (Model.CanPartiallyRefundOffline) { } @if (Model.CanVoidOffline) @@ -879,14 +892,21 @@
    + @Html.SmartLabelFor(model => model.AcceptThirdPartyEmailHandOver) + + @(Model.AcceptThirdPartyEmailHandOver ? T("Common.Yes") : T("Common.No")) +
    @if (Model.CustomerComment.HasValue()) {

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

    - - @Html.Raw(Model.CustomerComment) + @Model.CustomerComment
    }
    - + + + + + +
    + - - +
    + - @if (Model.HasDownloadableProducts) { - } - - - - - - @{ - var j = 0; - } @foreach (var item in Model.Items) { - j++; - - - +
    + @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) @@ -1750,16 +1761,21 @@
    -
    -

    @T("Admin.Orders.OrderNotes.AddTitle")

    -
    + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml index 84a3b78f07..8cc2132d70 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml @@ -1,12 +1,10 @@ @model OrderListModel - @using Telerik.Web.Mvc.UI - @{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - var hideProfitReport = false; - - ViewBag.Title = T("Admin.Orders").Text; + var hideProfitReport = false; + var allString = T("Admin.Common.All").Text; + + ViewBag.Title = T("Admin.Orders").Text; } @using (Html.BeginForm()) @@ -17,86 +15,64 @@ @T("Admin.Orders") -
    +
    +
    @T("Admin.Orders.OrderNotes.AddTitle")
    +
    +
    @Html.SmartLabelFor(model => model.AddOrderNoteMessage) - @Html.TextAreaFor(model => model.AddOrderNoteMessage, new { style = "Width: 350px; Height: 150px;" }) + @Html.TextAreaFor(model => model.AddOrderNoteMessage, new { @class = "control-xlarge", style = "height: 120px;" }) @Html.ValidationMessageFor(model => model.AddOrderNoteMessage)
    - - - - - - - - - - - - - - - - + +
    - @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) -
    + + + + + + + + + + + + + + + + - - - + + - - - - - - - - - + + - - - + + + + @if (Model.AvailableStores.Count > 1) + { + + + + + } - - - - - - + + + + + + + + + - - - - - -
    + @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) @@ -105,69 +81,72 @@ @Html.DropDownList("OrderStatusIds", Model.AvailableOrderStatuses, null, new { multiple = "multiple" })
    - @Html.SmartLabelFor(model => model.PaymentStatusIds) - +
    + @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.DropDownList("StoreId", Model.AvailableStores) -
    - @Html.SmartLabelFor(model => model.OrderGuid) - - @Html.EditorFor(model => Model.OrderGuid) -
    + @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.OrderNumber) - - @Html.EditorFor(model => Model.OrderNumber) -
    - @Html.SmartLabelFor(model => model.GoDirectlyToNumber) - - @Html.EditorFor(model => Model.GoDirectlyToNumber) - + @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) + -
    -   - - -
    +
    +   + + +

    @@ -175,30 +154,31 @@
    @(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" }); + .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" }); - columns.Bound(x => x.OrderNumber) - .Template(x => Html.ActionLink(x.OrderNumber, "Edit", new { id = x.Id })) - .ClientTemplate("\"><#= OrderNumber #>"); - columns.Bound(x => x.OrderStatus); - columns.Bound(x => x.PaymentStatus); - columns.Bound(x => x.ShippingStatus); - columns.Bound(x => x.CustomerEmail); - columns.Bound(x => x.StoreName); - columns.Bound(x => x.CreatedOn); - columns.Bound(x => x.OrderTotal) + 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( @@ -210,138 +190,135 @@ (hideProfitReport ? "" : @T("Admin.Orders.Report.Profit").Text), @T("Admin.Orders.Report.Tax").Text, @T("Admin.Orders.Report.Total").Text)); - columns.Bound(x => x.Id) - .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", new { id = x.Id })) - .ClientTemplate("\">" + T("Admin.Common.View").Text + "") - .Width(50) - .Centered() - .Title(T("Admin.Common.View").Text); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("OrderList", "Order")) - .EnableCustomBinding(true)) + }) + .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/NeverSoldReport.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/NeverSoldReport.cshtml index cfb18a24f0..ce82429872 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/NeverSoldReport.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/NeverSoldReport.cshtml @@ -60,6 +60,7 @@ }) .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("NeverSoldReportList", "Order")) + .PreserveGridState() .EnableCustomBinding(true)) 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 6138daf35e..bdc45e4ea9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.Print.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.Print.cshtml @@ -50,7 +50,7 @@
    @if (address.Company.HasValue()) -{ + {
    @address.Company
    }
    @JoinValues(new string[] { address.FirstName, address.LastName })
    @@ -66,10 +66,17 @@ {
    @JoinValues(new string[] { address.ZipPostalCode, address.City })
    } - @if (address.StateProvince != null || address.Country != null) - { -
    @JoinValues(new string[] { address.Country.Name, address.StateProvince.Name }, ", ")
    - } + @if (address.Country != null) + { + if (address.StateProvince != null) + { +
    @JoinValues(new string[] { address.Country.Name, address.StateProvince.Name }, ", ")
    + } + else + { +
    @JoinValues(new string[] { address.Country.Name }, ", ")
    + } + }
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml index e8a3fea84a..800e47eee5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml @@ -3,12 +3,10 @@ @using SmartStore.Core.Domain.Tax; @using SmartStore.Core.Domain.Catalog; @{ - //page title ViewBag.Title = T("Admin.Orders.Shipments.ViewDetails").Text; } @using (Html.BeginForm()) { -
    @T("Admin.Orders.Shipments.ViewDetails") - @Model.Id @Html.ActionLink("(" + T("Admin.Orders.Shipments.BackToOrder") + ")", "Edit", new { id = Model.OrderId }) @@ -23,8 +21,10 @@ }
    -
    + + @Html.ValidationSummary(false) + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Payment/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Payment/Edit.cshtml new file mode 100644 index 0000000000..5dd49c46d2 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Payment/Edit.cshtml @@ -0,0 +1,144 @@ +@model PaymentMethodEditModel +@{ + ViewBag.Title = T("Common.Edit").Text + " - " + Model.FriendlyName; + + Html.AddScriptParts(true, "~/Scripts/2012.2.607/telerik.common.min.js"); +} + +@using (Html.BeginForm(null, null, FormMethod.Post, new { id = "payment-method-form" })) +{ +
    +
    + @Model.FriendlyName + @ViewBag.Title (@(Model.SystemName)) + (@T("Admin.Common.BackToList")) +
    +
    + @Html.Widget("admin_button_toolbar_before") + + + + + @Html.Widget("admin_button_toolbar_after") +
    +
    + + @Html.ValidationSummary(false) + @Html.HiddenFor(model => model.SystemName) + + @Html.SmartStore().TabStrip().Name("payment-method-edit").Items(x => + { + x.Add().Text(T("Admin.Common.General").Text).Content(TabGeneral()).Selected(true); + x.Add().Text(T("Admin.Common.Restrictions").Text).Content(TabRestrictions()); + + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "payment-method-edit", this.Html, this.Model)); + }) +} + +@helper TabGeneral() +{ + @(Html.LocalizedEditor("paymentmethod-general-localized", + @
    @@ -82,54 +82,51 @@ -
    + -
    - - - - - - - - - - - @foreach (var item in Model.Items) - { - - + + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { +
    + @Html.Raw(item.AttributeInfo) + } + + + + + + } + +
    - @T("Admin.Orders.Shipments.Products.ProductName") - - @T("Admin.Orders.Shipments.Products.SKU") - - @T("Admin.Orders.Shipments.Products.QtyShipped") -
    -
    + + + + + + + + + + @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 -
    - -
    -
    +
    + @item.Sku +
    +
    + @item.QuantityInThisShipment +
    + +
    } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml index f2fd761f93..967a96ac86 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml @@ -96,10 +96,11 @@ .ClientTemplate("\">" + T("Admin.Common.View").Text + "") .Width(50) .Centered() - .HeaderTemplate(T("Admin.Common.View").Text); + .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.Locales[item].FriendlyName) + + @Html.EditorFor(model => Model.Locales[item].FriendlyName) + @Html.ValidationMessageFor(model => model.Locales[item].FriendlyName) +
    + @Html.SmartLabelFor(model => model.Locales[item].Description) + + @Html.TextAreaFor(model => Model.Locales[item].Description, new { @class = "input-large" }) + @Html.ValidationMessageFor(model => model.Locales[item].Description) +
    + @Html.SmartLabelFor(model => model.Locales[item].FullDescription) + + @Html.EditorFor(model => model.Locales[item].FullDescription, Html.RichEditorFlavor()) + @Html.ValidationMessageFor(model => model.Locales[item].FullDescription) +
    + @Html.HiddenFor(model => model.Locales[item].LanguageId) +
    + , + @ + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.FriendlyName) + + @Html.EditorFor(model => model.FriendlyName) + @Html.ValidationMessageFor(model => model.FriendlyName) +
    + @Html.SmartLabelFor(model => model.Description) + + @Html.TextAreaFor(model => model.Description, new { @class = "input-large" }) + @Html.ValidationMessageFor(model => model.Description) +
    + @Html.SmartLabelFor(model => model.FullDescription) + + @Html.EditorFor(x => x.FullDescription, Html.RichEditorFlavor()) + @Html.ValidationMessageFor(model => model.FullDescription) +
    + )) +} + +@helper TabRestrictions() +{ + if (Model.FilterConfigurationUrls.Count == 0) + { +
    + @T("Admin.Configuration.Payment.Methods.RestrictionNote") +
    + } + +
    +
    +
    +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Payment/Providers.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Payment/Providers.cshtml index 18cf6fe4d4..5f854c0015 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Payment/Providers.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Payment/Providers.cshtml @@ -9,6 +9,11 @@ @T("Admin.Configuration.Payment.Methods") +
    + @Html.Widget("admin_button_toolbar_before") + + @Html.Widget("admin_button_toolbar_after") +
    @@ -45,61 +50,3 @@ } } - -@*
    - - - -
    - @(Html.Telerik().Grid() - .Name("paymentmethods-grid") - .DataKeys(x => - { - x.Add(y => y.SystemName).RouteKey("systemName"); - }) - .BindTo(Model.Data) - .Columns(columns => - { - columns.Bound(x => x.FriendlyName) - .Width(250); - columns.Bound(x => x.SystemName).Width(250).ReadOnly(); - columns.Template( - @ -
    @Html.SymbolForBool(item.SupportCapture) @Html.LabelFor(model => item.SupportCapture, new { style = "display:inline" })
    -
    @Html.SymbolForBool(item.SupportRefund) @Html.LabelFor(model => item.SupportRefund, new { style = "display:inline" })
    -
    @Html.SymbolForBool(item.SupportPartiallyRefund) @Html.LabelFor(model => item.SupportPartiallyRefund, new { style = "display:inline" })
    -
    @Html.SymbolForBool(item.SupportVoid) @Html.LabelFor(model => item.SupportVoid, new { style = "display:inline" })
    -
    ).Title("Features"); - - columns.Bound(x => x.RecurringPaymentType) - .Width(100) - .ReadOnly(); - columns.Bound(x => x.DisplayOrder) - .Centered() - .Width(100); - columns.Bound(x => x.IsActive) - .Template(item => @Html.SymbolForBool(item.IsActive)) - .ClientTemplate(@Html.SymbolForBool("IsActive")) - .Centered() - .Width(100); - columns.Template( - @
    - @Html.ActionLink(T("Admin.Configuration.Payment.Methods.Configure").Text, "ConfigureMethod", new { systemName = item.SystemName }) -
    - ) - .ClientTemplate("\">" + T("Admin.Configuration.Payment.Methods.Configure").Text + "") - .Width(100) - .Centered() - .Title(T("Admin.Configuration.Payment.Methods.Configure").Text); - columns.Command(commands => - { - commands.Edit().Localize(T); - }).Title(T("Admin.Common.Edit").Text).Width(50); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("Methods", "Payment") - .Update("MethodUpdate", "Payment"); - }) - .EnableCustomBinding(true)) -
    *@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigurePlugin.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigurePlugin.cshtml index 2122382080..74f552e016 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigurePlugin.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigurePlugin.cshtml @@ -20,7 +20,18 @@ @if (Model.ConfigurationRoute != null && Model.ConfigurationRoute.Action.HasValue()) { - @Html.Action(Model.ConfigurationRoute.Action, Model.ConfigurationRoute.Controller, Model.ConfigurationRoute.RouteValues); + try + { + @Html.Action(Model.ConfigurationRoute.Action, Model.ConfigurationRoute.Controller, Model.ConfigurationRoute.RouteValues); + } + catch (Exception exception) + { +
    + @T("Admin.Configuration.Plugins.UnknownError") +
    + +
    @exception.ToString()
    + } } else { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigureProvider.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigureProvider.cshtml index ef61c34374..74edd98572 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigureProvider.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigureProvider.cshtml @@ -1,4 +1,4 @@ -@model ProviderModel +@model SmartStore.Web.Framework.Plugins.ProviderModel @{ ViewBag.Title = T("Admin.Common.Configure").Text + " - " + Model.FriendlyName; @@ -23,7 +23,18 @@ @if (Model.ConfigurationRoute != null && Model.ConfigurationRoute.Action.HasValue()) { - @Html.Action(Model.ConfigurationRoute.Action, Model.ConfigurationRoute.Controller, Model.ConfigurationRoute.RouteValues); + try + { + @Html.Action(Model.ConfigurationRoute.Action, Model.ConfigurationRoute.Controller, Model.ConfigurationRoute.RouteValues); + } + catch (Exception exception) + { +
    + @T("Admin.Configuration.Plugins.UnknownError") +
    + +
    @exception.ToString()
    + } } else { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/EditProviderPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/EditProviderPopup.cshtml index 407b4410ca..c6aae8a842 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/EditProviderPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/EditProviderPopup.cshtml @@ -1,4 +1,5 @@ -@model ProviderModel +@using SmartStore.Web.Framework.Plugins +@model SmartStore.Web.Framework.Plugins.ProviderModel @{ Layout = "~/Administration/Views/Shared/_AdminPopupLayout.cshtml"; diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/LicensePlugin.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/LicensePlugin.cshtml new file mode 100644 index 0000000000..5897e38319 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/LicensePlugin.cshtml @@ -0,0 +1,52 @@ +@model LicensePluginModel + +@{ + Layout = null; +} + +
    + + @if (Model.InvalidDataStoreId == 0) + { + + @for (int i = 0; i < Model.Licenses.Count; ++i) + { + + + + + + + + } +
    +
    +
    + @Model.Licenses[i].StoreUrl (@Model.Licenses[i].StoreName) + - + @T("Admin.Common.Change") + +
    +
    +
    + @Html.SmartLabelFor(model => model.Licenses[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" }) +
    + } + else + { +
    +

    @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 9f32ddd59b..e8c60696f4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml @@ -1,6 +1,7 @@ @model LocalPluginsModel @using SmartStore.Web.Framework.UI; @using Newtonsoft.Json; +@using SmartStore.Licensing; @{ //page title @@ -32,17 +33,58 @@ { @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) + { + @T("Admin.Plugins.LicensingDemoNotStarted") + } + else if (plugin.RemainingDemoUsageDays <= 0) + { + @T("Admin.Plugins.LicensingDemoExpired") + } + else + { + @T("Admin.Plugins.LicensingDemoRemainingDays", plugin.RemainingDemoUsageDays) + } + + } + }
    + @if (plugin.Url.HasValue()) + { + + + @T("Common.Website") + + } @if (plugin.Author.HasValue()) { - @T("Admin.Configuration.Plugins.Fields.Author"): - @plugin.Author + + @T("Admin.Configuration.Plugins.Fields.Author"): + @plugin.Author + } - @T("Admin.Configuration.Plugins.Fields.Version"): - @plugin.Version - @T("Admin.Configuration.Plugins.Fields.SystemName"): - @plugin.SystemName + + @T("Admin.Configuration.Plugins.Fields.Version"): + @plugin.Version + + + @T("Admin.Configuration.Plugins.Fields.SystemName"): + @plugin.SystemName +
    @if (plugin.Description.HasValue()) { @@ -74,6 +116,13 @@ @T("Admin.Configuration.Plugins.Fields.Configure") } + @if (plugin.LicenseUrl.HasValue()) + { + + } } @@ -176,196 +225,227 @@ } +
    + +
    + + - } - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchProductName) - - @Html.EditorFor(model => Model.SearchProductName) -
    - @Html.SmartLabelFor(model => model.SearchCategoryId) - - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchManufacturerId) - - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    - @Html.SmartLabelFor(model => model.SearchProductTypeId) - - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) -
    -   - - -
    - -

    - - - - - - - - -
    - @(Html.Telerik().Grid() - .Name("products-grid") - .Columns(columns => - { - columns.Bound(x => x.Id) - .Template(x => string.Format("", x.Id)) - .ClientTemplate("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Name) - .Template(x => @Html.LabeledProductName(0, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName(null, "Name")); - columns.Bound(x => x.Sku); - columns.Bound(x => x.Published) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered() - .Width(100); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("AssociatedProductAddPopupList", "Product")) - .ClientEvents(events => events.OnDataBinding("associatedProducts_onDataBinding")) - .EnableCustomBinding(true)) -
    -

    - -
    - - -} \ 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 868d78e99f..ebdea889fa 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml @@ -1,9 +1,8 @@ @model BulkEditListModel @using Telerik.Web.Mvc.UI @{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; + var allString = T("Admin.Common.All").Text; - //page title ViewBag.Title = T("Admin.Catalog.BulkEdit").Text; } @using (Html.BeginForm()) @@ -20,51 +19,63 @@
    @T("Admin.Catalog.BulkEdit.Info")
    - - - - - - - - - - - - - - - - - - - + +
    - @Html.SmartLabelFor(model => model.SearchProductName) - - @Html.EditorFor(model => Model.SearchProductName) -
    - @Html.SmartLabelFor(model => model.SearchCategoryId) - - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchManufacturerId) - - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchProductTypeId) - - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) -
    + + + + + + + + + @if (Model.AvailableStores.Count > 1) + { + + + + + } + + + + + + + + + - - -
    + @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) +
      - -
    -

    -

    + + + + + + +

    + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/PdfCatalog.Print.Cover.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/PdfCatalog.Print.Cover.cshtml deleted file mode 100644 index 4cfae9aa79..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/PdfCatalog.Print.Cover.cshtml +++ /dev/null @@ -1,35 +0,0 @@ -@model PrintableProductsModel -@using SmartStore.Core.Html; -@{ - Layout = "~/Views/Shared/_Print.cshtml"; -} - - - -
    - - @Html.Widget("pdfcatalog_cover_top") - - @if (Model.PrintLogoUrl.HasValue()) - { -
    - -
    -
    - } - - @Html.Widget("pdfcatalog_cover_logo_after") - -

    @Model.StoreName

    - - @Html.Widget("pdfcatalog_cover_storename_after") - -
    - @Html.Action("PdfReceiptFooter", "Common", new { area = "", isPartial = true }) -
    - -
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/PdfCatalog.Print.Footer.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/PdfCatalog.Print.Footer.cshtml deleted file mode 100644 index 69d906f865..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/PdfCatalog.Print.Footer.cshtml +++ /dev/null @@ -1,19 +0,0 @@ -@model PrintableProductsModel -@using SmartStore.Web.Framework.Pdf; -@{ - Layout = null; -} - - - -
    - -
    -
    - von -
    -
    - @Model.StoreName -
    -
    - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/PdfCatalog.Print.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/PdfCatalog.Print.cshtml deleted file mode 100644 index cd81040195..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/PdfCatalog.Print.cshtml +++ /dev/null @@ -1,187 +0,0 @@ -@model PrintableProductsModel -@using SmartStore.Core.Domain.Orders; -@using SmartStore.Core.Domain.Catalog; -@using SmartStore.Admin.Models.Shipping; -@using SmartStore.Services.Localization; -@using SmartStore.Core.Html; -@{ - - Layout = "~/Views/Shared/_Print.cshtml"; - - //title - Html.AddTitleParts(Model.StoreName); -} - -
    - @foreach (var product in Model.Products) - { -
    - @Html.Widget("pdfcatalog_productdetail_top") -

    @product.Name

    -

    - @product.ShortDescription -

    - -
    -
    @@ -121,8 +132,9 @@ }) .ClientEvents(events => events.OnDataBinding("Grid_onDataBinding").OnError("Grid_onError").OnSubmitChanges("Grid_onSubmitChanges")) .Editable(editing => editing.Mode(GridEditMode.InCell)) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) ) + - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchProductName) - - @Html.EditorFor(model => Model.SearchProductName) -
    - @Html.SmartLabelFor(model => model.SearchCategoryId) - - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchManufacturerId) - - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    - @Html.SmartLabelFor(model => model.SearchProductTypeId) - - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) -
    -   - - -
    - -

    - - - - - - - - -
    - @(Html.Telerik().Grid() - .Name("products-grid") - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center;" }); - columns.Bound(x => x.Name) - .ClientTemplate(@Html.LabeledProductName(null, "Name")); - columns.Bound(x => x.Sku); - columns.Bound(x => x.Published) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered() - .Width(100); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("BundleItemAddPopupList", "Product")) - .ClientEvents(events => events.OnDataBinding("bundleItem_onDataBinding")) - .EnableCustomBinding(true)) -
    - -
    - - -} \ 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 15d443fb2d..be97e1f213 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/Create.cshtml @@ -7,7 +7,8 @@ {
    - @T("Admin.Catalog.Products.AddNew") @Html.ActionLink("(" + T("Admin.Catalog.Products.BackToList") + ")", "List") + @T("Admin.Catalog.Products.AddNew") + (@T("Admin.Catalog.Products.BackToList"))
    - @@ -31,6 +33,8 @@ @T("Admin.Common.Delete") + + @Html.Widget("admin_button_toolbar_after")
    @Html.Partial("_CreateOrUpdate", Model) 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 a7703a72b3..4b6c4391f6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceCustomer.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceCustomer.cshtml @@ -2,7 +2,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 79d6b09ee1..28cf2f3f95 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceStore.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceStore.cshtml @@ -1,10 +1,9 @@  - -} -@*import products form*@ -@{Html.SmartStore().Window() - .Name("importexcel-window") - .Title(T("Admin.Common.ImportFromExcel").Text) - .Content(@ - @using (Html.BeginForm("ImportExcel", "Product", FormMethod.Post, new { enctype = "multipart/form-data", @class = "form-horizontal" })) - { -
    - -
    - -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    - @Html.Raw(T("Admin.Common.ImportFromExcel.ProcessedCount").ToString().FormatInvariant("0", "0")) - @Html.Raw(T("Admin.Common.ImportFromExcel.ActiveSince").ToString().FormatCurrent("")) -
    -
    - @(Html.Raw(T("Admin.Common.ImportFromExcel.QuickStats").ToString().FormatInvariant( - "0", - "0", - "0", - "0"))) -
    -
    - } - -
    ) - .FooterContent(@ - - @T("Common.Close") - ) - .Modal(true) - .Visible(false) - .Render(); -} - -
    - -
    - - +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/LowStockReport.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/LowStockReport.cshtml index 725d5e6aa9..78b446af75 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/LowStockReport.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/LowStockReport.cshtml @@ -50,6 +50,7 @@ }) .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("LowStockReportList", "Product")) + .PreserveGridState() .EnableCustomBinding(true))
    - @if (!String.IsNullOrEmpty(product.Sku)) { - - - - - } - @if (!String.IsNullOrEmpty(product.Price)) - { - - - - - } - @if (!String.IsNullOrEmpty(product.Manufacturer)) - { - - - - - } - @if (!String.IsNullOrEmpty(product.Weight)) - { - - - - - } - @if (!String.IsNullOrEmpty(product.Length)) - { - - - - - } - @if (!String.IsNullOrEmpty(product.Width)) - { - - - - - } - @if (!String.IsNullOrEmpty(product.Height)) - { - - - - - } -
    - @T("PDFProductCatalog.SKU"): - - @product.Sku -
    - @T("PDFProductCatalog.Price"): - - @product.Price -
    - @T("PDFProductCatalog.Manufacturer"): - - @product.Manufacturer -
    - @T("PDFProductCatalog.Weight"): - - @product.Weight -
    - @T("PDFProductCatalog.Length"): - - @product.Length -
    - @T("PDFProductCatalog.Width"): - - @product.Width -
    - @T("PDFProductCatalog.Height"): - - @product.Height -
    - - - @if (product.PictureUrl.HasValue()) - { -
    - -
    - } - - @Html.Widget("pdfcatalog_productdetail_fulldescription_before") - -

    - @Html.Raw(product.FullDescription) -

    - - @Html.Widget("pdfcatalog_productdetail_picture_before") - - @if(product.SpecificationAttributes.Count > 0) - { - @Html.Widget("pdfcatalog_productdetail_specificationattributes_before") - -

    @T("PDFProductCatalog.SpecificationAttributes")

    - - @foreach (var specattr in product.SpecificationAttributes) - { - - - - - } -
    - @specattr.SpecificationAttributeName : - - @specattr.SpecificationAttributeOption -
    - } - - @if(product.BundledItems.Count > 0) - { -
    - @Html.Widget("pdfcatalog_productdetail_bundleditems_before") -

    @T("PDFProductCatalog.BundledItems")

    - foreach (var bundleProduct in product.BundledItems) - { -
    -
    - -
    -
    - @bundleProduct.Name -
    - @bundleProduct.ShortDescription -
    - -
    - } - -
    - } - - @if (product.AssociatedProducts.Count > 0) - { -
    - @Html.Widget("pdfcatalog_productdetail_associatedproducts_before") -

    @T("PDFProductCatalog.AssociatedProducts")

    - foreach (var groupedProduct in product.AssociatedProducts) - { -
    -
    - -
    -
    - @groupedProduct.Name -
    - @groupedProduct.ShortDescription -
    -
    - } -
    - } - - - } - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/ProductAttributeValueLinkagePopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/ProductAttributeValueLinkagePopup.cshtml deleted file mode 100644 index 0ef7426f3c..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/ProductAttributeValueLinkagePopup.cshtml +++ /dev/null @@ -1,159 +0,0 @@ -@model ProductModel.ProductVariantAttributeValueModel.AddProductLinkageModel -@{ - Layout = "~/Administration/Views/Shared/_AdminPopupLayout.cshtml"; -} -@{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - ViewBag.Title = T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.LinkedProduct.AddNew").Text; -} - -@using Telerik.Web.Mvc.UI; - -@using (Html.BeginForm()) -{ -
    -
    - @T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.LinkedProduct.AddNew") -
    -
    - - if (ViewBag.RefreshPage == true) - { - - } - - @Html.HiddenFor(model => model.ProductId) - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchProductName) - - @Html.EditorFor(model => Model.SearchProductName) -
    - @Html.SmartLabelFor(model => model.SearchCategoryId) - - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchManufacturerId) - - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    - @Html.SmartLabelFor(model => model.SearchProductTypeId) - - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) -
    -   - - -
    - -

    - - - - - - - - -
    - @(Html.Telerik().Grid() - .Name("products-grid") - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title(T("Admin.Telerik.GridLocalization.Select").Text) - .Width(50); - columns.Bound(x => x.Name) - .Template(x => @Html.LabeledProductName(0, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName(null, "Name")); - columns.Bound(x => x.Sku); - columns.Bound(x => x.Published) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered() - .Width(100); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("ProductAttributeValueLinkagePopupList", "Product")) - .ClientEvents(events => events.OnDataBinding("productLinkage_onDataBinding")) - .EnableCustomBinding(true)) -
    - -
    - - -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/ProductTags.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/ProductTags.cshtml index 4f3f636451..db78195f3f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/ProductTags.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/ProductTags.cshtml @@ -50,6 +50,7 @@ .Select("ProductTags", "Product") .Delete("ProductTagDelete", "Product"); }) + .PreserveGridState() .EnableCustomBinding(true)) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/RelatedProductAddPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/RelatedProductAddPopup.cshtml deleted file mode 100644 index 7d5db715ca..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/RelatedProductAddPopup.cshtml +++ /dev/null @@ -1,146 +0,0 @@ -@{ - Layout = "~/Administration/Views/Shared/_AdminPopupLayout.cshtml"; -} -@model ProductModel.AddRelatedProductModel -@{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title - ViewBag.Title = T("Admin.Catalog.Products.RelatedProducts.AddNew").Text; -} -@using Telerik.Web.Mvc.UI; -@using (Html.BeginForm()) -{ -
    -
    - @T("Admin.Catalog.Products.RelatedProducts.AddNew") -
    -
    - - if (ViewBag.RefreshPage == true) - { - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchProductName) - - @Html.EditorFor(model => Model.SearchProductName) -
    - @Html.SmartLabelFor(model => model.SearchCategoryId) - - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchManufacturerId) - - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    - @Html.SmartLabelFor(model => model.SearchProductTypeId) - - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) -
    -   - - -
    -

    - - - - - - - - -
    - @(Html.Telerik().Grid(Model.Products.Data) - .Name("products-grid") - .Columns(columns => - { - columns.Bound(x => x.Id) - .Template(x => string.Format("", x.Id)) - .ClientTemplate("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Name) - .Template(x => @Html.LabeledProductName(0, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName(null, "Name")); - columns.Bound(x => x.Sku); - columns.Bound(x => x.Published) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered() - .Width(100); - }) - .Pageable(settings => settings.Total(Model.Products.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("RelatedProductAddPopupList", "Product")) - .ClientEvents(events => events.OnDataBinding("relatedProducts_onDataBinding")) - .EnableCustomBinding(true)) -
    -

    - -
    - - -} \ No newline at end of file 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 ebf79c151d..0827ea946f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.AssociatedProducts.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.AssociatedProducts.cshtml @@ -4,83 +4,93 @@ @if (Model.Id > 0) { -
    +
    @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() - .Template(x => @Html.LabeledProductName(x.Id, x.ProductName, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("Id", "ProductName")); - columns.Bound(x => x.Sku) - .ReadOnly(); - columns.Bound(x => x.Published) - .ReadOnly() - .Width(100) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(100) - .Centered(); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }) - .Width(180); - }) - .EnableCustomBinding(true)) -
    - - +

    - -
    -
    + }); + + }); + + + + } else { 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 9ba9831a5d..fc675e560f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml @@ -94,7 +94,7 @@ @T("Admin.Catalog.Products.ProductVariantAttributes.AttributeCombinations.Description")
    -
    +
    ") - // .Title(""); - columns.Command(commands => - { - commands.Delete().Localize(T); - }) - .Width(90); - }) - .ClientEvents(events => events.OnRowDataBound("onRowDataBound_Combinations")) - .EnableCustomBinding(true)) + columns.Bound(x => x.StockQuantity) + .Width(160) + .Centered(); + columns.Bound(x => x.AllowOutOfStockOrders) + .Width(60) + .HeaderTemplate("") + .Template(item => @Html.SymbolForBool(item.AllowOutOfStockOrders)) + .ClientTemplate(@Html.SymbolForBool("AllowOutOfStockOrders")) + .Centered(); + //columns.Bound(x => x.Id) + // .Width(90) + // .Centered() + // .ClientTemplate("") + // .Title(""); + columns.Command(commands => + { + commands.Delete().Localize(T); + }) + .Width(90); + }) + .Pageable(settings => settings.PageSize(100).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("ProductVariantAttributeCombinationList", "Product", new { productId = Model.Id }) + .Delete("ProductVariantAttributeCombinationDelete", "Product"); + }) + .ClientEvents(events => events.OnRowDataBound("onRowDataBound_Combinations")) + .EnableCustomBinding(true))
    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 f750912c51..1d3bbb0a9e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.BundleItems.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.BundleItems.cshtml @@ -11,7 +11,25 @@ } } - + +
    + +
    + @T("Admin.Catalog.Products.BundleItems.NotesOnProductBundles"): + @T("Common.Show") +
    +
    + @Html.Raw(T("Admin.Catalog.Products.BundleItems.AdminNoteGeneral")) + + @Html.Raw(T(Model.BundlePerItemPricing ? "Admin.Catalog.Products.BundleItems.AdminNotePerItemPricing" : "Admin.Catalog.Products.BundleItems.AdminNoteBundlePricing")) + + @if (Model.BundlePerItemShipping) + { + @Html.Raw(T("Admin.Catalog.Products.BundleItems.AdminNotePerItemShipping")) + } +
    +
    +
    @(Html.LocalizedEditor("product-bundle-localized", @ @@ -70,77 +88,67 @@
    -
    - - - + +
    - @(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 => +

    + + + + - - - + + + - -
    + @(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 => { - columns.Bound(x => x.ProductName) - .ReadOnly() - .Template(x => @Html.LabeledProductName(x.ProductId, x.ProductName, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); - columns.Bound(x => x.Sku) - .ReadOnly(); - columns.Bound(x => x.Quantity) - .Width(80) - .Centered(); - columns.Bound(x => x.Discount) - .Width(80) - .Centered(); - columns.Bound(x => x.Visible) - .Width(100) - .Template(item => @Html.SymbolForBool(item.Visible)) - .ClientTemplate(@Html.SymbolForBool("Visible")) - .Centered(); - columns.Bound(x => x.Published) - .Width(100) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(100) - .Centered(); - columns.Bound(x => x.Id) - .Width(90) - .Centered() - .ClientTemplate("") - .Title(""); - columns.Command(commands => - { - commands.Delete().Localize(T); - }) - .Width(90); - }) - .ClientEvents(events => events.OnRowDataBound("onRowDataBound_bundleItems")) - .EnableCustomBinding(true)) -
    - + commands.Delete().Localize(T); + }); + }) + .ClientEvents(events => events.OnRowDataBound("onRowDataBound_bundleItems")) + .EnableCustomBinding(true)) +
    + - -
    - + +
    } 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 70185ec8ed..5e758d0e85 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Categories.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Categories.cshtml @@ -32,7 +32,9 @@ }) .Columns(columns => { - columns.Bound(x => x.Category); + columns.Bound(x => x.Category) + .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)) 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 23e3aa1efb..694263f00a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Discounts.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Discounts.cshtml @@ -6,13 +6,15 @@ { } } else { - @T("Admin.Catalog.Products.Discounts.NoDiscounts") +
    + @T("Admin.Catalog.Products.Discounts.NoDiscounts") +
    } 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 c00b2a59de..8d295edfb2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml @@ -34,6 +34,25 @@ toggleShipping(); toggleTax(); + // toggle homepage display order + $('#ShowOnHomePage').change(function () { + $('#pnlHomePageDisplayOrder').toggle($(this).is(':checked')); + }).trigger('change'); + + // add required products + $(document).on('click', '#SearchRequiredProductsButton', function () { + $('#@(Html.FieldIdFor(x => x.RequiredProductIds))').entityPicker('loadDialog', { + url: '@Url.Action("EntityPicker", "Common", new { area = "" })', + caption: '@T("Admin.Catalog.Products.Fields.RequireOtherProducts")', + returnValueDelimiter: ',', + onLoadDialogBefore: function () { + $('#SearchRequiredProductsButton').button('loading').prop('disabled', true); + }, + onLoadDialogComplete: function () { + $('#SearchRequiredProductsButton').prop('disabled', false).button('reset'); + } + }); + }); }); function toggleProductType() { @@ -187,14 +206,14 @@
    - @if (Model.Id > 0) + @if (Model.Id != 0) { @@ -398,6 +417,15 @@ @Html.ValidationMessageFor(model => model.ShowOnHomePage) + + + + @@ -566,7 +599,7 @@ @Html.SmartLabelFor(model => model.UserAgreementText) 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 97ccd2c5b2..e06230c752 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Manufacturers.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Manufacturers.cshtml @@ -9,7 +9,11 @@ @(Html.Telerik().Grid() @@ -29,7 +33,8 @@ .Columns(columns => { columns.Bound(x => x.Manufacturer) - .Width(500); + .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)) @@ -46,7 +51,11 @@ .Width(180); }) .ToolBar(commands => commands.Insert()) - .ClientEvents(events => events.OnEdit("onProductManufacturerEdit")) + .ClientEvents(events => + { + events.OnEdit("onProductManufacturerEdit"); + events.OnDataBound("onProductManufacturerDataBound"); + }) .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 346b925d83..a8e66f1e74 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml @@ -38,9 +38,7 @@ }) .EnableCustomBinding(true)) -

    -

    @T("Admin.Catalog.Products.Pictures.AddNew")

    -

    + +
    @Html.SmartLabelFor(model => model.Id) - @Model.Id.ToString() + @Html.DisplayFor(model => model.Id) @Html.ValidationMessageFor(model => model.Id)
    + @Html.SmartLabelFor(model => model.HomePageDisplayOrder) + + @Html.EditorFor(model => model.HomePageDisplayOrder) + @Html.ValidationMessageFor(model => model.HomePageDisplayOrder) +
    @Html.SmartLabelFor(model => model.Published) @@ -452,6 +480,11 @@ @Html.EditorFor(model => model.RequiredProductIds) + + + @Html.ValidationMessageFor(model => model.RequiredProductIds)
    - @Html.TextAreaFor(model => model.UserAgreementText) + @Html.EditorFor(model => model.UserAgreementText, Html.RichEditorFlavor()) @Html.ValidationMessageFor(model => model.UserAgreementText)
    + + + @@ -92,12 +98,15 @@ - + +
    +
    +
    @T("Admin.Catalog.Products.Pictures.AddNew")
    +
    +
    @Html.SmartLabelFor(model => model.AddPictureModel.PictureId) - @Html.EditorFor(model => model.AddPictureModel.PictureId) + @Html.EditorFor(model => model.AddPictureModel.PictureId, new { transientUpload = true }) @Html.ValidationMessageFor(model => model.AddPictureModel.PictureId)
    - - +   + + +
    } 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 b21bf3da14..bebc6fa0c7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Price.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Price.cshtml @@ -16,7 +16,6 @@ fieldBasePriceBaseAmount = $("#@Html.FieldIdFor(model => model.BasePriceBaseAmount)"); fieldBasePriceMeasureUnit.on("change", getCurrentBasePrice); - fieldBasePriceAmount.on("blur", getCurrentBasePrice); fieldBasePriceBaseAmount.on("blur", getCurrentBasePrice); @@ -25,7 +24,9 @@ toggleBasePrice(); function getCurrentBasePrice() { - if (parseFloat(fieldBasePriceAmount.val()) > 0 && parseInt(fieldBasePriceBaseAmount.val()) > 0) { + var basePriceAmount = Globalize.parseFloat(fieldBasePriceAmount.val()); + var basePriceBaseAmount = Globalize.parseInt(fieldBasePriceBaseAmount.val()); + if (basePriceAmount > 0 && basePriceBaseAmount > 0) { $.ajax({ cache:false, type: "POST", @@ -40,7 +41,7 @@ $("#BasePriceAmountInfo").html(data.BasePrice); }, error: function (xhr, ajaxOptions, thrownError){ - alert(thrownError); + //alert(thrownError); } }); } @@ -51,10 +52,12 @@ if ($('#@Html.FieldIdFor(model => model.BasePriceEnabled)').is(':checked')) { $('#pnlBasePriceBaseAmount').show(); $('#pnlBasePriceAmount').show(); + $('#pnlBasePriceAmountHint').show(); } else { $('#pnlBasePriceBaseAmount').hide(); $('#pnlBasePriceAmount').hide(); + $('#pnlBasePriceAmountHint').hide(); } } @@ -224,6 +227,17 @@ @*@T("Admin.Catalog.Products.Fields.BasePriceInfo")*@ + + +   + + +
    + @T("Admin.Products.BasePrice.Hint") +
    + + +
    @@ -259,6 +273,7 @@ .Width(120); columns.Bound(x => x.Price1) .Format("{0:0.00}") + .Width(120) .RightAlign(); columns.Bound(x => x.Store); columns.Bound(x => x.CustomerRole); 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 af38a44a21..cdeda617b9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Promotion.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Promotion.cshtml @@ -26,51 +26,38 @@ { columns.Bound(x => x.Product2Name) .ReadOnly() - .Template(x => @Html.LabeledProductName(x.ProductId2, x.Product2Name, x.ProductTypeName, x.ProductTypeLabelHint)) + .Width(520) .ClientTemplate(@Html.LabeledProductName("ProductId2", "Product2Name")); columns.Bound(x => x.Product2Sku) .ReadOnly(); columns.Bound(x => x.Product2Published) .ReadOnly() - .Width(100) - .Template(item => @Html.SymbolForBool(item.Product2Published)) .ClientTemplate(@Html.SymbolForBool("Product2Published")) .Centered(); columns.Bound(x => x.DisplayOrder) - .Width(100) .Centered(); columns.Command(commands => { commands.Edit().Localize(T); commands.Delete().Localize(T); - }) - .Width(180); + }).Width(220); }) .EnableCustomBinding(true)) - - - - + @@ -83,7 +70,9 @@ }
    -
    +

     

    + +

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

    @if (Model.Id > 0) @@ -107,44 +96,32 @@ { columns.Bound(x => x.Product2Name) .ReadOnly() - .Template(x => @Html.LabeledProductName(x.ProductId2, x.Product2Name, x.ProductTypeName, x.ProductTypeLabelHint)) + .Width(520) .ClientTemplate(@Html.LabeledProductName("ProductId2", "Product2Name")); columns.Bound(x => x.Product2Sku); columns.Bound(x => x.Product2Published) - .Width(100) - .Template(item => @Html.SymbolForBool(item.Product2Published)) .ClientTemplate(@Html.SymbolForBool("Product2Published")) .Centered(); columns.Command(commands => { commands.Delete().Localize(T); - }) - .Width(90); + }).Width(220); }) .EnableCustomBinding(true)) - - - - - + @@ -156,3 +133,67 @@
    }
    + + \ 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 329d12360b..985ff1f3bb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SEO.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SEO.cshtml @@ -79,6 +79,10 @@ @Html.TextAreaFor(x => x.SeName, new { @class = "input-large" }) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "Product", entityId = @Model.Id }) + } @Html.ValidationMessageFor(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 fd1512ae37..87982efc85 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SpecificationAttributes.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SpecificationAttributes.cshtml @@ -1,7 +1,5 @@ -@model ProductModel - -@using Telerik.Web.Mvc.UI; - +@using Telerik.Web.Mvc.UI; +@model ProductModel @{ Layout = ""; } @@ -11,9 +9,6 @@ /*hide "add spec" table if no attributes are defined*/ if (Model.AddSpecificationAttributeModel.AvailableAttributes.Count > 0) { -
    -

    @T("Admin.Catalog.Products.SpecificationAttributes.AddNew")

    -
    - + +
    + + + - - + +
    +
    +
    @T("Admin.Catalog.Products.SpecificationAttributes.AddNew")
    +
    +
    @Html.SmartLabelFor(model => model.AddSpecificationAttributeModel.SpecificationAttributeId) @@ -112,13 +115,15 @@
    - - +   + + +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml index 475e675f78..4f5763b2db 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml @@ -64,6 +64,8 @@ } 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" }) @@ -109,8 +111,12 @@ 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(); + .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" }) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml index 92de51ade3..9e07dff329 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml @@ -29,12 +29,13 @@ @if (!ViewBag.IsEdit) { -
    +
    @if (Model.Warnings.Count > 0) { -
    - @foreach (var warning in Model.Warnings) { -

     @warning

    +
    + @foreach (var warning in Model.Warnings) + { +

     @warning

    }
    } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateProductAttributeValue.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateProductAttributeValue.cshtml index 0bc012f616..697da14da4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateProductAttributeValue.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateProductAttributeValue.cshtml @@ -36,19 +36,44 @@ } }).trigger('change'); - $('#remove-product-linkage').click(function () { + $('#RemoveProductLinkageButton').click(function () { $('#linked-product-name').empty(); $('#@Html.FieldIdFor(model => model.LinkedProductId)').val('0'); toggleLinkageButtons(); }); toggleLinkageButtons(); + + // add product linkage + $(document).on('click', '#AddProductLinkageButton', function () { + $('#@(Html.FieldIdFor(x => x.LinkedProductId))').entityPicker('loadDialog', { + url: '@Url.Action("EntityPicker", "Common", new { area = "" })', + caption: '@T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.LinkedProduct.AddNew")', + maxReturnValues: 1, + disableIds: '@Model.ProductId', + onLoadDialogBefore: function () { + $('#AddProductLinkageButton').button('loading').prop('disabled', true); + }, + onLoadDialogComplete: function () { + $('#AddProductLinkageButton').prop('disabled', false).button('reset'); + }, + onOkClicked: function (ids) { + var dialog = $('#entity-picker-product-dialog'), + item = dialog.find('.entity-picker-list .selected').first(); + + $('#linked-product-name').html(item.find('.title').attr('title')); + + return true; + } + }); + }); + }); function toggleLinkageButtons() { var unlinked = ($('#@Html.FieldIdFor(model => model.LinkedProductId)').val() === '0'); - $('#add-product-linkage').toggle(unlinked); - $('#remove-product-linkage').toggle(!unlinked); + $('#AddProductLinkageButton').toggle(unlinked); + $('#RemoveProductLinkageButton').toggle(!unlinked); } @@ -70,14 +95,15 @@ @Html.HiddenFor(model => model.LinkedProductId)
    @Model.LinkedProductName
    - - + + @Html.ValidationMessageFor(model => model.LinkedProductId) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml index 2e99344890..dd1c42971f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml @@ -71,6 +71,7 @@ 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) @@ -92,13 +93,14 @@ }) .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "ProductReview")) + .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 c2945f7dd3..b1720f9b11 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/_CreateOrUpdate.cshtml @@ -75,10 +75,27 @@ @Html.SmartLabelFor(model => model.Subject) - @Html.EditorFor(model => model.Subject) + @Html.TextBoxFor(model => model.Subject, new { @class = "input-large" }) @Html.ValidationMessageFor(model => model.Subject) + @if (Model.Attachments.Any()) + { + + + @Html.SmartLabelFor(model => model.Attachments) + + + @foreach (var attach in Model.Attachments) + { + + + @attach.Name + + } + + + } @Html.SmartLabelFor(model => model.Body) @@ -88,6 +105,15 @@ @Html.ValidationMessageFor(model => model.Body) + + + @Html.SmartLabelFor(model => model.SendManually) + + + @Html.EditorFor(model => model.SendManually) + @Html.ValidationMessageFor(model => model.SendManually) + + @Html.SmartLabelFor(model => model.SentTries) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml index 4b677ad4d8..3cdeed38c0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml @@ -12,8 +12,7 @@
    - - +
    @Html.Partial("_CreateOrUpdate", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml index 3a33bcf933..7c20b8063e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml @@ -55,6 +55,7 @@ }) .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 f43b1ff44e..9091971da4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/_CreateOrUpdate.cshtml @@ -97,23 +97,29 @@ } @helper TabHistory() { + - -
    + @if (!String.IsNullOrEmpty(Model.NextPaymentDate)) - { + { @T("Admin.RecurringPayments.History.NextPaymentDate") - : @Model.NextPaymentDate - - } + + : @Model.NextPaymentDate + + + } @if (Model.CanCancelRecurringPayment) - { - - } + { + + }
    - + + @if(Model.RequestedActionUpdated.HasValue) + { + + + + + } + + + + + + + +
    @(Html.Telerik().Grid(Model.History) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml index 78de378985..c9a62c9b5c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml @@ -13,82 +13,88 @@ - - - - - - - - - - - - - - +
    - @Html.SmartLabelFor(model => model.SearchId) - - @Html.EditorFor(model => Model.SearchId) -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores) -
    - @Html.SmartLabelFor(model => model.SearchReturnRequestStatusId) - - @Html.DropDownListFor(model => model.SearchReturnRequestStatusId, Model.AvailableReturnRequestStatus) -
    + + + + + @if (Model.AvailableStores.Count > 1) + { + + + + + } + + + + + - - + +
    + @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.Telerik().Grid() - .Name("returnrequests-grid") - .Columns(columns => - { + .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) + 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 { })) + .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) + columns.Bound(x => x.OrderId) .Width(80) - .Centered() - .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Order", new { id = x.OrderId }, new { })) + .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); - columns.Bound(x => x.Id) + 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); - }) + .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")) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "ReturnRequest")) .ClientEvents(events => events.OnDataBinding("returnRequestGrid_onDataBinding")) - .EnableCustomBinding(true)) + .PreserveGridState() + .EnableCustomBinding(true))
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/_CreateOrUpdate.cshtml index 2421cf7076..518acee762 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/_CreateOrUpdate.cshtml @@ -90,6 +90,17 @@ @Html.ValidationMessageFor(model => model.RequestedAction)
    + @Html.SmartLabelFor(model => model.RequestedActionUpdated) + + @Html.DisplayFor(model => model.RequestedActionUpdated) +
    @Html.SmartLabelFor(model => model.CustomerComments) @@ -108,6 +119,15 @@ @Html.ValidationMessageFor(model => model.StaffNotes)
    + @Html.SmartLabelFor(model => model.AdminComment) + + @Html.TextAreaFor(model => model.AdminComment, new { style = "min-width: 500px; min-height: 100px;" }) + @Html.ValidationMessageFor(model => model.AdminComment) +
    @Html.SmartLabelFor(model => model.CreatedOn) @@ -116,4 +136,12 @@ @Html.DisplayFor(model => model.CreatedOn)
    + @Html.SmartLabelFor(model => model.UpdatedOn) + + @Html.DisplayFor(model => model.UpdatedOn) +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml new file mode 100644 index 0000000000..a1018ad9ea --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml @@ -0,0 +1,167 @@ +@model ScheduleTaskModel +@using SmartStore +@using SmartStore.Core +@{ + ViewBag.Title = T("Admin.System.ScheduleTasks.EditTask") + " - " + Model.Name; + var cronHelpLink = GenerateHelpUrl("Managing+Scheduled+Tasks#ManagingScheduledTasks-Cron"); + var returnUrl = ((string)ViewBag.ReturnUrl).NullEmpty(); +} + +@using (Html.BeginForm()) +{ + @Html.AntiForgeryToken() + @Html.HiddenFor(model => model.Id) + @Html.Hidden("returnUrl", returnUrl) + +
    +
    + + @ViewBag.Title + (@T("Common.Back")) +
    +
    + + + @if (!Model.IsRunning) + { +  @T("Admin.System.ScheduleTasks.RunNow") + } +
    +
    + + @Html.ValidationSummary(false) + + + + + + + + + + + + + + + + + + + @if (Model.Duration.HasValue()) + { + + + + + } + @if (Model.LastError.HasValue()) + { + + + + + if (Model.LastSuccess.HasValue && Model.LastSuccess != Model.LastEnd) + { + + + + + } + } + @if (Model.NextRun.HasValue) + { + + + + + } + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.Name) + + @Html.EditorFor(model => model.Name) + @Html.ValidationMessageFor(model => model.Name) +
    + @Html.SmartLabelFor(model => model.Enabled) + + @Html.EditorFor(model => model.Enabled) + @Html.ValidationMessageFor(model => model.Enabled) +
    + @Html.SmartLabelFor(model => model.StopOnError) + + @Html.EditorFor(model => model.StopOnError) + @Html.ValidationMessageFor(model => model.StopOnError) +
    + @Html.SmartLabelFor(model => model.LastStart) + + + @(Model.LastStart.HasValue ? Model.LastStart.Value.ToString("g") : T("Common.Never").Text) +
    + @Html.SmartLabelFor(model => model.Duration) + + @Html.DisplayFor(model => model.Duration) +
    + @Html.SmartLabelFor(model => model.LastError) + + @Html.DisplayFor(model => model.LastError) +
    + @Html.SmartLabelFor(model => model.LastSuccess) + + @Model.LastSuccess.Value.ToString("g") +
    + @Html.SmartLabelFor(model => model.NextRun) + + @Model.NextRun.Value.ToString("g") +
    + @Html.SmartLabelFor(model => model.CronExpression) + + @Html.EditorFor(model => model.CronExpression ) + @Html.ValidationMessageFor(model => model.CronExpression) +
      +
    @T("Admin.System.ScheduleTasks.CronHelp", cronHelpLink)
    +
      +
    +
    +} + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/FutureSchedules.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/FutureSchedules.cshtml new file mode 100644 index 0000000000..8a86a5d4dc --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/FutureSchedules.cshtml @@ -0,0 +1,46 @@ +@model IEnumerable +@using SmartStore.Core + +@{ + string error = (string)ViewBag.CronScheduleParseError; + string description = (string)ViewBag.Description; +} + + + +@if (error.HasValue()) +{ +
    @error
    +} +else +{ +

    @T("Admin.System.ScheduleTasks.FutureSchedules")

    + if (description.HasValue()) + { +

    @description

    + } + + + @foreach (var day in Model.ToLookup(x => x.Date)) + { + + + + + } + +
    @day.Key.ToShortDateString() + @foreach (var time in day) + { +
    @time.ToShortTimeString()
    + } +
    +} + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml index d25f7de887..c7e498cf2f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml @@ -1,12 +1,10 @@ -@model dynamic +@model IList @using Telerik.Web.Mvc.UI; @using System.Linq; @{ - //page title ViewBag.Title = T("Admin.System.ScheduleTasks").Text; - - var runJobUrl = Url.Content("~/Admin/ScheduleTask/RunJob/"); } +
    @@ -14,69 +12,86 @@
    -
    - - @T("Admin.System.ScheduleTasks.RestartApplication") -
    - - - - - +
    - @(Html.Telerik().Grid() - .Name("schedule-tasks-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("Id"); - }) - .Columns(columns => - { - columns.Bound(x => x.Name).ClientTemplate("<#= Name #>"); - columns.Bound(x => x.Seconds); - columns.Bound(x => x.Enabled) - .ClientTemplate(@Html.SymbolForBool("Enabled")) - .Centered() - .Width(100); - columns.Bound(x => x.StopOnError) - .ClientTemplate(@Html.SymbolForBool("StopOnError")) - .Centered() - .Width(100); - //columns.Bound(x => x.LastStartUtc).ReadOnly(); - //columns.Bound(x => x.LastEndUtc).ReadOnly(); - //columns.Bound(x => x.LastSuccessUtc).ReadOnly(); - columns.Bound(x => x.Id) - .ClientTemplate("<# if (LastStartUtc.length) {#>
    " + @T("Admin.System.ScheduleTasks.LastStart") + ": <#= LastStartUtc #>
    <# } #>" + - "<# if (!LastEndUtc && !IsRunning) {#>
    " + @T("Admin.System.ScheduleTasks.LastStart") + ": " + @T("Common.Never") + "
    <# } #>" + - "<# if (Duration.length) {#>
    " + @T("Common.Duration") + ": <#= Duration #>
    <# } #>" + - "<# if (LastError.length) {#>
    " + @T("Common.Error") + ": <#= LastError #>
    <# } #>" + - "<# if (LastSuccessUtc != LastEndUtc) {#>
    " + @T("Admin.System.ScheduleTasks.LastSuccess") + ": <#= LastSuccessUtc #>
    <# } #>" + - "<# if (!IsRunning) {#> " + @T("Admin.System.ScheduleTasks.RunNow") + "<# } #>" + - "<# if (IsRunning) {#>
    " + @T("Admin.System.ScheduleTasks.RunNow.IsRunning") + "
    <# } #>") - .Title("Ausführung") - .ReadOnly(); - columns.Command(commands => - { - commands.Edit().Text(T("Admin.Common.Edit").Text); - }).Width(180).Title(T("Admin.Common.Edit").Text); - - }) - .Editable(x => - { - x.Mode(GridEditMode.InLine); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("List", "ScheduleTask") - .Update("TaskUpdate", "ScheduleTask"); - }) - .ClientEvents(x => x.OnError("grid_onError")) - .EnableCustomBinding(true)) - -
    + + + + + + + + + + + + @foreach (var task in Model) + { + + + + + + + + + + + + } +
    @T("Admin.System.ScheduleTasks.Name")@T("Admin.System.ScheduleTasks.Enabled")@T("Admin.System.ScheduleTasks.CronExpression")@T("Admin.System.ScheduleTasks.LastStart")@T("Admin.System.ScheduleTasks.NextRun")@T("Admin.Common.Actions")
    + @task.Name + @Html.SymbolForBool(task.Enabled) +
    @task.CronExpression
    + @if (task.CronDescription.HasValue()) + { +
    @task.CronDescription
    + } +
    +
    + @Html.Partial("_LastRun", task) +
    +
    +
    + @Html.Partial("_NextRun", task) +
    +
    +
    + @T("Common.Edit") + + @T("Admin.System.ScheduleTasks.RunNow") + + + @T("Common.Cancel") + +
    + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/MinimalTask.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/MinimalTask.cshtml new file mode 100644 index 0000000000..530a08f62e --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/MinimalTask.cshtml @@ -0,0 +1,63 @@ +@model ScheduleTaskModel +@{ + Layout = null; + var widgetId = "minimal-task-widget-" + Model.Id; + var returnUrl = (string)ViewBag.ReturnUrl; + var hasPermission = ViewBag.HasPermission == true; + var cancellable = ViewBag.Cancellable == true; + var reloadPage = ViewBag.ReloadPage == true; +} +
    +
    + @Html.Partial("_MinimalTaskWidget", Model) +
    +
    + @if (cancellable && hasPermission) + { + + @T("Common.Cancel") + + } +
    + + + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_LastRun.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_LastRun.cshtml new file mode 100644 index 0000000000..6626a3e711 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_LastRun.cshtml @@ -0,0 +1,30 @@ +@model ScheduleTaskModel +@{ + Layout = null; +} + +@if (!Model.IsRunning && !Model.LastEnd.HasValue) +{ +
    @T("Common.Never")
    +} +else if (Model.LastStart.HasValue) +{ +
    @Model.LastStart.Value.ToString("g")
    +
    @Model.LastStartPretty
    + if (Model.Duration.HasValue()) + { +
    @T("Common.Duration"): @Model.Duration
    + } + if (Model.LastError.HasValue()) + { +
    @T("Common.Error"): @Model.LastError
    + if (Model.LastSuccess.HasValue && Model.LastSuccess != Model.LastEnd) + { +
    @T("Admin.System.ScheduleTasks.LastSuccess"): @Model.LastSuccess.Value.ToString("g")
    + } + } +} + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_MinimalTaskWidget.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_MinimalTaskWidget.cshtml new file mode 100644 index 0000000000..8c93b494dc --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_MinimalTaskWidget.cshtml @@ -0,0 +1,58 @@ +@model ScheduleTaskModel +@{ + Layout = null; + var returnUrl = (string)ViewBag.ReturnUrl; + var hasPermission = ViewBag.HasPermission == true; +} +@(Model.Enabled ? T("Common.Scheduled") : T("Common.Unscheduled")) +@if (Model.Enabled) +{ + @T("Common.Rule"): + @(Model.CronDescription ?? Model.CronExpression) + if (Model.NextRun.HasValue) + { + - @T("Admin.System.ScheduleTasks.NextRun"): + @Model.NextRunPretty + } +} +else +{ + @T("Admin.System.ScheduleTasks.LastStart"): + @(Model.LastStart.HasValue ? Model.LastStartPretty : T("Common.Never").Text) +} +@if (hasPermission) +{ + +} + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_NextRun.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_NextRun.cshtml new file mode 100644 index 0000000000..8bdd1b5575 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/_NextRun.cshtml @@ -0,0 +1,25 @@ +@model ScheduleTaskModel +@{ + Layout = null; +} + +@if (Model.NextRun.HasValue) +{ + if (Model.IsOverdue) + { + @Model.NextRunPretty + } + else + { +
    @Model.NextRun.Value.ToString("g")
    +
    @Model.NextRunPretty
    + } +} +else +{ +
    @T("Common.Never")
    +} + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Security/Permissions.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Security/Permissions.cshtml index 5703d75a62..743c8f9d8f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Security/Permissions.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Security/Permissions.cshtml @@ -1,6 +1,6 @@ -@model PermissionMappingModel +@using SmartStore.Utilities; +@model PermissionMappingModel @{ - //page title ViewBag.Title = T("Admin.Configuration.ACL").Text; } @using (Html.BeginForm()) @@ -11,7 +11,9 @@ @T("Admin.Configuration.ACL")
    - +
    @@ -19,12 +21,16 @@ @if (Model.AvailablePermissions.Count == 0) - { - No permissions defined - } - else if (Model.AvailableCustomerRoles.Count == 0) - { - No customer roles available + { +
    + @T("Admin.System.Warnings.NoPermissionsDefined") +
    + } + else if (Model.AvailableCustomerRoles.Count == 0) + { +
    + @T("Admin.System.Warnings.NoCustomerRolesDefined") +
    } else { @@ -33,12 +39,12 @@ - @T("Admin.Configuration.ACL.Permission") + @T("Admin.Configuration.ACL.Permission") @foreach (var cr in Model.AvailableCustomerRoles) { - @cr.Name + @cr.Name } @@ -48,6 +54,7 @@ { + @Inflector.Titleize(pr.Category).NaIfEmpty() @pr.Name @foreach (var cr in Model.AvailableCustomerRoles) @@ -66,5 +73,4 @@ - } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml index b4bc33afa9..2ad6b7fb2e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml @@ -53,6 +53,7 @@ .Insert("SettingAdd", "Setting"); }) .ClientEvents(x => x.OnError("grid_onError").OnEdit("grid_onStoreEdit")) + .PreserveGridState() .EnableCustomBinding(true)) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml index dc64352a48..973aa0703f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml @@ -64,6 +64,15 @@ @Html.ValidationMessageFor(model => model.NumberOfTags) + + + @Html.SmartLabelFor(model => model.MaxAgeInDays) + + + @Html.SettingEditorFor(model => model.MaxAgeInDays) + @Html.ValidationMessageFor(model => model.MaxAgeInDays) + + @Html.SmartLabelFor(model => model.ShowHeaderRssUrl) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml index a530f1f059..56664aafbb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml @@ -23,13 +23,17 @@ $("#@Html.FieldIdFor(model => model.RecentlyViewedProductsEnabled)").click(toggleRecentlyViewedProducts); $("#@Html.FieldIdFor(model => model.RecentlyAddedProductsEnabled)").click(toggleRecentlyAddedProducts); $("#@Html.FieldIdFor(model => model.ShowBestsellersOnHomepage)").click(toggleBestsellersOnHomepage); + $("#@Html.FieldIdFor(model => model.ShowManufacturersOnHomepage)").click(toggleManufacturersOnHomepage); $("#@Html.FieldIdFor(model => model.ProductsAlsoPurchasedEnabled)").click(toggleProductsAlsoPurchasedNumber); $("#@Html.FieldIdFor(model => model.ProductsByTagAllowCustomersToSelectPageSize)").click(toggleTagPageSize); $("#@Html.FieldIdFor(model => model.ProductSearchAllowCustomersToSelectPageSize)").click(toggleSearchPageSize); $("#@Html.FieldIdFor(model => model.ProductSearchAutoCompleteEnabled)").click(toggleProductSearchAutoComplete); $("#@Html.FieldIdFor(model => model.CompareProductsEnabled)").click(toggleCompareProducts); $("#@Html.FieldIdFor(model => model.EnableHtmlTextCollapser)").click(toggleHtmlTextCollapsedHeight); - $("#@Html.FieldIdFor(model => model.FilterEnabled)").click(toggleFilterEnabled); + + $("#@Html.FieldIdFor(model => model.FilterEnabled)").change(function () { + $('#ProductFilterTable').find('.product-filter-option').toggle($(this).is(':checked')); + }).trigger('change'); toggleShowCategoryProductNumberIncludingSubcategories(); toggleEmailAFriend(); @@ -42,7 +46,7 @@ toggleProductSearchAutoComplete(); toggleCompareProducts(); toggleHtmlTextCollapsedHeight(); - toggleFilterEnabled(); + toggleManufacturersOnHomepage(); }); function toggleShowCategoryProductNumberIncludingSubcategories() { @@ -90,6 +94,17 @@ } } + function toggleManufacturersOnHomepage() { + if ($('#@Html.FieldIdFor(model => model.ShowManufacturersOnHomepage)').is(':checked')) { + $('#pnlShowManufacturerPictures').show(); + $('#pnlManufacturersBlockItemsToDisplay').show(); + } + else { + $('#pnlShowManufacturerPictures').hide(); + $('#pnlManufacturersBlockItemsToDisplay').hide(); + } + } + function toggleProductsAlsoPurchasedNumber() { if ($('#@Html.FieldIdFor(model => model.ProductsAlsoPurchasedEnabled)').is(':checked')) { $('#pnlProductsAlsoPurchasedNumber').show(); @@ -117,10 +132,12 @@ function toggleProductSearchAutoComplete() { if ($('#@Html.FieldIdFor(model => model.ProductSearchAutoCompleteEnabled)').is(':checked')) { - $('#pnlProductSearchAutoCompleteNumberOfProducts').show(); + $('#pnlProductSearchAutoCompleteNumberOfProducts').show(); + $('#pnlShowProductImagesInSearchAutoComplete').show(); } else { - $('#pnlProductSearchAutoCompleteNumberOfProducts').hide(); + $('#pnlProductSearchAutoCompleteNumberOfProducts').hide(); + $('#pnlShowProductImagesInSearchAutoComplete').hide(); } } @@ -142,16 +159,6 @@ $('#pnlHtmlTextCollapsedHeight').hide(); } } - - function toggleFilterEnabled() { - if ($('#@Html.FieldIdFor(model => model.FilterEnabled)').is(':checked')) { - $('#pnlMaxFilterItemsToDisplay').show(); - $('#pnlExpandAllFilterCriteria').show(); - } else { - $('#pnlMaxFilterItemsToDisplay').hide(); - $('#pnlExpandAllFilterCriteria').hide(); - } - } @Html.Action("StoreScopeConfiguration", "Setting") @@ -161,8 +168,8 @@ { 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.UserSettings").Text).Content(@TabUserSettings()); 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()); })) @@ -242,8 +249,7 @@ @Html.ValidationMessageFor(model => model.IgnoreFeaturedProducts) - - + @Html.SmartLabelFor(model => model.CompareProductsEnabled) @@ -288,6 +294,62 @@ @Html.ValidationMessageFor(model => model.NumberOfBestsellersOnHomepage) + + + + @Html.SmartLabelFor(model => model.ShowManufacturersOnHomepage) + + + @Html.SettingEditorFor(model => model.ShowManufacturersOnHomepage) + @Html.ValidationMessageFor(model => model.ShowManufacturersOnHomepage) + + + + + @Html.SmartLabelFor(model => model.ManufacturersBlockItemsToDisplay) + + + @Html.SettingEditorFor(model => model.ManufacturersBlockItemsToDisplay) + @Html.ValidationMessageFor(model => model.ManufacturersBlockItemsToDisplay) + + + + + @Html.SmartLabelFor(model => model.ShowManufacturerPictures) + + + @Html.SettingEditorFor(model => model.ShowManufacturerPictures) + @Html.ValidationMessageFor(model => model.ShowManufacturerPictures) + + + + + @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) @@ -320,236 +382,289 @@ @helper TabProductListSettings() { -
    - @T("Common.Navigation") - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
    - @Html.SmartLabelFor(model => model.ShowProductsFromSubcategories) - - @Html.SettingEditorFor(model => model.ShowProductsFromSubcategories) - @Html.ValidationMessageFor(model => model.ShowProductsFromSubcategories) -
    - @Html.SmartLabelFor(model => model.IncludeFeaturedProductsInNormalLists) - - @Html.SettingEditorFor(model => model.IncludeFeaturedProductsInNormalLists) - @Html.ValidationMessageFor(model => model.IncludeFeaturedProductsInNormalLists) -
    - @Html.SmartLabelFor(model => model.ShowCategoryProductNumber) - - @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.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.SettingEditorFor(model => model.ExpandAllFilterCriteria) - @Html.ValidationMessageFor(model => model.ExpandAllFilterCriteria) -
    - @Html.SmartLabelFor(model => model.ShowSubcategoriesAboveProductLists) - - @Html.SettingEditorFor(model => model.ShowSubcategoriesAboveProductLists) - @Html.ValidationMessageFor(model => model.ShowSubcategoriesAboveProductLists) -
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    @T("Common.Navigation")
    +
    +
    + @Html.SmartLabelFor(model => model.ShowProductsFromSubcategories) + + @Html.SettingEditorFor(model => model.ShowProductsFromSubcategories) + @Html.ValidationMessageFor(model => model.ShowProductsFromSubcategories) +
    + @Html.SmartLabelFor(model => model.IncludeFeaturedProductsInNormalLists) + + @Html.SettingEditorFor(model => model.IncludeFeaturedProductsInNormalLists) + @Html.ValidationMessageFor(model => model.IncludeFeaturedProductsInNormalLists) +
    + @Html.SmartLabelFor(model => model.ShowCategoryProductNumber) + + @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.SmartLabelFor(model => model.SubCategoryDisplayType) + + @Html.SettingOverrideCheckbox(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.SettingEditorFor(model => model.ExpandAllFilterCriteria) + @Html.ValidationMessageFor(model => model.ExpandAllFilterCriteria) +
    + @Html.SmartLabelFor(model => model.SortFilterResultsByMatches) + + @Html.SettingEditorFor(model => model.SortFilterResultsByMatches) + @Html.ValidationMessageFor(model => model.SortFilterResultsByMatches) +
    -
    - @T("Common.List") - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.AllowProductSorting) - - @Html.SettingEditorFor(model => model.AllowProductSorting) - @Html.ValidationMessageFor(model => model.AllowProductSorting) -
    - @Html.SmartLabelFor(model => model.DefaultViewMode) - - @Html.SettingOverrideCheckbox(model => Model.DefaultViewMode) - @Html.DropDownListFor(model => model.DefaultViewMode, Model.AvailableDefaultViewModes) - @Html.ValidationMessageFor(model => model.DefaultViewMode) -
    - @Html.SmartLabelFor(model => model.AllowProductViewModeChanging) - - @Html.SettingEditorFor(model => model.AllowProductViewModeChanging) - @Html.ValidationMessageFor(model => model.AllowProductViewModeChanging) -
    - @Html.SmartLabelFor(model => model.DefaultPageSizeOptions) - - @Html.SettingEditorFor(model => model.DefaultPageSizeOptions) - @Html.ValidationMessageFor(model => model.DefaultPageSizeOptions) -
    -
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    @T("Common.List")
    +
    +
    + @Html.SmartLabelFor(model => model.AllowProductSorting) + + @Html.SettingEditorFor(model => model.AllowProductSorting) + @Html.ValidationMessageFor(model => model.AllowProductSorting) +
    + @Html.SmartLabelFor(model => model.DefaultViewMode) + + @Html.SettingOverrideCheckbox(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.SettingEditorFor(model => model.DefaultPageSizeOptions) + @Html.ValidationMessageFor(model => model.DefaultPageSizeOptions) +
    + @Html.SmartLabelFor(model => model.PriceDisplayType) + + @Html.SettingOverrideCheckbox(model => Model.PriceDisplayType) + @Html.DropDownListFor(model => model.PriceDisplayType, Model.AvailablePriceDisplayTypes) + @Html.ValidationMessageFor(model => model.PriceDisplayType) +
    -
    - @T("Admin.Catalog.Products") - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.ShowDeliveryTimesInProductLists) - - @Html.SettingEditorFor(model => model.ShowDeliveryTimesInProductLists) - @Html.ValidationMessageFor(model => model.ShowDeliveryTimesInProductLists) -
    - @Html.SmartLabelFor(model => model.ShowBasePriceInProductLists) - - @Html.SettingEditorFor(model => model.ShowBasePriceInProductLists) - @Html.ValidationMessageFor(model => model.ShowBasePriceInProductLists) -
    - @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) - @Html.ValidationMessageFor(model => model.LabelAsNewForMaxDays) -
    -
    + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    @T("Admin.Catalog.Products")
    +
    +
    + @Html.SmartLabelFor(model => model.ShowDeliveryTimesInProductLists) + + @Html.SettingEditorFor(model => model.ShowDeliveryTimesInProductLists) + @Html.ValidationMessageFor(model => model.ShowDeliveryTimesInProductLists) +
    + @Html.SmartLabelFor(model => model.ShowBasePriceInProductLists) + + @Html.SettingEditorFor(model => model.ShowBasePriceInProductLists) + @Html.ValidationMessageFor(model => model.ShowBasePriceInProductLists) +
    + @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) + @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("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) +
    } @helper TabUserSettings() { - +
    - - + @@ -169,8 +168,8 @@ @Html.ValidationMessageFor(model => model.NotifyAboutPrivateMessages) - - + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/GeneralCommon.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/GeneralCommon.cshtml index 76180000af..5162311aec 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/GeneralCommon.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/GeneralCommon.cshtml @@ -27,8 +27,8 @@ $("#@Html.FieldIdFor(model => model.StoreInformationSettings.StoreClosed)").click(toggleStoreClosed); $("#@Html.FieldIdFor(model => model.CaptchaSettings.Enabled)").change(function () { $('#SecuritySettingTable').find('.captcha-setting').toggle($(this).is(':checked')); - }).trigger('change'); - $("#@Html.FieldIdFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled)").click(toggleSeoLanguageUrls); + }).trigger('change'); + $("#@Html.FieldIdFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled)").click(toggleSeoLanguageUrls); toggleStoreClosed(); toggleSeoLanguageUrls(); @@ -36,6 +36,17 @@ $("#@Html.FieldIdFor(model => model.SocialSettings.ShowSocialLinksInFooter)").change(function () { $('#SocialLinkTable').find('.social-link').toggle($(this).is(':checked')); }).trigger('change'); + + // test creation of SEO names + $('#TestSeoNameCreationButton').click(function () { + $(this).closest('form').doAjax({ + type: 'POST', + url: '@Url.Action("TestSeoNameCreation")', + callbackSuccess: function (resp) { + $('#TestSeoNameCreationResult').text(resp); + } + }); + }); }); function toggleStoreClosed() { @@ -148,15 +159,24 @@ @Html.ValidationMessageFor(model => model.SeoSettings.DefaultMetaDescription) - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @Html.SmartLabelFor(model => model.ShowProductReviewsInProductLists) @@ -627,7 +742,7 @@ @helper TabProductDetailSettings() { - +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + +
    @Html.SmartLabelFor(model => model.RecentlyViewedProductsEnabled) @@ -700,6 +815,15 @@ @Html.ValidationMessageFor(model => model.DisplayAllImagesNumber)
    + @Html.SmartLabelFor(model => model.ShowManufacturerPicturesInProductDetail) + + @Html.SettingEditorFor(model => model.ShowManufacturerPicturesInProductDetail) + @Html.ValidationMessageFor(model => model.ShowManufacturerPicturesInProductDetail) +
    @Html.SmartLabelFor(model => model.ShowDeliveryTimesInProductDetail) @@ -768,8 +892,7 @@ } @helper TabProductSearchSettings() { - - +
    + + + + + + + + + + + + + + + - - - -
    @Html.SmartLabelFor(model => model.SearchPageProductsPerPage) @@ -797,6 +920,29 @@ @Html.ValidationMessageFor(model => model.ProductSearchPageSizeOptions)
    + @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) @@ -806,6 +952,15 @@ @Html.ValidationMessageFor(model => model.ProductSearchAutoCompleteEnabled)
    + @Html.SmartLabelFor(model => model.ShowProductImagesInSearchAutoComplete) + + @Html.SettingEditorFor(model => model.ShowProductImagesInSearchAutoComplete) + @Html.ValidationMessageFor(model => model.ShowProductImagesInSearchAutoComplete) +
    @Html.SmartLabelFor(model => model.ProductSearchAutoCompleteNumberOfProducts) @@ -815,15 +970,6 @@ @Html.ValidationMessageFor(model => model.ProductSearchAutoCompleteNumberOfProducts)
    - @Html.SmartLabelFor(model => model.SuppressSkuSearch) - - @Html.SettingEditorFor(model => model.SuppressSkuSearch) - @Html.ValidationMessageFor(model => model.SuppressSkuSearch) -
    } } \ 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 4fcee50d11..ccb0eb2876 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml @@ -3,7 +3,6 @@ @using SmartStore.Core.Domain.Customers; @using SmartStore.Core.Domain.Security; @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.CustomerUser").Text; } @using (Html.BeginForm()) @@ -44,6 +43,10 @@ toggleAvatar(); toggleUsername(); + + $('#@Html.FieldIdFor(model => model.CustomerSettings.CustomerNumberMethod)').change(function () { + $('#pnlCustomerNumberVisibility').toggle($(this).val() !== '@((int)CustomerNumberMethod.Disabled)'); + }).trigger('change'); }); function toggleAvatar() { @@ -95,6 +98,47 @@ @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.ValidationMessageFor(model => model.CustomerSettings.CustomerNameFormat) +
    + @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) + + @Html.SettingEditorFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) + @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) +
    + @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNumberMethod) + + @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.CustomerNumberMethod) + @Html.DropDownListFor(model => model.CustomerSettings.CustomerNumberMethod, Model.CustomerSettings.AvailableCustomerNumberMethods) + @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNumberMethod) +
    + @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNumberVisibility) + + @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.CustomerNumberVisibility) + @Html.DropDownListFor(model => model.CustomerSettings.CustomerNumberVisibility, Model.CustomerSettings.AvailableCustomerNumberVisibilities) + @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNumberVisibility) +
    @Html.SmartLabelFor(model => model.CustomerSettings.UserRegistrationType) @@ -105,6 +149,26 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.UserRegistrationType)
    + @Html.SmartLabelFor(model => model.CustomerSettings.DefaultPasswordFormat) + + @Html.DropDownListFor(model => model.CustomerSettings.DefaultPasswordFormat, ((PasswordFormat)Model.CustomerSettings.DefaultPasswordFormat).ToSelectList()) + @Html.ValidationMessageFor(model => model.CustomerSettings.DefaultPasswordFormat) +
    + @Html.SmartLabelFor(model => model.CustomerSettings.RegisterCustomerRoleId) + + @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.RegisterCustomerRoleId) + @Html.DropDownListFor(model => model.CustomerSettings.RegisterCustomerRoleId, Model.CustomerSettings.AvailableRegisterCustomerRoles, T("Common.Unspecified")) + @Html.ValidationMessageFor(model => model.CustomerSettings.RegisterCustomerRoleId) +
    @Html.SmartLabelFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars) @@ -177,25 +241,6 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.HideBackInStockSubscriptionsTab)
    - @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNameFormat) - - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.UserRegistrationType) - @Html.DropDownListFor(model => model.CustomerSettings.CustomerNameFormat, ((CustomerNameFormat)Model.CustomerSettings.CustomerNameFormat).ToSelectList()) - @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNameFormat) -
    - @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) - - @Html.SettingEditorFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) - @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) -
    @Html.SmartLabelFor(model => model.CustomerSettings.HideNewsletterBlock) @@ -214,15 +259,15 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.StoreLastVisitedPage)
    - @Html.SmartLabelFor(model => model.CustomerSettings.DefaultPasswordFormat) - - @Html.DropDownListFor(model => model.CustomerSettings.DefaultPasswordFormat, ((PasswordFormat)Model.CustomerSettings.DefaultPasswordFormat).ToSelectList()) - @Html.ValidationMessageFor(model => model.CustomerSettings.DefaultPasswordFormat) -
    + @Html.SmartLabelFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) + + @Html.SettingEditorFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) + @Html.ValidationMessageFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) +
    } @helper TabCustomerFormFields() diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml new file mode 100644 index 0000000000..88bcdab917 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml @@ -0,0 +1,56 @@ +@model DataExchangeSettingsModel +@{ + ViewBag.Title = T("Admin.Common.DataExchange").Text; +} +@using (Html.BeginForm()) +{ +
    +
    + + @T("Admin.Common.DataExchange") +
    +
    + +
    +
    + + @Html.Action("StoreScopeConfiguration", "Setting") + @Html.ValidationSummary(false) + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.MaxFileNameLength) + + @Html.SettingEditorFor(model => model.MaxFileNameLength) + @Html.ValidationMessageFor(model => model.MaxFileNameLength) +
    +
    +
    @T("Common.Import")
    +
    +
    + @Html.SmartLabelFor(model => model.ImageImportFolder) + + @Html.SettingEditorFor(model => model.ImageImportFolder) + @Html.ValidationMessageFor(model => model.ImageImportFolder) +
    + @Html.SmartLabelFor(model => model.ImageDownloadTimeout) + + @Html.SettingEditorFor(model => model.ImageDownloadTimeout) + @Html.ValidationMessageFor(model => model.ImageDownloadTimeout) +
    +} diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml index b89e7f267a..960758edfd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Forum.cshtml @@ -1,7 +1,6 @@ @model ForumSettingsModel @using Telerik.Web.Mvc.UI; @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.Forums").Text; } @using (Html.BeginForm()) @@ -136,8 +135,8 @@ @Html.ValidationMessageFor(model => model.SignaturesEnabled)
    +

    +

    - @Html.SmartLabelFor(model => model.SeoSettings.ConvertNonWesternChars) - - @Html.SettingEditorFor(model => model.SeoSettings.ConvertNonWesternChars) - @Html.ValidationMessageFor(model => model.SeoSettings.ConvertNonWesternChars) -
    + @Html.SmartLabelFor(model => model.SeoSettings.MetaRobotsContent) + + @Html.SettingOverrideCheckbox(model => model.SeoSettings.MetaRobotsContent) + @Html.DropDownListFor(model => model.SeoSettings.MetaRobotsContent, new List + { + new SelectListItem { Text = "index", Value = "index" }, + new SelectListItem { Text = "noindex", Value = "noindex" }, + new SelectListItem { Text = "index, follow", Value = "index, follow" }, + new SelectListItem { Text = "index, nofollow", Value = "index, nofollow" }, + new SelectListItem { Text = "noindex, follow", Value = "noindex, follow" }, + new SelectListItem { Text = "noindex, nofollow", Value = "noindex, nofollow" } + }, T("Common.Unspecified")) + @Html.ValidationMessageFor(model => model.SeoSettings.MetaRobotsContent) +
    @Html.SmartLabelFor(model => model.SeoSettings.CanonicalUrlsEnabled) @@ -176,6 +196,68 @@ @Html.ValidationMessageFor(model => model.SeoSettings.CanonicalHostNameRule)
    + @Html.SmartLabelFor(model => model.SeoSettings.ExtraRobotsDisallows) + + @Html.TextAreaFor(model => model.SeoSettings.ExtraRobotsDisallows, new { @class = "input-large", style = "height:250px" }) + @Html.ValidationMessageFor(model => model.SeoSettings.ExtraRobotsDisallows) +
    +
    +
    @T("Admin.System.SeNames")
    +
    +
    + @Html.SmartLabelFor(model => model.SeoSettings.ConvertNonWesternChars) + + @Html.SettingEditorFor(model => model.SeoSettings.ConvertNonWesternChars) + @Html.ValidationMessageFor(model => model.SeoSettings.ConvertNonWesternChars) +
    + @Html.SmartLabelFor(model => model.SeoSettings.AllowUnicodeCharsInUrls) + + @Html.SettingEditorFor(model => model.SeoSettings.AllowUnicodeCharsInUrls) + @Html.ValidationMessageFor(model => model.SeoSettings.AllowUnicodeCharsInUrls) +
    + @Html.SmartLabelFor(model => model.SeoSettings.SeoNameCharConversion) + + @Html.TextAreaFor(model => model.SeoSettings.SeoNameCharConversion, new { @class = "input-large", style = "height:250px" }) + @Html.ValidationMessageFor(model => model.SeoSettings.SeoNameCharConversion) +
    + @Html.SmartLabelFor(model => model.SeoSettings.TestSeoNameCreation) + + @Html.EditorFor(model => model.SeoSettings.TestSeoNameCreation) + + +
    +   + +
    } @helper TabSecuritySettings() @@ -355,10 +437,33 @@ @Html.SmartLabelFor(model => model.PdfSettings.LogoPictureId) - @Html.SettingEditorFor(model => model.PdfSettings.LogoPictureId, "#pdf-logo-picture") + @Html.SettingEditorFor(model => model.PdfSettings.LogoPictureId, "#pdf-logo-picture", new { transientUpload = true }) @Html.ValidationMessageFor(model => model.PdfSettings.LogoPictureId) + + +
    + + + + + @Html.SmartLabelFor(model => model.PdfSettings.AttachOrderPdfToOrderPlacedEmail) + + + @Html.SettingEditorFor(model => model.PdfSettings.AttachOrderPdfToOrderPlacedEmail) + @Html.ValidationMessageFor(model => model.PdfSettings.AttachOrderPdfToOrderPlacedEmail) + + + + + @Html.SmartLabelFor(model => model.PdfSettings.AttachOrderPdfToOrderCompletedEmail) + + + @Html.SettingEditorFor(model => model.PdfSettings.AttachOrderPdfToOrderCompletedEmail) + @Html.ValidationMessageFor(model => model.PdfSettings.AttachOrderPdfToOrderCompletedEmail) + + } @helper TabLocalizationSettings() @@ -433,9 +538,9 @@ - -
    - + +
    + @@ -551,9 +656,9 @@ - -
    - + +
    + @@ -582,9 +687,6 @@ @Html.ValidationMessageFor(model => model.CompanyInformationSettings.TaxNumber) - - - } @@ -629,9 +731,9 @@ - -
    - + +
    + @@ -670,9 +772,9 @@ - -
    - + +
    + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml index 566f49c981..fbd72c4cd3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml @@ -1,7 +1,6 @@ @model MediaSettingsModel @using Telerik.Web.Mvc.UI; @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.Media").Text; } @using (Html.BeginForm()) @@ -62,6 +61,15 @@ @Html.ValidationMessageFor(model => model.ProductThumbPictureSizeOnProductDetailsPage) + + + @Html.SmartLabelFor(model => model.MessageProductThumbPictureSize) + + + @Html.SettingEditorFor(model => model.MessageProductThumbPictureSize) + @Html.ValidationMessageFor(model => model.MessageProductThumbPictureSize) + + @Html.SmartLabelFor(model => model.ProductDetailsPictureSize) @@ -162,38 +170,31 @@ @Html.ValidationMessageFor(model => model.MaximumImageSize) - - -
    - @T("Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase") -
    -
    - @T("Admin.Configuration.Settings.Media.MovePicturesNote") -
    + + +
    +
    @T("Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase")
    +
    +
    + @T("Admin.Configuration.Settings.Media.MovePicturesNote") +
    + + @T(Model.PicturesStoredIntoDatabase ? "Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase.Database" : "Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase.FileSystem") + - - @if (Model.PicturesStoredIntoDatabase) - { - @T("Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase.Database") - } - else - { - @T("Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase.FileSystem") - } - - - -
    -
    + + + + } \ 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 cf0d619047..cd65a40c30 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/News.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/News.cshtml @@ -73,6 +73,15 @@ @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) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml index 3c29b263a1..c10c4c8f52 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml @@ -105,6 +105,15 @@ @Html.ValidationMessageFor(model => model.GiftCards_Deactivated_OrderStatusId) + + + @Html.SmartLabelFor(model => model.OrderListPageSize) + + + @Html.EditorFor(model => model.OrderListPageSize) + @Html.ValidationMessageFor(model => model.OrderListPageSize) + + @if (Model.OrderIdent.HasValue) { @@ -116,6 +125,18 @@ } + @if (Model.StoreCount > 1) + { + + + @Html.SmartLabelFor(model => model.DisplayOrdersOfAllStores) + + + @Html.SettingEditorFor(model => model.DisplayOrdersOfAllStores) + @Html.ValidationMessageFor(model => model.DisplayOrdersOfAllStores) + + + } } @@ -158,7 +179,7 @@ @Html.SmartLabelFor(model => model.Locales[item].ReturnRequestReasons) - @Html.TextBoxFor(model => Model.Locales[item].ReturnRequestReasons, new { style = "min-width: 800px;" }) + @Html.TextBoxFor(model => Model.Locales[item].ReturnRequestReasons, new { @class = "input-xlarge" }) @Html.ValidationMessageFor(model => model.Locales[item].ReturnRequestReasons) @@ -167,7 +188,7 @@ @Html.SmartLabelFor(model => model.Locales[item].ReturnRequestActions) - @Html.TextBoxFor(model => model.Locales[item].ReturnRequestActions, new { style = "min-width: 800px;" }) + @Html.TextBoxFor(model => model.Locales[item].ReturnRequestActions, new { @class = "input-xlarge" }) @Html.ValidationMessageFor(model => model.Locales[item].ReturnRequestActions) @@ -184,7 +205,7 @@ @Html.SmartLabelFor(model => model.ReturnRequestReasons) - @Html.TextBoxFor(model => model.ReturnRequestReasons, new { style = "min-width: 800px;" }) + @Html.TextBoxFor(model => model.ReturnRequestReasons, new { @class = "input-xlarge" }) @Html.ValidationMessageFor(model => model.ReturnRequestReasons) @@ -193,7 +214,7 @@ @Html.SmartLabelFor(model => model.ReturnRequestActions) - @Html.TextBoxFor(x => x.ReturnRequestActions, new { style = "min-width: 800px;" }) + @Html.TextBoxFor(x => x.ReturnRequestActions, new { @class = "input-xlarge" }) @Html.ValidationMessageFor(model => model.ReturnRequestActions) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/RewardPoints.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/RewardPoints.cshtml index c6bf2240dd..2db648327b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/RewardPoints.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/RewardPoints.cshtml @@ -2,7 +2,6 @@ @using Telerik.Web.Mvc.UI; @using SmartStore.Core.Domain.Orders; @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.RewardPoints").Text; } @using (Html.BeginForm()) @@ -16,8 +15,9 @@
    - @Html.ValidationSummary(false) + @Html.Action("StoreScopeConfiguration", "Setting") + @Html.ValidationSummary(false)
    @@ -46,69 +46,81 @@ @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.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) -
    -
    + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    @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) +
    } \ 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 56a5139003..65f9355676 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Shipping.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Shipping.cshtml @@ -1,6 +1,5 @@ @model ShippingSettingsModel @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.Shipping").Text; } @using (Html.BeginForm()) @@ -82,14 +81,14 @@ @Html.ValidationMessageFor(model => model.DisplayShipmentEventsToCustomers) - - - @Html.LabelFor(model => model.ShippingOriginAddress, new { style="font-weight: bold" }) - + + + @Html.SmartLabelFor(model => model.ShippingOriginAddress) + @Html.SettingOverrideCheckbox(model => Model.ShippingOriginAddress, "#pnlShippingOriginAddress") - + @Html.EditorFor(model => model.ShippingOriginAddress, "Address") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml index 2a4d51b0b4..8c1db1d995 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml @@ -1,6 +1,5 @@ @model ShoppingCartSettingsModel @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.ShoppingCart").Text; } @using (Html.BeginForm()) @@ -22,6 +21,12 @@ toggleMiniShoppingCartEnabled(); toggleEmailWishlistEnabled(); + + // toggle third party email hand over label text + $('#@Html.FieldIdFor(model => model.ThirdPartyEmailHandOver)').change(function () { + $('#ThirdPartyEmailHandOverTextLocalized').toggle($(this).val() !== '0'); + }).trigger('change'); + }); function toggleMiniShoppingCartEnabled() { @@ -52,8 +57,8 @@ @(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.WishlistSettings").Text).Content(@TabWishlistSettings()); x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.Checkout").Text).Content(@TabCheckoutSettings()); + x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.WishlistSettings").Text).Content(@TabWishlistSettings()); })) } @@ -142,8 +147,8 @@ - - + +
    @@ -176,10 +181,10 @@ - - -
    - + + +
    + @@ -293,6 +298,32 @@ @helper TabCheckoutSettings() { + + + + + + + + + + + - - - - + + + + + + + +
    +
    +
    @T("Admin.Configuration.Settings.ShoppingCart.OrderConfirmationPage")
    +
    +
    + @Html.SmartLabelFor(model => model.ShowCommentBox) + + @Html.SettingEditorFor(model => model.ShowCommentBox) + @Html.ValidationMessageFor(model => model.ShowCommentBox) +
    + @Html.SmartLabelFor(model => model.NewsLetterSubscription) + + @Html.SettingOverrideCheckbox(model => Model.NewsLetterSubscription) + @Html.DropDownListFor(model => model.NewsLetterSubscription, Model.AvailableNewsLetterSubscriptions) + @Html.ValidationMessageFor(model => model.NewsLetterSubscription) +
    @Html.SmartLabelFor(model => model.ShowConfirmOrderLegalHint) @@ -302,14 +333,59 @@ @Html.ValidationMessageFor(model => model.ShowConfirmOrderLegalHint)
    - @Html.SmartLabelFor(model => model.ShowCommentBox) - - @Html.SettingEditorFor(model => model.ShowCommentBox) - @Html.ValidationMessageFor(model => model.ShowCommentBox) -
    + @Html.SmartLabelFor(model => model.ShowEsdRevocationWaiverBox) + + @Html.SettingEditorFor(model => model.ShowEsdRevocationWaiverBox) + @Html.ValidationMessageFor(model => model.ShowEsdRevocationWaiverBox) +
    + @Html.SmartLabelFor(model => model.ThirdPartyEmailHandOver) + + @Html.SettingOverrideCheckbox(model => Model.ThirdPartyEmailHandOver) + @Html.DropDownListFor(model => model.ThirdPartyEmailHandOver, Model.AvailableThirdPartyEmailHandOver) + @Html.ValidationMessageFor(model => model.ThirdPartyEmailHandOver) +
    + +
    +

    + + @(Html.LocalizedEditor("setting-shopping-cart-localized", + @ + + + + + + + +
    + @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) +
    + @Html.HiddenFor(model => model.Locales[item].LanguageId) +
    + , + @ + + + + +
    + @Html.SmartLabelFor(model => model.ThirdPartyEmailHandOverLabel) + + @Html.TextAreaFor(model => model.ThirdPartyEmailHandOverLabel, new { @class = "input-large" }) + @Html.ValidationMessageFor(model => model.ThirdPartyEmailHandOverLabel) +
    + )) +
    } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml index bb8a6a430e..7f2e872994 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml @@ -1,7 +1,6 @@ @model TaxSettingsModel @using Telerik.Web.Mvc.UI; @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.Tax").Text; } @using (Html.BeginForm()) @@ -59,12 +58,14 @@ $('#pnlEuVatAllowVATExemption').show(); $('#pnlEuVatUseWebService').show(); $('#pnlEuVatEmailAdminWhenNewVATSubmitted').show(); + $('#pnlVatRequired').show(); } else { $('#pnlEuVatShopCountry').hide(); $('#pnlEuVatAllowVATExemption').hide(); $('#pnlEuVatUseWebService').hide(); $('#pnlEuVatEmailAdminWhenNewVATSubmitted').hide(); + $('#pnlVatRequired').hide(); } } @@ -148,11 +149,6 @@ @Html.ValidationMessageFor(model => model.HideTaxInOrderSummary) - - -
    - - @Html.SmartLabelFor(model => model.ShowLegalHintsInProductList) @@ -180,8 +176,8 @@ @Html.ValidationMessageFor(model => model.ShowLegalHintsInFooter) - - + +
    @@ -195,21 +191,21 @@ @Html.ValidationMessageFor(model => model.TaxBasedOn) - + - @Html.LabelFor(model => model.DefaultTaxAddress, new { style="font-weight: bold" }) + @Html.SmartLabelFor(model => model.DefaultTaxAddress) @Html.SettingOverrideCheckbox(model => Model.DefaultTaxAddress, "#pnlDefaultTaxAddress") - + @Html.EditorFor(x => x.DefaultTaxAddress, "Address") - - + +
    @@ -241,8 +237,8 @@ @Html.ValidationMessageFor(model => model.ShippingTaxClassId) - - + +
    @@ -274,8 +270,8 @@ @Html.ValidationMessageFor(model => model.PaymentMethodAdditionalFeeTaxClassId) - - + +
    @@ -325,5 +321,14 @@ @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/CsvConfiguration.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/CsvConfiguration.cshtml new file mode 100644 index 0000000000..9a1a8f1622 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/CsvConfiguration.cshtml @@ -0,0 +1,68 @@ +@using SmartStore.Admin.Models.DataExchange; +@model CsvConfigurationModel + + @if (ViewData["ShowGroupCaption"] != null && (bool)ViewData["ShowGroupCaption"]) + { + + + + } + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    @T("Admin.Common.CsvConfiguration")
    +
    +
    + @Html.SmartLabelFor(x => x.QuoteAllFields) + + @Html.EditorFor(x => x.QuoteAllFields) + @Html.ValidationMessageFor(x => x.QuoteAllFields) +
    + @Html.SmartLabelFor(x => x.TrimValues) + + @Html.EditorFor(x => x.TrimValues) + @Html.ValidationMessageFor(x => x.TrimValues) +
    + @Html.SmartLabelFor(x => x.SupportsMultiline) + + @Html.EditorFor(x => x.SupportsMultiline) + @Html.ValidationMessageFor(x => x.SupportsMultiline) +
    + @Html.SmartLabelFor(x => x.Delimiter) + + @Html.TextBoxFor(x => x.Delimiter, new { style = "width: 30px;", maxlength = "2" }) + @Html.ValidationMessageFor(x => x.Delimiter) +
    + @Html.SmartLabelFor(x => x.Quote) + + @Html.TextBoxFor(x => x.Quote, new { style = "width: 30px;", maxlength = "2" }) + @Html.ValidationMessageFor(x => x.Quote) +
    + @Html.SmartLabelFor(x => x.Escape) + + @Html.TextBoxFor(x => x.Escape, new { style = "width: 30px;", maxlength = "2" }) + @Html.ValidationMessageFor(x => x.Escape) +
    \ 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 index f88dd27dae..2ecbda038a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Delete.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Delete.cshtml @@ -1,4 +1,4 @@ -@model SmartStore.Web.Framework.Mvc.DeleteConfirmationModel +@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;" })) {
    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 d530182896..04de1aee37 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml @@ -1,186 +1,149 @@ -@model int +@model int? @using SmartStore.Core; @using SmartStore.Web.Framework.UI; @using SmartStore.Utilities; + +@functions { + bool? _minimalMode = null; + string _fieldName = null; + + private bool MinimalMode + { + get + { + if (!_minimalMode.HasValue) + { + _minimalMode = ViewData.ContainsKey("minimalMode") ? ViewData["minimalMode"].Convert() : false; + } + return _minimalMode.Value; + } + } + + private string FieldName + { + get + { + if (_fieldName == null) + { + _fieldName = ViewData.ContainsKey("fieldName") ? ViewData["fieldName"].Convert() : ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); + } + return _fieldName; + } + } +} + @{ - - //other variables - var randomNumber = CommonHelper.GenerateRandomInteger(); - var clientId = "download" + randomNumber; + var clientId = "download-editor-" + CommonHelper.GenerateRandomInteger(); var downloadService = EngineContext.Current.Resolve(); - var download = downloadService.GetDownloadById(Model); - + 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"); } - -
    @(download != null ? download.Filename : "")
    - -
    - - - - - - - - - - - - - - - - - -
    - - - checked="checked" - } - /> -
    - @T("Admin.Download.DownloadURL"): - - value="@(download.DownloadUrl)" - } - /> - - -
    - @T("Admin.Download.UploadFile"): - -
    - -
    - - @Html.HiddenFor(x => x, new { @class = "hidden"} ) - - - - - - @T("Common.Fileuploader.Upload") - - - - - - -
    - -
    -
    -
    - -
     
    -
    - -
    - - -
    - -
    - -
    -
    +
    + + + + + @if (!MinimalMode) + { + + } + else if (hasFile) + { + + + + } + + +
    + + +
    + + @if (hasFile) + { + + @download.Filename@download.Extension + + } + + + @if (hasFile) + { + + } + else + { + @T("Common.Fileuploader.Upload") + } + + + + + + +
    + +
    +
    +
    + +
     
    +
    +
    + + + @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/Picture.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Picture.cshtml index d60f7b8d8a..fc793a9bcb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Picture.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Picture.cshtml @@ -2,9 +2,23 @@ @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; + } + } +} + @{ - - //other variables var random = CommonHelper.GenerateRandomInteger(); var clientId = "picture" + random; var pictureService = EngineContext.Current.Resolve(); @@ -15,7 +29,7 @@ Html.AddCssFileParts("~/css/fileupload"); } -
    +
    @Html.HiddenFor(x => x, new { @class = "hidden"} ) @@ -59,7 +73,7 @@ elRemove = el.find('.remove'); $('#@clientId').fileupload({ - url: '@(Url.Content("~/Admin/Picture/AsyncUpload"))', + url: el.data('upload-url'), dataType: 'json', //acceptFileTypes: /^image\/(gif|jpeg|jpg|png)$/, acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml index f22d41d211..04db8e0e26 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml @@ -29,7 +29,7 @@ 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 bab1deb472..feba06977d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml @@ -11,7 +11,7 @@ } } - + - -
    - - - - - @foreach (var sm in Model.AvailableShippingMethods) - { - - } - - - - @foreach (var c in Model.AvailableCountries) - { - - - @foreach (var sm in Model.AvailableShippingMethods) - { - var restricted = Model.Restricted.ContainsKey(c.Id) && Model.Restricted[c.Id][sm.Id]; - - } - - } - -
    - @T("Admin.Configuration.Shipping.Restrictions.Country") - - - -
    - @c.Name - - -
    -
    - } - - - -} diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml index dbd129ab13..8d63b6e67b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml @@ -1,72 +1,130 @@ @model ShippingMethodModel @using Telerik.Web.Mvc.UI; + @Html.ValidationSummary(true) @Html.HiddenFor(model => model.Id) -@(Html.LocalizedEditor("shipping-method-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.TextAreaFor(model => model.Locales[item].Description) - @Html.ValidationMessageFor(model => model.Locales[item].Description) -
    - @Html.HiddenFor(model => model.Locales[item].LanguageId) -
    - , - @ - - - - - - - - -
    - @Html.SmartLabelFor(model => model.Name) - - @Html.EditorFor(model => model.Name) - @Html.ValidationMessageFor(model => model.Name) -
    - @Html.SmartLabelFor(model => model.Description) - - @Html.TextAreaFor(model => model.Description) - @Html.ValidationMessageFor(model => model.Description) -
    - )) - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.DisplayOrder) - - @Html.EditorFor(model => model.DisplayOrder) - @Html.ValidationMessageFor(model => model.DisplayOrder) -
    - @Html.SmartLabelFor(model => model.IgnoreCharges) - - @Html.EditorFor(model => model.IgnoreCharges) - @Html.ValidationMessageFor(model => model.IgnoreCharges) -
    + +@Html.SmartStore().TabStrip().Name("shipping-method-edit").Items(x => +{ + x.Add().Text(T("Admin.Common.General").Text).Content(TabGeneral()).Selected(true); + x.Add().Text(T("Admin.Common.Restrictions").Text).Content(TabRestrictions()); + + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "shipping-method-edit", this.Html, this.Model)); +}) + +@helper TabGeneral() +{ + @(Html.LocalizedEditor("shipping-method-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.SmartLabelFor(model => model.Name) + + @Html.EditorFor(model => model.Name) + @Html.ValidationMessageFor(model => model.Name) +
    + @Html.SmartLabelFor(model => model.Description) + + @Html.EditorFor(model => model.Description, Html.RichEditorFlavor()) + @Html.ValidationMessageFor(model => model.Description) +
    + )) + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.DisplayOrder) + + @Html.EditorFor(model => model.DisplayOrder) + @Html.ValidationMessageFor(model => model.DisplayOrder) +
    + @Html.SmartLabelFor(model => model.IgnoreCharges) + + @Html.EditorFor(model => model.IgnoreCharges) + @Html.ValidationMessageFor(model => model.IgnoreCharges) +
    +} + +@helper TabRestrictions() +{ + if (Model.Id == 0) + { +
    + @T("Admin.Configuration.Restriction.SaveBeforeEdit") +
    + } + else if (Model.FilterConfigurationUrls.Count == 0) + { +
    + @T("Admin.Configuration.Shipping.Methods.RestrictionNote") +
    + } + +
    +
    +
    +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentCarts.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentCarts.cshtml index 21e584867d..ed1ecbc83f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentCarts.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentCarts.cshtml @@ -63,6 +63,7 @@ ) .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("CurrentCarts", "ShoppingCart")) + .PreserveGridState() .EnableCustomBinding(true)) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentWishlists.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentWishlists.cshtml index 849f168f7b..1f401b3bf6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentWishlists.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentWishlists.cshtml @@ -10,7 +10,7 @@ {
    - + @T("Admin.CurrentWishlists")
    @@ -53,6 +53,7 @@ ) .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("CurrentWishlists", "ShoppingCart")) + .PreserveGridState() .EnableCustomBinding(true)) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/List.cshtml index b208dca9b3..966cc46457 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/List.cshtml @@ -3,34 +3,131 @@ ViewBag.Title = T("Admin.Catalog.Attributes.SpecificationAttributes").Text; } -
    -
    - @T("Admin.Catalog.Attributes.SpecificationAttributes") -
    - -
    - - - - - -
    - @(Html.Telerik().Grid() - .Name("specificationattributes-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.DisplayOrder) - .Width(200) - .Filterable(false) - .Centered(); - }) - .Filterable() - .Pageable(settings => settings.PageSize((int)ViewData["GridPageSize"]).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "SpecificationAttribute")) - .EnableCustomBinding(true)) -
    \ No newline at end of file +@using (Html.BeginForm()) +{ +
    +
    + @T("Admin.Catalog.Attributes.SpecificationAttributes") +
    +
    +  @T("Admin.Common.AddNew") + + +
    +
    + + + + + +
    + @(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)) +
    +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml index a20f817342..b3832779c4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml @@ -1,6 +1,5 @@ @using Telerik.Web.Mvc.UI @{ - //page title ViewBag.Title = T("Admin.Common.Stores").Text; }
    @@ -24,23 +23,19 @@ .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.Hosts); columns.Bound(x => x.SslEnabled) .Template(item => @Html.SymbolForBool(item.SslEnabled)) .ClientTemplate(@Html.SymbolForBool("SslEnabled")) - .Width(100) - .Centered(); + .Centered() + .Width(80); columns.Bound(x => x.DisplayOrder) - .Width(100) .Centered(); - columns.Bound(x => x.Id) - .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id })) - .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") - .Width(100) - .Centered() - .HeaderTemplate(T("Admin.Common.Edit").Text); }) .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 aac7dff4f0..4b3855f9ab 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Store/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Store/_CreateOrUpdate.cshtml @@ -27,7 +27,7 @@ @Html.SmartLabelFor(model => model.LogoPictureId) - @Html.EditorFor(model => model.LogoPictureId, "#store-logo-picture") + @Html.EditorFor(model => model.LogoPictureId, "#store-logo-picture", new { transientUpload = true }) @Html.ValidationMessageFor(model => model.LogoPictureId) @@ -49,6 +49,15 @@ @Html.ValidationMessageFor(model => model.Url) + + + @Html.SmartLabelFor(model => model.Hosts) + + + @Html.TextBoxFor(model => model.Hosts, new { @class = "input-large" }) + @Html.ValidationMessageFor(model => model.Hosts) + + @Html.SmartLabelFor(model => model.ContentDeliveryNetwork) @@ -58,6 +67,24 @@ @Html.ValidationMessageFor(model => model.ContentDeliveryNetwork) + + + @Html.SmartLabelFor(model => model.PrimaryStoreCurrencyId) + + + @Html.DropDownListFor(model => model.PrimaryStoreCurrencyId, Model.AvailableCurrencies) + @Html.ValidationMessageFor(model => model.PrimaryStoreCurrencyId) + + + + + @Html.SmartLabelFor(model => model.PrimaryExchangeRateCurrencyId) + + + @Html.DropDownListFor(model => model.PrimaryExchangeRateCurrencyId, Model.AvailableCurrencies) + @Html.ValidationMessageFor(model => model.PrimaryExchangeRateCurrencyId) + + @Html.SmartLabelFor(model => model.SslEnabled) @@ -76,15 +103,6 @@ @Html.ValidationMessageFor(model => model.SecureUrl) - - - @Html.SmartLabelFor(model => model.Hosts) - - - @Html.TextBoxFor(model => model.Hosts, new { @class = "input-large" }) - @Html.ValidationMessageFor(model => model.Hosts) - - @Html.SmartLabelFor(model => model.HtmlBodyId) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml index a84848f8b0..627eb3ba88 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml @@ -27,6 +27,8 @@ @ViewBag.Title @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List", new { storeId = Model.StoreId })
    + @Html.Widget("admin_button_toolbar_before") +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Theme/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Theme/List.cshtml index 24ccab4101..15e975c4ce 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Theme/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Theme/List.cshtml @@ -5,9 +5,9 @@ @using SmartStore.Core.Themes; @{ - //page title ViewBag.Title = T("Admin.Configuration.Themes").Text; } + @using (Html.BeginForm()) {
    @@ -16,6 +16,8 @@ @T("Admin.Configuration.Themes")
    + @Html.Widget("admin_button_toolbar_before") +
    @@ -144,13 +148,13 @@ @if (!isBroken) {
    - if (!theme.IsMobileTheme) + @if (!theme.IsMobileTheme) { } - if (theme.IsConfigurable) + @if (theme.IsConfigurable) { @@ -177,21 +181,45 @@ @helper ThemeSettingsTab() { - + + @Html.HiddenFor(x => x.DefaultDesktopTheme) @Html.HiddenFor(x => x.DefaultMobileTheme) @Html.ValidationSummary(false) - - - - + + + + + + + + - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml index 00f7b28cee..18fda90102 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml @@ -1,7 +1,6 @@ @model TopicListModel @using Telerik.Web.Mvc.UI @{ - //page title ViewBag.Title = T("Admin.ContentManagement.Topics").Text; }
    @@ -16,68 +15,69 @@
    -
    - @Html.SmartLabelFor(model => model.AllowCustomerToSelectTheme) - - @Html.EditorFor(model => model.AllowCustomerToSelectTheme) - @Html.ValidationMessageFor(model => model.AllowCustomerToSelectTheme) -
    + @Html.SmartLabelFor(model => model.AllowCustomerToSelectTheme) + + @Html.EditorFor(model => model.AllowCustomerToSelectTheme) + @Html.ValidationMessageFor(model => model.AllowCustomerToSelectTheme) +
    + @Html.SmartLabelFor(model => model.SaveThemeChoiceInCookie) + + @Html.EditorFor(model => model.SaveThemeChoiceInCookie) + @Html.ValidationMessageFor(model => model.SaveThemeChoiceInCookie) +
    @Html.SmartLabelFor(model => model.MobileDevicesSupported) @@ -202,7 +230,7 @@
    +
    - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    -   - - -
    -

    -

    + +@if (Model.AvailableStores.Count > 1) +{ + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    +   + + +
    +} + +

    +
    @(Html.Telerik().Grid() - .Name("topics-grid") - .Columns(columns => - { - columns.Bound(x => x.SystemName) - .Width(200) - .Template(x => Html.ActionLink(x.SystemName, "Edit", new { id = x.Id })) + .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) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsPasswordProtected)) - .ClientTemplate(@Html.SymbolForBool("IsPasswordProtected")) - .Centered(); - columns.Bound(x => x.IncludeInSitemap) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IncludeInSitemap)) - .ClientTemplate(@Html.SymbolForBool("IncludeInSitemap")) - .Centered(); - columns.Bound(x => x.RenderAsWidget) - .Width(100) - .Template(item => @Html.SymbolForBool(item.RenderAsWidget)) - .ClientTemplate(@Html.SymbolForBool("RenderAsWidget")) - .Centered(); - columns.Bound(x => x.WidgetZone); - columns.Bound(x => x.Priority) + 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(); - 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", "Topic")) + }) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Topic")) .ClientEvents(events => events.OnDataBinding("onDataBinding")) - .EnableCustomBinding(true)) + .EnableCustomBinding(true))
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Topic/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Topic/_CreateOrUpdate.cshtml index e1e0e9610d..f18c32777f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Topic/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Topic/_CreateOrUpdate.cshtml @@ -177,7 +177,7 @@ @Html.SmartLabelFor(model => model.TitleTag) - @Html.DropDownListFor(model => model.TitleTag, Model.AvailableTitleTags) + @Html.DropDownListFor(model => model.TitleTag, Model.AvailableTitleTags, new { @class = "autowidth", data_select_min_results_for_search = 100 }) @Html.ValidationMessageFor(model => model.TitleTag) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml new file mode 100644 index 0000000000..4914d5c1ea --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml @@ -0,0 +1,24 @@ +@model UrlRecordModel +@using SmartStore.Admin.Models.UrlRecord; +@{ + var title = string.Concat(T("Admin.Common.Edit"), " ", T("Admin.System.SeNames.Name")); + ViewBag.Title = title; +} +@using (Html.BeginForm()) +{ +
    +
    + @title - @Model.Slug @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List") +
    +
    + +  @T("Admin.Common.Entity") + + + + +
    +
    + @Html.Partial("_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 new file mode 100644 index 0000000000..fa2a560a7d --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml @@ -0,0 +1,247 @@ +@model UrlRecordListModel +@using SmartStore.Admin.Models.UrlRecord; +@using Telerik.Web.Mvc.UI +@{ + ViewBag.Title = T("Admin.System.SeNames").Text; +} + +@using (Html.BeginForm()) +{ +
    +
    + + @T("Admin.System.SeNames") +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @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)) +
    +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/NamesPerEntity.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/NamesPerEntity.cshtml new file mode 100644 index 0000000000..14e9654c01 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/NamesPerEntity.cshtml @@ -0,0 +1,6 @@ +@{ + Layout = null; +} + +  @T("Common.ShowAll") (@ViewBag.CountSlugsPerEntity) + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml new file mode 100644 index 0000000000..ba361be39e --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml @@ -0,0 +1,63 @@ +@model UrlRecordModel +@using SmartStore.Admin.Models.UrlRecord; +@using Telerik.Web.Mvc.UI; + +@Html.ValidationSummary() +@Html.HiddenFor(model => model.Id) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.Id) + + @Html.DisplayFor(model => model.Id) +
    + @Html.SmartLabelFor(model => model.IsActive) + + @Html.EditorFor(model => model.IsActive) + @Html.ValidationMessageFor(model => model.IsActive) +
    + @Html.SmartLabelFor(model => model.Slug) + + @Html.EditorFor(model => model.Slug) + @Html.ValidationMessageFor(model => model.Slug) + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = Model.EntityName, entityId = @Model.EntityId }) +
    + @Html.SmartLabelFor(model => model.EntityName) + + @Html.EditorFor(model => model.EntityName) + @Html.ValidationMessageFor(model => model.EntityName) +
    + @Html.SmartLabelFor(model => model.EntityId) + + @Html.EditorFor(model => model.EntityId) + @Html.ValidationMessageFor(model => model.EntityId) +
    + @Html.SmartLabelFor(x => x.LanguageId) + + @Html.DropDownListFor(x => x.LanguageId, Model.AvailableLanguages) + @Html.ValidationMessageFor(x => x.LanguageId) +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Web.config b/src/Presentation/SmartStore.Web/Administration/Views/Web.config index 3da158da4f..a78708ea5a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Web.config +++ b/src/Presentation/SmartStore.Web/Administration/Views/Web.config @@ -10,7 +10,7 @@ - + @@ -49,7 +49,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Widget/WidgetsByZone.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Widget/WidgetsByZone.cshtml deleted file mode 100644 index faa065e000..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Widget/WidgetsByZone.cshtml +++ /dev/null @@ -1,4 +0,0 @@ -@model List -@foreach (var widget in Model) { - @Html.Action(widget.ActionName, widget.ControllerName, widget.RouteValues) -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Web.config b/src/Presentation/SmartStore.Web/Administration/Web.config index de19cc2802..7f12a993a8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Web.config +++ b/src/Presentation/SmartStore.Web/Administration/Web.config @@ -9,7 +9,7 @@ - + @@ -34,17 +34,17 @@ - - - - - - - - + + + + + + + + - + @@ -64,7 +64,7 @@ - + @@ -92,11 +92,7 @@ - - - - - + @@ -104,8 +100,16 @@ - + + + + + + + + + - + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/packages.config b/src/Presentation/SmartStore.Web/Administration/packages.config index a46598e9e3..7e1b1648ae 100644 --- a/src/Presentation/SmartStore.Web/Administration/packages.config +++ b/src/Presentation/SmartStore.Web/Administration/packages.config @@ -1,18 +1,20 @@  - - - - + + + + + - - - + + + - - + + + - + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/sitemap.config b/src/Presentation/SmartStore.Web/Administration/sitemap.config index 0035df8219..a1501d7653 100644 --- a/src/Presentation/SmartStore.Web/Administration/sitemap.config +++ b/src/Presentation/SmartStore.Web/Administration/sitemap.config @@ -94,6 +94,7 @@ + @@ -107,7 +108,6 @@ - @@ -128,6 +128,10 @@ + + + + @@ -137,13 +141,12 @@ - + - diff --git a/src/Presentation/SmartStore.Web/App_Data/GeoIP.dat b/src/Presentation/SmartStore.Web/App_Data/GeoIP.dat index 399b4588f2..700174ae51 100644 Binary files a/src/Presentation/SmartStore.Web/App_Data/GeoIP.dat and b/src/Presentation/SmartStore.Web/App_Data/GeoIP.dat differ diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml index 6b0c14afad..8e379a8055 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml @@ -135,6 +135,9 @@ Keine Aufträge + + Dieser Auftrag konnte Ihnen nicht zugeordnet werden. + Auftragsdatum @@ -240,6 +243,9 @@ Land + + Kundennummer + Geburtsdatum @@ -486,6 +492,15 @@ Registrierung + + Der Kunde ist bereits registriert. + + + Eine Suchmaschine kann nicht registriert werden. + + + Das Konto einer geplanten Aufgabe kann nicht registriert werden. + Die eingegebene E-Mail-Adresse wird bereits verwendet. @@ -594,6 +609,9 @@ Hersteller ('{0}') gelöscht + + Auftrag {0} gelöscht + Produkt ('{0}') gelöscht @@ -756,6 +774,9 @@ E-Mail wird benötigt. + + Bestätigung der E-Mail + Fax @@ -780,6 +801,9 @@ Telefonnummer wird benötigt + + * Eingabefelder mit Sternchen sind Pflichfelder und müssen ausgefüllt werden. + Bundesland/Region @@ -864,6 +888,15 @@ E-Mail wird benötigt. + + Bestätigung der E-Mail + + + Die E-Mail-Adresse muss mit der zuvor eingegebenen E-Mail-Adresse übereinstimmen + + + Bitte wiederholen Sie die Eingabe Ihrer E-Mail-Adresse + Fax @@ -1263,6 +1296,9 @@ Sie müssen das Attribut zunächst spreichern, bevor Sie Optionen hinzufügen können. + + Anzahl Optionen + Das Attribut wurde erfolgreich bearbeitet. @@ -1344,6 +1380,9 @@ Die Warengruppe wurde gelöscht. + + Andere Beschreibung anzeigen + Rabatte @@ -1371,6 +1410,12 @@ Kunden können die Listengröße mit Hilfe einer vorgegebenen Optionsliste ändern. + + Untere Beschreibung + + + Optionale zweite Beschreibung, die auf der Warengruppenseite unterhalb der Produkte angezeigt wird. + Design Vorlage @@ -1384,10 +1429,10 @@ Gelöscht - Beschreibung + Obere Beschreibung - Beschreibung der Warengruppe + Beschreibung der Warengruppe, die auf der Warengruppenseite oberhalb der Produkte angezeigt wird. Reihenfolge @@ -1395,6 +1440,12 @@ Legt die Anzeige-Priorität fest (1 steht bspw. für das erste Element in der Liste) + + Vollständiger Name + + + Vollständiger Name, der als Titel auf der Warengruppenseite angezeigt wird. + Meta Description @@ -1440,6 +1491,9 @@ Legt die Vater-Warengruppe fest. Lassen Sie das Feld leer, um eine Warengruppe erster Ebene zu erzeugen. + + Übergeordnete Warengruppe + Bild @@ -1897,6 +1951,9 @@ Produkte, die Attributwerte vom Typ "Produkt" haben, können nicht Bestandteil eines Bundles sein. + + Hinweise zu Produkt-Bundles + Das Produkt muss gespeichert werden, bevor Produkte zur Stückliste hinzugefügt werden können. @@ -1943,7 +2000,7 @@ Checkout-Selling - Hinzufügen + Checkout-Selling-Produkt hinzufügen Produkt @@ -1999,6 +2056,12 @@ Eine kommagetrennte Liste der zulässigen Bestellmengen, die für dieses Produkt gelten. Kunden wählen i.d.F. eine Bestellmenge aus einem Dropdown-Menü aus, anstatt eine freie Eingabe zu tätigen. + + Summe genehmigter Bewertungen + + + Summe genehmigter Rezensionen + Verknüpftes Produkt @@ -2063,7 +2126,7 @@ {0} pro Einheit (Grundpreis: {1} pro {2}) - "Maßeinheit" ist erforderlich, wenn der Grundpreis berechnet werden soll. + Grundpreis Maßeinheit "Maßeinheit" ist erforderlich, wenn der Grundpreis berechnet werden soll. @@ -2170,12 +2233,18 @@ EAN (Europa), GTIN (global trade item number), UPC (USA), JAN (Japan) oder ISBN (Bücher). + + Hat angewendete Rabatte + Hat Probedownload Legt fest, ob der Kunde eine Beispieldatei vor dem Checkout runterladen kann. + + Hat Staffelpreise + Hat Benutzervereinbarung @@ -2188,6 +2257,12 @@ Die Höhe des Produktes. + + Homepage Reihenfolge + + + Legt die Anzeige-Reihenfolge der Produkte auf der Homepage fest (1 steht bspw. für das erste Element in der Liste). + ID @@ -2242,6 +2317,9 @@ Die Länge des Produktes. + + Niedrigster Attributkombinationspreis + Aktion bei Erreichen des Meldebestandes @@ -2311,6 +2389,12 @@ Produktname ist erforderlich + + Summe nicht genehmigter Bewertungen + + + Summe nicht genehmigter Rezensionen + Benachrichtigt den Administrator, wenn die Mindestmenge unterschritten wird. @@ -2380,6 +2464,9 @@ Produkt veröffentlicht + + Verpackungseinheit + Erneuerungszeitraum @@ -2530,6 +2617,18 @@ Warengruppe eingrenzen + + Auf Homepage angezeigt + + + Filtert nach Produkten, die auf der Homepage angezeigt oder nicht angezeigt werden. + + + Veröffentlicht + + + Filtert nach veröffentlichten bzw. unveröffentlichten Produkten. + Hersteller @@ -2548,6 +2647,18 @@ Nach einem Produkttyp suchen. + + Ohne Warengruppenzuordnung + + + Filtert nach Produkten ohne Warengruppenzuordnung. + + + Ohne Herstellerzuordnung + + + Filtert nach Produkten ohne Herstellerzuordnung. + Produktverwaltung @@ -2783,7 +2894,7 @@ Cross-Selling - Hinzufügen + Cross-Selling-Produkt hinzufügen Reihenfolge @@ -2792,7 +2903,7 @@ Produkt - Sie müssen das Produkt speichern, bevor Sie verwandte Produkte festlegen können + Sie müssen das Produkt speichern, bevor Sie Cross-Selling-Produkte hinzufügen können. Spezifikationsattribute @@ -2801,7 +2912,7 @@ Hinzufügen - Neues Spezifikationsattribut hinzufügen + Spezifikationsattribut zuordnen Filtern ermöglichen @@ -2887,8 +2998,41 @@ Anzahl Artikel + + Auf Aktualisierung prüfen + + + Unbekannter Fehler beim Paket-Download. Bitte versuchen Sie es später erneut. + + + AutoUpdate möglich + + + <p>Dieses Update kann automatisch installiert werden. Hierfür lädt SmartStore.NET ein Installationspaket auf Ihren Webserver herunter, führt die Installation durch und startet die Anwendung neu. Vor der Installation wird der Verzeichnisinhalt Ihres Shops gesichert, mit Ausnahme der Ordner <i>App_Data</i> und <i>Media</i> sowie der SQL Server Datenbank. </p><p>Klicken Sie die Schaltfläche <b>Jetzt aktualisieren</b>, um das Paket downzuloaden und zu installieren. Alternativ hierzu können Sie weiter unten das Paket auf Ihren lokalen PC downloaden und die Installation zu einem späteren Zeitpunkt manuell durchführen.</p> + + + Aktuelle Version + + + Nicht mehr benachrichtigen + + + SmartStore.NET ist auf dem neuesten Stand + + + Release Notes + + + Update verfügbar + + + Jetzt aktualisieren + + + Ihre Version + - Über + Über SmartStore.NET Aktionen @@ -2899,12 +3043,21 @@ Neu + + Admin-Kommentar + + + Kommentar für internen Gebrauch. Wird nicht veröffentlicht. + Alle Sind Sie sicher? + + Möchten Sie alle gegenseitigen Zuordnungen erstellen? + Möchten Sie fortfahren? @@ -2938,12 +3091,21 @@ Kopie von - - CSV-Datei + + Alle gegenseitigen Zuordnungen erstellen + + + Es wurden {0} gegenseitige Zuordnung(en) erstellt. + + + CSV Konfiguration Die Daten wurden erfolgreich geändert. + + Datenaustausch + Die Daten wurden erfolgreich gespeichert. @@ -2956,29 +3118,59 @@ Ausgewählte Löschen + + Alle löschen + Möchten Sie dieses Element wirklich löschen? Möchten Sie "{0}" wirklich löschen? + + Gelöscht + + + Ausgewählte löschen + Bearbeiten + + Die E-Mail wurde erfolgreich versendet. + + + Bitte geben Sie eine E-Mail-Adresse ein. + + + Objekt + ID Die eindeutige nummerische ID des Datensatzes - - Excel-Datei + + Fehler + + + Fehler beim Senden einer E-Mail + + + PDF Export + + + Bitte haben Sie einen Augenblick Geduld, während der Export durchgeführt wird + + + Alle exportieren Keine Daten zu exportieren. - - Als CSV-Datei exportieren + + Ausgewählte exportieren Nach Excel exportieren @@ -2995,14 +3187,29 @@ Nur Ausgewählte nach PDF exportieren + + Zu viele Objekte! Mehr als 500 Objekte können nicht konvertiert werden. Bitte reduzieren Sie die Anzahl ausgewählter Datensätze. + Als XML exportieren - - Alles als XML exportieren + + Die Datei ist in Benutzung und kann daher nicht geöffnet werden. + + + Datei nicht gefunden + + + {0} Dateien wurden gelöscht + + + Die Datei muss vom Typ {0} sein. - - Nur Ausgewählte als XML exportieren + + {0} Verzeichnisse wurden gelöscht + + + FTP-Status {0} ({1}). Allgemein @@ -3028,41 +3235,17 @@ Verbergen - - Von CSV-Datei importieren - - - Von Excel importieren - - - Aktiv seit: {0}. - - - Import abbrechen - - - Import wurde abgebrochen - - - Soll der aktive Importvorgang wirklich abgebrochen werden? Bislang importierte Produkte werden nicht gelöscht. - - - Vollständigen Bericht runterladen... - - - Der Import läuft jetzt im Hintergrund . Sie können den Fortschritt bzw. das Ergebnis des letzten Importvorganges jederzeit im Import-Dialog einsehen. + + HTTP-Status {0} ({1}). - - <b>Letzter Import</b>: {0}{1}. + + Ignorieren - - Kein Bericht verfügbar + + Importdatei - - {0} von {1} Zeilen verarbeitet. - - - {0} neu, {1} aktualisiert - bei {2} Warnung(en) und {3} Fehler(n). + + Importdateien Bitte warten Sie, der Importvorgang wird durchgeführt @@ -3070,8 +3253,14 @@ Info + + Letzte Ausführung + - Lizenz + Lizenzieren + + + Lizenziert Listenansicht @@ -3079,24 +3268,63 @@ Alle nicht gespeicherten Änderungen gehen verloren. Sind Sie sicher? + + Neue Datensätze + Nein Nein, abbrechen + + Es wurden keine Einträge ausgewählt. + + + von + + + Platzhalter + Bitte auswählen Vorschau + + Der Provider {0} konnte nicht geladen werden. + + + Öffentliche Dateien + + + Es wurden {0} Datensätze gelöscht. + + + Überspringen + + + Legt die Anzahl der zu überspringenden Datensätze fest. + + + Begrenzen + + + Legt die maximale Anzahl der zu verarbeitenden Datensätze fest. + Auf Standardwerte zurücksetzen + + Die gewünschte Ressource wurde nicht gefunden. + Sie müssen die Anwendung neu starten, damit die Änderungen wirksam werden. + + Einschränkungen + Speichern @@ -3106,6 +3334,12 @@ Suchen + + Ausgewählte + + + Jetzt senden + Suchmaschinen (SEO) @@ -3115,6 +3349,12 @@ Anzeigen + + Werte für Überspringen und Begrenzen müssen größer oder gleich 0 sein. + + + Ausgelassen + Standard @@ -3145,15 +3385,30 @@ Alle Shops + + Erfolgreich am + Der Vorgang wurde erfolgreich ausgeführt. + + Zeilen insgesamt + Baumansicht + + Unlizenziert + + + Nicht unterstützter Entitätstyp '{0}' + Aktualisieren + + Aktualisiert + Bitte eine Datei hochladen. @@ -3163,6 +3418,9 @@ Bitte warten, die Verarbeitung läuft... + + Warnungen + Falsche E-Mail @@ -3175,6 +3433,9 @@ Zugriffsrechte + + Es sind keine Kundengruppen definiert + Zugriffsrecht @@ -3193,9 +3454,6 @@ Filtert Ereignisse nach Ereignistypen. - - Ereignistyp - Nachricht @@ -3220,6 +3478,15 @@ Kunden-E-Mail + + Filtert Ergebnisse nach E-Mail-Adresse der Kunden. + + + Kundensystemkonto + + + Filtert Ergebnisse nach Kundenystemkonten. + Aktivitätstypen @@ -3235,6 +3502,24 @@ Die Ereignistypen wurden erfolgreich bearbeitet. + + Diese Konfiguration für Kindelemente übernehmen + + + Diese Funktion übernimmt die Zugriffsrecht-Konfiguration dieser Warengruppe für alle Unterwarengruppen und Produkte.<br/> + Bitte beachten Sie, dass die Änderungen der Zugriffsrechte zunächst gespeichert werden müssen, <br /> + bevor diese für Unterkategorien und Produkte übernommen werden können. <br /> + <b>Vorsicht:</b> Bitte beachten Sie, <b>dass vorhandene Zugriffsrechte überschrieben bzw. gelöscht werden</b>. + + + Diese Konfiguration für Kindelemente übernehmen + + + Diese Funktion übernimmt die Shop-Konfiguration dieser Warengruppe für alle Unterwarengruppen und Produkte.<br/> + Bitte beachten Sie, dass die Änderungen an der Store-Konfiguration zunächst gespeichert werden müssen, <br /> + bevor diese für Unterkategorien und Produkte übernommen werden können. <br /> + <b>Vorsicht:</b> Bitte beachten Sie, <b>dass vorhandene Store-Konfiguration überschrieben bzw. gelöscht werden</b>. + Content Slider @@ -3379,6 +3664,9 @@ Zurück zur Länderliste + + Das Land kann nicht gelöscht werden, weil ihm Adressen zugeordnet sind. + Das Land wurde gelöscht. @@ -3467,7 +3755,7 @@ Neue Region hinzufügen - Das Land kann nicht gelöscht werden. Es wird bei vorhandenen Adressen verwendet. + Das Bundesland\Region kann nicht gelöscht werden, weil ihm Adressen zugeordnet sind. Region bearbeiten @@ -3520,15 +3808,12 @@ Zurück zur Währungsliste - - Die Leitwährung für Währungsumrechnungen kann nicht gelöscht werden. - - - Die Leitwährung des Shops kann nicht gelöscht werden. - Die Währung wurde gelöscht. + + Die Währung kann nicht gelöscht oder deaktiviert werden, weil sie dem Shop "{0}" als Leit- oder Umrechnungswährung zugeordnet ist. + Währungsdetails bearbeiten @@ -3587,16 +3872,10 @@ Online Wechselkursdienst - Hauptwährung für Währungsumrechnung + Umrechnungswährung - Leitwährung im Shop - - - Als Hauptwährung für Währungsumrechnung festlegen - - - Als Leitwährung im Shop festlegen + Leitwährung Name @@ -3610,6 +3889,18 @@ Der Name ist erforderlich. + + Ist Umrechnungswährung für + + + Eine Liste mit Shops, in denen die Währung Umrechnungswährung ist. + + + Ist Leitwährung für + + + Eine Liste mit Shops, in denen die Währung Leitwährung ist. + Veröffentlicht @@ -3778,6 +4069,9 @@ Die E-Mail wurde erfolgreich versandt. + + Test der E-Mail-Funktion. + Das E-Mail-Konto wurde erfolgreich bearbeitet. @@ -3818,10 +4112,10 @@ Legt die Anzeige-Priorität fest (1 steht bspw. für das erste Element in der Liste). - Dateiname des Flaggenbildes + Flaggenbild - Dateiname des Flaggenbildes. Die Datei sollte in folgendem Verzeichnis gespeichert werden: \\images\\flags. + Legt das Flaggenbild fest. Die Dateien der Flaggenbilder müssen in /Content/Images/flags/ liegen. Gebietsschema @@ -3863,7 +4157,7 @@ Der SEO-Code muss 2 Zeichen haben. - Der SEO-Code muss eindeutig sein. + Bitte legen Sie einen SEO Sprach-Code fest. Ressourcen importieren @@ -4003,6 +4297,9 @@ Zahlung + + Das Plugin erlaubt keine Aktivierung dieser Zahlungsart. + Zahlungsarten @@ -4013,10 +4310,10 @@ Konfiguration - Wiederkehrender Zahlungstyp + Wiederkehrende Zahlungen - Unterstützt Capture + Buchung möglich Teilerstattung möglich @@ -4025,11 +4322,29 @@ Erstattung möglich - Kann storniert werden + Stornierung möglich + + + Langtext + + + Legt eine vollständige Beschreibung der Zahlungsmethode fest. Sie erscheint in der Zahlungsliste im Kassenbereich. + + + Es wurden keine Möglichkeiten zur Einschränkung von Zahlungsarten gefunden. + + + Kurzbeschreibung + + + Legt eine Kurzbeschreibung der Zahlungsmethode fest. Plugins + + Bitte überprüfen Sie, ob die Lizenzierung für die richtige Shop-URL erfolgt! Mit der Lizenzierung fortfahren? + Plugins installieren oder aktualisieren @@ -4108,6 +4423,18 @@ Das Plugin wurde installiert. + + Das Plugin wurde erfolgreich lizenziert. + + + Lizenzschlüssel + + + Bitte den Lizenzschlüssel für das Plugin eingeben. + + + Der Lizenzschlüssel kann für die hinterlegte Shop-URL nicht aktiviert werden. Bitte tragen Sie vor der Aktivierung in den Shop-Details die korrekte Shop-URL ein. + Zurück zur Plugin-Liste @@ -4141,9 +4468,42 @@ Das Plugin wurde deinstalliert. + + Beim Aufruf eines Plugins ist ein unbekannter Fehler aufgetreten. Details entnehmen Sie bitte der folgenden Meldung. + + + Verpackungseinheiten + + + Verpackungseinheit wurde erfolgreich zugefügt + + + Verpackungseinheit hinzufügen + + + Verpackungseinheit bearbeiten + + + Standard-Verpackungseinheit + + + Legt die Standard-Verpackungseinheit fest + + + Name + + + Legt den Namen der Verpackungseinheit fest + + + Verpackungseinheit wurde erfolgreich aktualisiert + Regionale Einstellungen + + Sie müssen zunächst speichern, bevor Sie Einschränkungen festlegen können. + Einstellungen @@ -4183,6 +4543,12 @@ Aktiviert den Blog. + + Maximales Alter (in Tagen) + + + Legt das maximale Blog-Alter in Tagen fest. Ältere Blog-Einträge werden im RSS-Feed nicht exportiert. + Benachrichtigung bei neuen Kommentaren @@ -4205,7 +4571,7 @@ RSS-Feed URL im Browser in der Adressleiste anzeigen - Legt fest, ob der RSS-Feed-Link in der Browser-Adressleiste zu angezeigt werden soll. + Legt fest, ob der RSS-Feed-Link in der Adressleiste des Browsers angezeigt werden soll. Katalog-Einstellungen @@ -4264,12 +4630,24 @@ Legt die Anzahl der dargestellten Produkte pro Seite fest. + + Standardsortierreihenfolge für Produkte + + + Legt die Standardsortierreihenfolge für Produkte fest. + Standard Listendarstellung Legt fest, wie Produktlisten standardmäßig dargestellt werden sollen. Der Kunde kann die Darstellung im Shop ändern. + + Lieferzeit, die bei einem leerem Lagerbestand angezeigt wird + + + Lieferzeit, die angezeigt wird, wenn der Warenbestand des Produktes kleiner gleich 0 ist. + Anzahl, ab der Bilder nur noch bei Variantenwechsel angezeigt werden @@ -4294,11 +4672,17 @@ Option, bei der Langtexte gekürzt und erst auf Klick in voller Länge angezeigt werden. + + Alle Filtergruppen aufklappen + + + Legt fest, ob alle Filtergruppen aufgeklappt angezeigt werden sollen + - Filterfunktion aktivieren + Filter Sidebar Widget anzeigen - Aktiviert die Filterfunktion für Produkte in Warengruppen. + Aktiviert das Filter Sidebar Widget zum Filtern von Produkten in Warengruppen Verberge Kaufen-Button in Produktlisten @@ -4306,6 +4690,24 @@ Legt fest, ob der Kaufen-Button in Produktlisten ausgeblendet werden soll. + + Standardbild bei Warengruppen ausblenden + + + Legt fest, ob das Standardbild bei Warengruppen ausgeblendet werden soll. Das Standardbild wird angezeigt, wenn der Warengruppe kein Bild zugeordnet ist. + + + Standardbild bei Herstellern ausblenden + + + Legt fest, ob das Standardbild bei Herstellern ausgeblendet werden soll. Das Standardbild wird angezeigt, wenn dem Hersteller kein Bild zugeordnet ist. + + + Standardbild bei Produkten ausblenden + + + Legt fest, ob das Standardbild bei Produkten ausgeblendet werden soll. Das Standardbild wird angezeigt, wenn dem Produkt kein Bild zugeordnet ist. + Höhe des gekürzten Langtextes @@ -4324,6 +4726,12 @@ Deaktiviert die Anzeige empfohlener Produkte im Shop. Dies kann die Performance erhöhen. + + Top-Produkte in Listen anzeigen + + + Legt fest, ob Top-Produkte sowohl in den Produkt- als auch in den Filterlisten angezeigt werden sollen. Ansonsten erscheinen sie nur oberhalb in der Top-Produktliste. + Komplette Beschreibung beim Produktvergleich einbeziehen @@ -4348,6 +4756,12 @@ Legt die Anzahl der anzuzeigenen Hersteller in der Navigationsleiste fest. + + Maximale Anzahl Filtereinträge + + + Bestimmt die maximale Anzahl angezeigter Filtereinträge + Allgemein @@ -4369,6 +4783,12 @@ Anzahl der angezeigten Produkt-Tags + + Preisanzeige + + + Legt fest, ob bzw. welcher Typ von Preis in Produktlisten angezeigt werden soll. + Produktdetail @@ -4486,6 +4906,12 @@ Legt die Anzahl der sichtbaren Produkte in der Liste der kürzlich angesehenen Produkte fest. + + Produktbeschreibung durchsuchen + + + Legt fest, ob die Produktbeschreibung in der Suche einbezogen werden soll. + Anzahl Ergebnisse pro Seite auf Suchseite @@ -4522,6 +4948,12 @@ Legt fest, ob die Farben des ersten Farbattributes auch in Produktlisten angezeigt werden sollen + + Zeige die Standard-Verpackungseinheit + + + Zeige die Standard-Verpackungseinheit, falls für das Produkt keine Verpackungseinheit festgelegt ist. + Zeige Lieferzeiten @@ -4570,12 +5002,36 @@ Legt fest, ob die Hersteller-Produktnummer im Shop angezeigt werden soll. + + Zeige Herstellerbilder auf der Homepage + + + Bestimmt ob Hersteller auf der Homepage als Bilder oder textuelle Links angezeigt werden. + + + Bilder von Herstellern anzeigen + + + Legt fest, ob Herstellerbilder auf der Produktdetailseite angezeigt werden sollen. + + + Zeige Hersteller auf der Homepage + + + Bestimmt ob Hersteller auf der Homepage angezeigt werden. + Produktbilder bei der Autovervollständigung anzeigen Legt fest, ob Produktbilder bei der Autovervollständigung währen der Suche angezeigt werden sollen. + + Zeige Produktbewertungen in der Produktdetailansicht + + + Legt fest, ob Produktbewertungen in der Produktdetailansicht angezeigt werden. + Zeige Produktbewertungen in Produktlisten @@ -4612,6 +5068,18 @@ Zeigt das Gewicht des Produktes an. + + Filterergebnisse nach Trefferanzahl sortieren + + + Legt fest, das Filterergebnisse absteigend nach der Anzahl an Übereinstimmungen sortiert werden. Ist diese Option deaktiviert, so wird in der für die Werte festgelegten Reihenfolge sortiert. + + + Unterwarengruppen anzeigen + + + Legt fest, ob und wo Unterwarengruppen auf einer Warengruppenseite angezeigt werden sollen. + SKU Suche unterdrücken @@ -4786,6 +5254,12 @@ Legt fest, ob das Eingabefeld "Land" während der Registrierung aktiviert ist. + + Kunden können Kundennummer hinterlegen + + + Bestimmt ob Kunden eine Kundennummer angeben können, wenn für diese noch kein Wert hinterlegt wurde. + Benutzerformular-Felder @@ -4804,6 +5278,24 @@ Legt die maximale Länge des angezeigten Benutzernamens fest. + + Kundennummer speichern + + + Bestimmt ob Kundennummern hinterlegt werden können. + + + Kundennummern + + + Legt fest, ob Kundennummern vergeben werden und ob diese automatisch vergeben werden sollen. + + + Darstellung der Kundennummer + + + Legt die Darstellung und Handhabung der Kundennummer gegenüber dem Kunden fest. + Kundeneinstellungen @@ -4834,6 +5326,18 @@ Legt die Standard-Zeitzone für den Shop fest. + + Kundennummern im Frontend anzeigen + + + Bestimmt ob Kunden ihre Kundennummer in Ihrem Account-Bereich einsehen können. + + + Einwilligungserklärung im Kontaktformular fordern + + + Bestimmt ob im Kontaktformular eine Checkbox angezeigt wird, die den Benutzer auffordert der Speicherung seiner Daten zuzustimmen. + Automatische Registrierung aktiviert @@ -4903,6 +5407,12 @@ Legt fest, ob die Eingabe der Telefonnummer erforderlich ist. + + Kundengruppe bei Registrierungen + + + Legt eine Kundengruppe fest, die neu registrierten Kunden zugeordnet wird. + Anzeige des Registrierungsdatums eines Kunden @@ -4963,6 +5473,12 @@ Registrierungsmethode für Benutzer. Standard-Methode: Registrierung ohne Bestätigung möglich. E-Mail-Bestätigungsmethode: Benutzer müssen auf eine Überprüfungs-E-Mail antworten, bevor ihr Konto aktiviert wird. Admin-Bestätigungs-Methode: Kunden können sich registrieren. Das Konto wird aber erst nach Bestätigung des Administrators aktiviert. Disabled-Methode: Registrierung ist deaktiviert. + + Prüfung der Kunden-Email-Adresse + + + Bestimmt ob die Email-Adresse des Kunden im Checkout validiert wird. + Anzeige "Postleitzahl" aktiviert @@ -4975,6 +5491,24 @@ Legt fest, ob Angabe der Postleitzahl erforderlich ist. + + Zeitlimit für Bilder-Download (Minuten) + + + Legt das Zeitlimit für den Bilder-Download in Minuten fest. + + + Bilderordner (relativer Pfad) + + + Legt einen relativen Pfad zu einem Ordner mit zu importierenden Bildern fest (z.B. Inhalt\Bilder). + + + Maximale Länge von Datei- und Ordnernamen + + + Legt die maximale Länge von Datei- und Ordnernamen fest, die im Rahmen eines Imports\Exports erzeugt wurden. + Forum @@ -5110,6 +5644,24 @@ Der Zugriff auf den Adminbereich kann nur noch über diese IP-Adresse erfolgen. + + Unicode-Zeichen erlauben + + + Legt fest, ob als Unicode-Zeichen eingestufte Buchstaben in SEO relevanten Namen erlaubt sind. + + + Bei Abschluss einer Bestellung PDF mitsenden + + + Erstellt bei Abschluss einer Bestellung das Auftrags-PDF-Dokument und hängt es der Kunden-Benachrichtigungs-Email an + + + Bei Bestelleingang PDF mitsenden + + + Erstellt bei Bestelleingang das Auftrags-PDF-Dokument und hängt es der Kunden-Benachrichtigungs-Email an + Bankverbindung @@ -5149,6 +5701,12 @@ Geben Sie die IBAN Ihrer Bank an. + + Regel für kanonischen Domänennamen + + + Erzwingt die permanente Umleitung zu einem einzelnen Domännennamen für ein besseres Seitenranking (z.B. meinshop.de > www.meinshop.de oder umgekehrt) + Kanonische Urls aktivieren @@ -5446,6 +6004,12 @@ Der Sicherheitsschlüssel muss 16 Zeichen haben. + + Extra Disallows für robots.txt + + + Geben Sie hier zusätzliche Pfade an, die als Disallow-Einträge zur robots.txt hinzugefügt werden sollen. Jeder Eintrag muss in einer neuen Zeile erfolgen. + Einstellungen zur Volltextsuche @@ -5500,6 +6064,12 @@ Lokalisierung + + Meta Robots + + + Legt fest, ob und wie Suchmaschinen die Seiten Ihres Shops indexieren. + Seiten-Titel-Anpassung @@ -5554,6 +6124,12 @@ Wenn aktiviert, werden Suchmaschinenfreundliche URLs generiert. + + Zu konvertierende Zeichen + + + Ermöglicht das individuelle Konvertieren von Zeichen bei der Erstellung SEO Namen. Geben Sie hier durch Semikolon getrennt das alte und das neue Zeichen ein, z.B. ä;ae. Jeder Eintrag muss in einer neuen Zeile erfolgen. + Suchmaschinen @@ -5608,6 +6184,12 @@ Shop Information + + Zeichenkette prüfen + + + Geben Sie eine beliebige Zeichenkette ein, um daraus den SEO Namen zu erstellen. Geänderte Einstellungen müssen zuvor gespeichert werden. + Bilder zur Sprachauswahl verwenden @@ -5668,12 +6250,27 @@ Legt die maximal erlaubte Größe (längste Seite) für Bilduploads fest. + + Thumbnail-Größe von Produkten in E-Mails + + + Legt die Thumbnail-Bildgröße (in Pixel) von Produkten in E-Mails fest. Geben Sie 0 ein, um keine Thumbnails anzuzeigen. + Miniwarenkorb-Symbolbild-Größe Legt die Miniwarenkorb-Symbolbild-Größe in Pixeln fest. + + Bitte sichern Sie Ihre Datenbank, ehe Sie Mediendateien verschieben. Dieser Vorgang kann je nach Menge der Bilddaten mehrere Minuten in Anspruch nehmen. + + + In Datenbank verschieben + + + Ins Dateisystem verschieben + Bilddaten speichern in... @@ -5686,9 +6283,6 @@ Dateisystem - - Legt fest, ob Bilder in der Datenbank oder im Dateisystem gespeichert werden. - Zoomtyp @@ -5740,6 +6334,12 @@ Legt die Anzahl der angezeigten News auf der Startseite fest. + + Maximales Alter (in Tagen) + + + Legt das maximale News-Alter in Tagen fest. Ältere News werden im RSS-Feed nicht exportiert. + Seitengröße @@ -5782,6 +6382,12 @@ Der Kunde wird direkt auf die Auftrags-Detail-Seite geleitet, falls diese Einstellung aktiviert ist. + + Aufträge aller Shops anzeigen + + + Legt fest, ob dem Kunden die Aufträge aller Shops angezeigt werden sollen. Ist diese Option deaktiviert, so werden nur die Aufträge des aktuellen Shops angezeigt. + Geschenkgutschein ist eingelöst, wenn Auftragsstatus... @@ -5792,7 +6398,7 @@ Der Auftragsstatus konnte nicht auf "in Bearbeitung" gesetzt werden - Geschenkgutschein wird deactiviert, wenn Auftragsstatus ... + Geschenkgutschein wird deaktiviert, wenn Auftragsstatus... Geschenkgutscheine sind deaktiviert, wenn der Auftragsstatus ist … @@ -5807,16 +6413,16 @@ Benutzer können Bestellung erneut abschicken. - Mindestbestellwert Netto + Mindestbestellwert Zwischensumme - Legt den Netto-Mindestbestellwert fest. + Legt den Mindestbestellwert der Zwischensumme fest. - Mindestbestellwert Brutto + Mindestbestellwert Gesamtsumme - Legt den Brutto-Mindestbestellwert fest. + Legt den Mindestbestellwert der Gesamtsumme fest. Erlaubter Zeitraum für Retourenwünsche in Tagen @@ -5836,6 +6442,12 @@ Legen Sie einen Startwert für Bestellnummern fest. Dies ist nützlich, wenn Sie Ihre Aufträge ab einem Bestimmten Wert starten lassen möchten. Der Wert muss größer sein, als die aktuell höchste Bestellnummer und gilt für zukünftige Bestellungen. + + Anzahl der Aufträge pro Seite + + + Legt die Anzahl der dargestellten Aufträge pro Seite fest. + Auftragseinstellungen @@ -5941,6 +6553,12 @@ Bonuspunkte, die für eine Registrierung gewährt werden. + + Punkte abrunden + + + Legt fest, ob bei der Punkteberechnung abgerundet werden soll. Ansonsten werden Bonuspunkte aufgerundet. + Versand-Einstellungen @@ -5998,6 +6616,9 @@ Warenkorb + + Checkout + Anzahl der angezeigten Cross-Selling-Artikel @@ -6052,6 +6673,15 @@ Aktivieren, um Artikel aus der Wunschliste in den Warenkorb zu verschieben. + + Abonnieren von Newslettern + + + Legt fest, ob Kunden bei einer Bestellung Newsletter abonnieren können und ob die Checkbox standardmäßig aktiviert ist. + + + Bestellabschlussseite + Preise bei der Berechnung runden @@ -6065,16 +6695,16 @@ Bestimmt ob der Grundpreis im Warenkorb angezeigt werden soll. - Zeige Kommentarbox auf Bestellabschlussseite + Kommentarbox anzeigen - Legt fest ob der Kunde auf der Bestellabschlussseite einen Kommentar zu seiner Bestellung hinterlegen kann. + Legt fest, ob der Kunde auf der Bestellabschlussseite einen Kommentar zu seiner Bestellung hinterlegen kann. - Rechtliche Hinweise in der Warenkorbübersicht auf der Bestellabschlußseite anzeigen + Rechtliche Hinweise in der Warenkorbübersicht anzeigen - Bestimmt, ob rechtliche Hinweise in der Warenkorbübersicht auf der Bestellabschlußseite angezeigt werden. Dieser Text kann in den Sprachresourcen geändert werden. + Legt fest, ob rechtliche Hinweise in der Warenkorbübersicht auf der Bestellabschlußseite angezeigt werden. Dieser Text kann in den Sprachresourcen geändert werden. Lieferzeiten anzeigen @@ -6088,6 +6718,12 @@ Zeigt die Rabattbox im Warenkorb an. + + Widerrufsverzichtbox für elektronische Leistungen anzeigen + + + Legt fest, ob der Kunde auf der Bestellabschlussseite einem Widerrufsverzicht für elektronische Leistungen zustimmen muss. + Zeige die Geschenkgutschein-Box im Warenkorb @@ -6142,6 +6778,21 @@ Legt fest ob das Produktgewicht im Warenkorb angezeigt wird. + + Zustimmung zur E-Mail Weitergabe an Dritte + + + Legt fest, ob Kunden bei einer Bestellung der Weitergabe ihrer E-Mail Adresse an Dritte zustimmen können und ob die Checkbox dafür standardmäßig aktiviert ist. + + + Text für E-Mail Weitergabe + + + Mit der Übermittlung und Speicherung meiner E-Mail-Adresse durch dritte Parteien bin ich einverstanden. + + + Legt den Text für die Zustimmung zur Weitergabe der E-Mail Adresse an Dritte fest. Wählen Sie bitte einen konkreten Grund für die Weitergabe, z.B. 'Mit der Übermittlung und Speicherung meiner E-Mail-Adresse zur Abwicklung des Käuferschutzes durch Trusted Shops bin ich einverstanden.' + Wunschzettel @@ -6304,6 +6955,9 @@ Versand + + Das Plugin erlaubt keine Aktivierung dieser Berechnungsmethode für Versandkosten. + Versandarten @@ -6349,6 +7003,12 @@ Der Name ist erforderlich + + Es konnten keine Versandarten geladen werden. + + + Es wurden keine Möglichkeiten zur Einschränkung von Versandarten gefunden. + Die Versandart wurde erfolgreich bearbeitet. @@ -6364,18 +7024,6 @@ Reihenfolge - - Versandart-Einschränkungen - - - Land - - - Markieren Sie die Länder, bei denen die Einschränkung gelten soll - - - Die Einstellungen wurden erfolgreich bearbeitet - SMS-Anbieter @@ -6445,6 +7093,18 @@ Bitte einen Namen angeben. + + Umrechnungswährung + + + Legt die Umrechnungswährung für diesen Shop fest. + + + Leitwährung + + + Legt die Leitwährung des Shops fest. + Gesicherte URL @@ -6452,10 +7112,10 @@ Die gesicherte URL des Shops, z.B. https://www.mein-shop.de/ or http://sharedssl.mein-shop.de/. Die gesicherte URL wird automatisch erkannt, wenn dieses Feld leer ist. - SSL aktivieren + SSL - Aktiviert SSL, falls der Shop SSL gesichert werden soll. + Legt fest, ob der Shop SSL gesichert werden soll. Shop Logo @@ -6550,6 +7210,9 @@ Name + + Theme benötigt keine Konfiguration + LESS CSS Parser Fehler: Ihre Änderungen wurden nicht gespeichert, da Ihre Konfiguration zu einem Fehler im Shop führen würde. Details siehe Fehlerbericht. @@ -6598,6 +7261,12 @@ Legt fest, ob für Mobile-Endgeräte die Mobile-Ansicht unterstützt wird. + + Benutzer Theme in Cookie speichern + + + Wenn nicht gewählt, wird das Benutzer Theme mit dem Kundenkonto verknüpft, was u.U. unerwünscht sein kann, wenn sich bspw. mehrere User einen Gastzugang teilen. + Deaktivieren Sie Ressourcen-Bundling und -Caching, um Theme-Änderungen optimal testen und debuggen zu können. @@ -6817,6 +7486,12 @@ Ein Name für die Forengruppe ist erforderlich + + URL-Alias + + + Legt einen Suchmaschinen-freundlichen Seitennamen für das Forum fest. 'Tolles Forum' resultiert bspw. in '~/tolles-forum'. Standard ist der Name des Forums. + Das Forum wurde erfolgreich aktualisiert @@ -6862,6 +7537,12 @@ Ein Name für die Forengruppe ist erforderlich + + URL-Alias + + + Legt einen Suchmaschinen-freundlichen Seitennamen für die Forengruppe fest. 'Tolle Forengruppe' resultiert bspw. in '~/tolle-forengruppe'. Standard ist der Name der Forengruppe. + Die Forengruppe wurde erfolgreich aktualisert. @@ -6889,6 +7570,24 @@ Liste erlaubter Platzhalter. Diese können in E-Mail-Vorlagen benutzt werden. + + Anhang 1 + + + Eine Datei, die jedem gesendeten E-Mail angehangen werden soll (z.B. AGB, Widerrufsbelehrung etc.) + + + Anhang 2 + + + Eine Datei, die jedem gesendeten E-Mail angehangen werden soll (z.B. AGB, Widerrufsbelehrung etc.) + + + Anhang 3 + + + Eine Datei, die jedem gesendeten E-Mail angehangen werden soll (z.B. AGB, Widerrufsbelehrung etc.) + BCC-Adresse @@ -6922,6 +7621,12 @@ Der Name der Vorlage (Schreibgeschützt) + + Nur manuell senden + + + Legt fest, ob E-Mails, die von dieser Nachrichtenvorlage abgeleitet sind, ausschließlich manuell gesendet werden sollen. + Betreff @@ -7261,6 +7966,12 @@ Der Titel der Seite. + + Titel-Tag + + + Legt das Tag fest, welches für die Überschrift des Topics ausgegeben wird + URL @@ -7276,6 +7987,12 @@ Legt fest, ob der Titel als Überschrift dargestellt werden soll. + + Inhalt mit Container umhüllen + + + Umhüllt den Widget Inhalt mit einem HTML-Container + Widget Zone @@ -7351,6 +8068,9 @@ Zurück zur Gesamtliste + + Die Kundengruppe "{0}" wurde nicht gefunden. + Die Kundengruppe wurde gelöscht. @@ -7534,6 +8254,9 @@ Erstellt am + + Kunden GUID + Kundengruppen @@ -7591,6 +8314,9 @@ IP-Adresse, mit der sich der Kunde zuletzt im Shop aktiv war. + + Ist Systemkonto + Mehrwertsteuerfrei @@ -7603,6 +8329,9 @@ Zeigt an, wann der Kunde zuletzt im Shop aktiv war. + + Letztes Login-Datum + Nachname @@ -7624,6 +8353,9 @@ Passwort des Kunden. + + Passwort Salt + Telefon @@ -7828,9 +8560,6 @@ Punkte hinzufügen. - - Datum - Nachricht @@ -7981,6 +8710,729 @@ Dashboard + + Neues Profil + + + Standardwert + + + Eigenschaft des Objektes + + + Importfeld + + + Sie können optional für jedes Feld der Importdatei festlegen, ob und nach welcher Objekteigenschaft dessen Daten zu importieren sind. Gleichnamige Felder werden grundsätzlich immer importiert, sofern sie nicht explizit ignoriert werden sollen. Noch nicht ausgewählte Eigenschaften sind in der Auswahlliste hervorgehoben. Zudem ist die Angabe eines Standardwertes möglich, der angewendet wird, wenn das Importfeld leer ist. Durch Änderung des Trennzeichens werden gespeicherte Zuordnungen ungültig und zurückgesetzt. + + + Die gespeicherten Feldzuordnungen sind aufgrund der Änderung des Trennzeichens ungültig und wurden zurückgesetzt. + + + Trennzeichen + + + Legt das zu verwendende Trennzeichen für die Felder fest. + + + Geben Sie bitte ein gültiges Trennzeichen ein. + + + Inneres Anführungszeichen + + + Legt das innere Anführungszeichen (Escaping) fest. + + + Geben Sie bitte ein gültiges, inneres Anführungszeichen (Escaping) ein. + + + Trennzeichen und inneres Anführungszeichen können in CSV Dateien nicht gleich sein. + + + Anführungszeichen + + + Legt das zu verwendende Anführungszeichen fest. + + + Geben Sie bitte ein gültiges Anführungszeichen ein. + + + Alle Felder in Anführungszeichen + + + Legt fest, ob die Werte aller Felder in Anführungszeichen gestellt werden sollen. + + + Trennzeichen und Anführungszeichen können in CSV Dateien nicht gleich sein. + + + Mehrzeilen erlaubt + + + Legt fest, ob mehrzeilige Feldwerte unterstützt werden. + + + Überflüssige Leerzeichen entfernen + + + Legt fest, ob Leerzeichen am Anfang und am Ende eines Feldwertes entfernt werden sollen. + + + Stapelgröße + + + Legt die maximale Anzahl der Datensätze pro Exportdatei fest. 0 ist der Standard und bedeutet, dass alle Datensätze in eine Datei exportiert werden. + + + Ein System-Exportprofil kann nicht gelöscht werden. + + + Nach erfolgreicher Veröffentlichung aufräumen + + + Legt fest, ob nicht mehr benötigte Dateien gelöscht werden sollen, nachdem alle Veröffentlichungen erfolgreich waren. + + + Einstellungen übernehmen von + + + Legt das Exportprofil fest, von welchem die Einstellungen übernommen werden sollen. + + + Dies ist eine automatische Benachrichtung von Shop "{0}" über einen erfolgten Datenexport. + + + Export von Profil "{0}" ist abgeschlossen + + + E-Mail Adressen an + + + Legt die E-Mail Adressen fest, an die die Benachrichtigung geschickt werden soll. + + + Die folgenden spezifischen Angaben werden durch den Provider beim Export berücksichtigt. + + + Der Export-Provider <b>{0}</b> benötigt keine weitergehende Konfiguration. + + + ZIP-Archiv erstellen + + + Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst werden sollen. Das Archiv verbleibt im temporären Ordner des Exportprofils ohne weitere Vearbeitung. + + + Mindestens eine Datei konnte nicht kopiert werden. + + + Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst und nur das Archiv bereitgestellt werden soll. + + + Art der Veröffentlichung + + + Legt die Art Veröffentlichung fest. + + + E-Mail Konto + + + Legt das E-Mail Konto fest, über welches die Daten verschickt werden sollen. + + + E-Mail Adressen an + + + Legt die E-Mail Adressen fest, an die die Daten verschickt werden soll. + + + E-Mail Betreff + + + Legt den Betreff der verschickten Daten fest. + + + Ordnerpfad + + + Legt den Pfad (relativ oder absolut) zu einem Ordner fest, in den die Daten bereitgestellt werden sollen. + + + HTTP Übertragungsart + + + Legt fest, aus welcher Art die Exportdateien per HTTP übertragen werden sollen. + + + Legt fest, ob die exportierten Daten in einen übers Internet zugänglichen Ordner kopiert werden sollen. + + + Name des Profils + + + Legt den Namen des Veröffentlichungsprofils fest. + + + Es liegen keine Veröffentlichungsprofile vor. + + + Legen Sie über <b>Neues Profil</b> ein oder mehrere Veröffentlichungsprofile an, um festzulegen wie mit den Exportdateien weiter zu verfahren ist. + + + Passiver Modus + + + Legt fest, ob Daten im aktiven oder passiven Modus ausgetauscht werden sollen. + + + Passwort + + + Legt das Passwort fest. + + + Veröffentlichungsprofile + + + Veröffentlichungsziel + + + Name des Unterordners + + + Legt den Namen eines Unterordners fest, in den die Daten veröffentlicht werden sollen. + + + URL\Host + + + Legt die URL bzw. den Host-Namen fest, an die die Daten übermittelt werden sollen. + + + Benutzername + + + Legt den Benutzernamen fest. + + + SSL verwenden + + + Legt fest, ob einen SSL (Secure Sockets Layer) Verbindung genutzt werden soll. + + + Bei einer großen Anzahl an Exportdateien wird empfohlen die Option <b>ZIP-Archiv erstellen</b> zu benutzen. Das spart Zeit und vermeidet Probleme, wie z.B. ein volles E-Mail Postfach. + + + E-Mail Benachrichtigung + + + Legt das E-Mail Konto fest, über welches eine Benachrichtigung über die Fertigstellung des Exports verschickt werden soll. + + + Das Exportprofil ist deaktiviert. Für eine Exportvorschau muss das Exportprofil aktiviert sein. + + + Objekt + + + Der Objekttyp, den der Provider verarbeitet. + + + Exportdateien + + + Dateityp + + + Der Dateityp der exportierten Daten. + + + Muster für Dateinamen + + + Legt das Muster fest, nach dem Dateinamen erzeugt werden. + + + Bitte ein gültiges Muster für Dateinamen eingeben. Beispiel: %Store.Id%-%Profile.Id%-%File.Index%-%Profile.SeoName% + + + ID des Exportprofils;Ordername des Exportprofils;SEO Name des Exportprofils;Shop ID;SEO Name des Shops;Mit 1 beginnender Dateiindex;Zufallszahl;UTC Zeitstempel + + + Verfügbar bis + + + Nach der Verfügbarkeitsmenge filtern. + + + Verfügbar von + + + Nach der Verfügbarkeitsmenge filtern. + + + Rechnungsländer + + + Nach Rechnungsländern filtern. + + + Warengruppen + + + Nach Warengruppen filtern. + + + Erstellt von + + + Nach dem Erstellungsdatum filtern. + + + Erstellt bis + + + Nach dem Erstellungsdatum filtern. + + + Kundengruppen + + + Nach Kundengruppen filtern. + + + Nur empfohlene Produkte + + + Nach empfohlenen Produkten filtern. Wird nur bei der Filterung nach Warengruppen und Hersteller angewendet. + + + Hat x Bestellungen + + + Nach der Anzahl der getätigten Bestellungen filtern. + + + Hat Betrag x ausgegeben + + + Nach dem insgesamt ausgegebenen Betrag filtern. + + + Produkt-ID bis + + + Nach der Produkt-ID filtern. + + + Produkt-ID von + + + Nach der Produkt-ID filtern. + + + Nur aktive Kunden + + + Nach aktiven bzw. inaktiven Kunden filtern. + + + Nur aktive Abonnenten + + + Nach aktiven bzw. inaktiven Newsletter Abonnenten filtern. + + + Veröffentlicht + + + Nach Veröffentlichung filtern. + + + Nur MwSt. befreite Kunden + + + Nach MwSt. befreiten Kunden filtern. + + + Zuletzt aktiv von + + + Nach dem Datum der letzten Shop Aktivität filtern. + + + Zuletzt aktiv bis + + + Nach dem Datum der letzten Shop-Aktivität filtern. + + + Hersteller + + + Nach Hersteller filtern. + + + Legen Sie individuelle Filter fest, um die zu exportierenden Daten einzugrenzen. + + + Auftragsstatus + + + Nach Auftragsstaus filtern. + + + Zahlungsstatus + + + Nach Zahlungsstatus filtern. + + + Preis bis + + + Nach dem Preis filtern. + + + Preis von + + + Nach dem Preis filtern. + + + Produkt-Tag + + + Nach Produkt-Tag filtern. + + + Produkttyp + + + Nach Produkttyp filtern. + + + Versandländer + + + Nach Versandländern filtern. + + + Versandstatus + + + Nach Versandstatus filtern. + + + Shop + + + Nach Shop filtern. + + + Ohne Warengruppenzuordnung + + + Nach fehlender Warengruppenzuordnung filtern. + + + Ohne Herstellerzuordnung + + + Nach fehlender Herstellerzuordnung filtern. + + + Ordnerpfad + + + Legt den relativen Pfad des Ordners fest, in den die Daten exportiert werden. + + + Bitte einen gültigen, relativen Ordnerpfad für die zu exportierenden Daten eingeben. + + + Systemprofil + + + Gibt an, ob es sich bei dem Exportprofil um eine Systemprofil handelt. Systemprofile können nicht entfernt werden. + + + Das System-Exportprofil {0} wurde nicht gefunden. + + + Name des Profils + + + Legt den Namen des Exportprofils fest. + + + Es wurden keine Export-Provider gefunden. + + + Möglichkeiten der Filterung stehen nicht zur Verfügung. + + + Der Export-Provider unterstützt keinen expliziten Dateityp. Für eine weitere Bereitstellung der Exportdaten ist daher der Export-Provider verantwortlich. + + + Eine Vorschau steht für diesen Entitätstyp nicht zur Verfügung. + + + Es wurden keine Exportprofile gefunden. + + + Es wurde kein Exportprofil vom Typ <b>{0}</b> gefunden. Jetzt ein <a href="{1}">neues Exportprofil anlegen</a>. + + + Möglichkeiten der Projektion stehen nicht zur Verfügung. + + + Diese Option wird in der Vorschau nicht berücksichtigt. + + + Mit den folgenden Einstellungen lassen sich die zu exportierenden Daten aufteilen. Dazu zählt<ul><li>Das Überspringen der ersten n Datensätze</li><li>Die maximale Anzahl zu exportierender Datensätze</li><li>Die Anzahl der Datensätze pro Exportdatei</li><li>Daten von jedem Shop in eine separate Datei exportieren</li></ul>Standardmäßig werden alle Daten eines Shops in eine Datei exportiert. + + + Einstellungen zur Aufteilung müssen größer oder gleich 0 sein. + + + Per Shop + + + Legt fest, ob für jeden Shop ein separater Verarbeitungsdurchlauf erfolgen soll. Für jeden Shop wird eine neue Datei erzeugt. + + + Exportprofil + + + Das Exportprofil für diesen Export-Provider. + + + {0} von {1} Datensätzen exportiert + + + Anzuhängender Text + + + Legt den an die Artikelbeschreibung anzuhängenden Text fest. Bei mehreren Texten wird einer per Zufall ausgewählt. + + + Attributkombinationen exportieren + + + Legt fest, ob für jede aktive Attributkombination ein eigenständiges Produkt exportiert werden soll. + + + Attributwerte + + + Legt fest, ob und wie die Werte der Attribute weiter verarbeitet werden sollen. + + + Hersteller\Marke + + + Legt den zu exportierenden Hersteller bzw. die Marke fest, wenn für ein Produkt kein Hersteller zugeordnet ist. + + + Netto- in Bruttopreise umrechnen + + + Legt fest, dass Netto- in Bruttopreise umgerechnet werden sollen. + + + Kritische Zeichen + + + Liste mit Zeichen, die aus der Detailbeschreibung entfernt werden sollen. + + + Währung + + + Legt die auf den Export anzuwendende Währung fest. + + + Kunden-ID + + + Legt die ID des Kunden fest, auf den sich der Export beziehen soll. Wird z.B. bei Preisberechnungen berücksichtigt. + + + Artikelbeschreibung + + + Legt fest, welche Informationen zur Beschreibung des Artikel wie verwendet werden sollen. + + + HTML aus der Beschreibung entfernen + + + Legt fest, ob für den Export alle HTML-Auszeichnungen aus der Artikelbeschreibung entfernt werden sollen. + + + Kostenloser Versand ab + + + Legt den Betrag fest, ab dem keine Versandkosten anfallen. + + + Sprache + + + Legt die auf den Export anzuwendende Sprache fest. + + + Keine Gruppenprodukte exportieren + + + Legt fest, ob Gruppenprodukte exportiert werden sollen. Ist diese Option aktiviert, so werden die zur Gruppe gehörenden Produkte exportiert. + + + Die folgenden Angaben werden beim Export berücksichtigt und an entsprechenden Stellen in den Vorgang eingebunden. + + + Anzahl der Bilder + + + Legt die Anzahl der zu exportierenden Bilder pro Objekt fest. + + + Auftragsstatus ändern in + + + Legt fest, ob und wie der Status der exportierten Aufträge geändert werden soll. + + + Produktbildgröße + + + Legt die Größe (in Pixel) des Produktbildes fest. + + + Produktpreis + + + Legt den zu exportierenden Produktpreis fest. + + + Kritische Zeichen entfernen + + + Legt fest, ob kritische Zeichen (wie z.B. ½) aus der Detailsbeschreibung entfernt werden sollen. + + + Versandkosten + + + Die zu exportierenden Versandkosten. + + + Lieferzeit + + + Legt die Lieferzeit für Produkte fest, wo diese nicht angegeben ist. + + + Shop + + + Legt den auf den Export anzuwendenden Shop fest. + + + Provider + + + Legt den Export-Provider fest. Er ist für die individuelle Formatierung der zu exportierenden Daten zuständig. + + + Systemname des Profils + + + Der Systemname des Exportprofils. + + + Die folgende Liste enthält Systemprofile, die von Plugins wie bspw. dem <a href='http://community.smartstore.com/index.php?/files/file/85-smartstorenet-common-export-providers/' target='_blank'>Datenexporte Plugin</a> bereitgestellt werden. Sie können Systemprofile nach Belieben anpassen, aber keine Neuen erstellen. Für diese Profile stehen außerdem zusätzliche Aktions-Buttons zur Verfügung. Sie finden diese über den entsprechenden Listen, wie z.B. der Produkt- oder Auftragsliste. + + + Systemprofile + + + Benutzerprofile + + + Importdatei hinzufügen... + + + Zuordnung der Importfelder + + + Dies ist eine automatische Benachrichtung von Shop "{0}" über einen erfolgten Datenimport. Zusammenfassung: + + + Import von "{0}" ist abgeschlossen + + + Mein Produktimport;Mein Warengruppenimport;Mein Kundenimport;Mein Newsletter-Abonnement-Import + + + Importdatei hochladen... + + + Schlüsselfelder + + + Anhand von Schlüsselfeldern können vorhandene Datensätze zwecks Aktualisierung identifiziert werden. Die Schlüsselfelder werden in der hier festgelegten Reihenfolge verarbeitet. + + + Benutzen Sie das ID-Feld bitte nur dann als Schlüsselfeld, wenn die Daten aus der derselben Datenbank stammen, in der sie importiert werden sollen. Ansonsten werden u.U. die falschen Datensätze aktualisiert. + + + Letztes Importergebnis + + + Bitte laden Sie eine Importdatei hoch. + + + Bei mehreren Importdateien ist darauf zu achten, dass diese vom selben Dateityp sind und deren Inhalt demselben Schema folgt (z.B. gleiche Spaltenüberschriften). + + + Name des Profils + + + Legt den Namen des Importprofils fest. + + + Es wurden keine Importprofile gefunden. + + + Anzahl der Bilder + + + Legt die Anzahl der zu importierenden Bilder pro Objekt fest. + + + Wählen Sie bitte das zu importierende Objekt und laden Sie eine Importdatei hoch. + + + {0} von {1} Datensätzen verarbeitet + + + Hier neue Zuordnung vornehmen + + + Nur aktualisieren + + + Ist diese Option aktiviert, werden nur vorhandene Daten aktualisiert, aber keine neue Datensätze hinzugefügt. + + + Es ist mindestens ein Schlüsselfeld erforderlich. + Herunterladen der hochgeladenen Datei @@ -8155,8 +9607,11 @@ Hilfe + + Dokumentation + - Community-Forum + Community SmartStore.NET ist ein Fork der ASP.NET Open-Source E-Commerce-Lösung {0}. @@ -8182,6 +9637,9 @@ Der Kunde hat folgenden Kommentar für diese Bestellung hinterlassen + + Ein Auftrag mit dieser Nummer wurde nicht gefunden. + "Auftrag storniert" E-Mail (an Kunden) wurde gequeued. Queued Email ID: {0} @@ -8200,6 +9658,12 @@ "Auftrag eingegangen" E-Mail (an Shopbetreiber) wurde gequeued. Queued Email ID: {0} + + Newsletter wurde abonniert + + + Newsletter-Abonnent wurde entfernt + Auftrag wurde storniert @@ -8207,7 +9671,7 @@ Zahlung wurde erfasst - Fehler bei Zahlungserfassung für Auftrag #{0}. Fehler: {1} + Es ist ein Fehler bei der Zahlungsbuchung zu Auftrag {0} aufgetreten. Auftrag wurde gelöscht @@ -8231,7 +9695,7 @@ Auftrag wurde teilweise erstattet. Betrag: {0} - Fehler bei Teilerstattung: {0} + Es ist ein Fehler bei einer Teilerstattung zu Auftrag {0} aufgetreten. Auftrag ist eingegangen @@ -8240,7 +9704,7 @@ Auftrag wurde erstattet. Betrag: {0} - Fehler bei Erstattung: {0} + Es ist ein Fehler bei einer Rückerstattung zu Auftrag {0} aufgetreten. Auftragsstatus geändert. Neuer Status: {0} @@ -8249,10 +9713,10 @@ Zahlungstransaktion wurde storniert - Fehler bei Stornierung der Zahlungstransktion: {0} + Es ist ein Fehler bei der Stornierung einer Zahlungstransaktion zu Auftrag {0} aufgetreten. - Konnte wiederkehrende Zahlung nicht stornieren. Fehler: {0} + Es ist ein Fehler bei der Stornierung einer wiederkehrenden Zahlung für Auftrag {0} aufgetreten. Wiederkehrende Zahlung wurde storniert @@ -8314,6 +9778,12 @@ Auftragsdetails bearbeiten + + Akzeptiert Weitergabe der E-Mail + + + Gibt an, ob der Kunde bei der Bestellung einer Weitergabe seiner E-Mail Adresse an Dritte zugestimmt hat oder nicht. + Partner @@ -8321,10 +9791,10 @@ Das Partner-Unternehmen, dem dieser Auftrag zugeordnet ist. - Authorizations-Transaktions-ID + Transaktions-ID für Autorisierung - Vom Zahlungsdienstleister erhaltene Authorizations-Transaktions-ID. + Vom Zahlungsanbieter erhaltene Transaktions-ID für die Autorisierung. Transaktionsergebnis für Autorisierung @@ -8347,11 +9817,14 @@ Buchen + + Zieht eine zuvor reservierte Zahlung über den Zahlungsanbieter ein. + - Transaction ID buchen + Transaktions-ID für Buchung - Über vom Zahlungsdienstleister erhaltenene transaction id buchen. + Vom Zahlungsanbieter erhaltene Transaktions-ID für die Buchung. Transaktionsergebnis für Buchung @@ -8518,11 +9991,14 @@ Auftrag bezahlt + + Setzt den Zahlungsstatus auf 'Bezahlt' ohne dabei den Zahlungsanbieter zu kontaktieren. + - Auftrags-GUID + Bestellreferenznummer - Interne Referenz für den Auftrag. + Die interne Bestellreferenznummer. Im Gegensatz zur Auftragsnummer existiert diese bereits im Kassenbereich, d.h. vor der eigentlichen Erstelllung des Auftrags. Auftragsnummer @@ -8605,17 +10081,23 @@ Der maximale Erstattungsbetrag ist {0} {1} + + Leitet die Rückerstattung eines Teilbetrages des Auftragswertes beim Zahlungsanbieter ein. + Erstattungsbetrag für Auftrag #{0} - Erstattungsbetrag (Offline) + Teilerstattung (Offline) + + + Setzt den Zahlungsstatus auf 'Teilweise erstattet' samt Erstattungsbetrag, ohne dabei den Zahlungsanbieter zu kontaktieren. - Zahlungsmethode + Zahlungsart - Die Zahlungsmethode für diese Transaktion + Die Zahlungsart für diese Transaktion Die Zahlungsgebühr (netto) für diesen Auftrag @@ -8659,6 +10141,9 @@ Erstattung + + Leitet eine Rückerstattung des gesamten Auftragswertes beim Zahlungsanbieter ein. + Erstatteter Betrag @@ -8668,6 +10153,9 @@ Erstattung (Offline) + + Setzt den Zahlungsstatus auf 'Erstattet', ohne dabei den Zahlungsanbieter zu kontaktieren. + Sichere Kreditkarte @@ -8723,10 +10211,16 @@ Benutzte Umsatzsteuer-Identifikationsnummer. - Ungültig + Stornieren + + + Leitet die Stornierung der Zahlungstransaktion beim Zahlungsanbieter ein. - Ungültig (offline) + Stornieren (Offline) + + + Setzt den Zahlungsstatus auf 'Storniert', ohne dabei den Zahlungsanbieter zu kontaktieren. Information @@ -8842,6 +10336,12 @@ Notiz + + Neue IPN + + + In den Auftragsnotizen ist eine neue Benachrichtigung vom Zahlungsanbieter eingetroffen. + Auftrag als PDF @@ -8884,6 +10384,12 @@ Geben Sie den Gesamtbetrag (inklusive Steuer) ein. + + Steuersatz + + + Die Steuerrate des Produktes + Ein neues Produkt zu Auftrag #{0} hinzufügen @@ -9163,6 +10669,9 @@ Plugins + + Add-on Lizenzierung + Administration @@ -9220,9 +10729,24 @@ Steuern + + Demo abgelaufen + + + Demo + + + Demo {0} Tag(e) verbleibend + + + Prüfung erneuern + Plugins verwalten + + Bitte beachten Sie, dass der tatsächliche Grundpreis von verschiedenen Faktoren abhängig ist und erst im Shop zuverlässig berechnet werden kann. + Promotion Feeds @@ -9472,8 +10996,8 @@ E-Mail-Adresse erforderlich - - Es wurden {0} E-Mail(s) importiert und {1} aktualisiert. + + Abonnement GUID Bereitstellendes Plugin @@ -9661,6 +11185,12 @@ Die angeforderte Aktion. + + Letzte Aktualisierung der angeforderten Aktion + + + Datum, an dem die angeforderte Aktion zuletzt geändert wurde. + Mitarbeiternotizen @@ -9994,6 +11524,9 @@ Fügen Sie die SQL-Abfrage hier ein: + + Die SQL Anweisung wurde erfolgreich ausgeführt. + Ausführen @@ -10006,6 +11539,9 @@ Zurück zur Übersicht + + E-Mail Anhang konnte nicht herunterladen: Daten nicht verfügbar. + Die E-Mail wurde gelöscht @@ -10015,6 +11551,18 @@ E-Mail bearbeiten + + Während der Erstellung des E-Mail-Anhangs ist ein Fehler aufgetreten + + + Daten für den E-Mail Anhang konnten nicht heruntergeladen werden. Pfad: {0} + + + Der Inhaltstyp des E-Mail Anhangs muss 'application/pdf' sein + + + Anhänge + BCC @@ -10075,6 +11623,12 @@ Priorität eingeben + + Nur manuell senden + + + Legt fest, ob die E-Mail ausschließlich manuell gesendet werden soll. + Gesendet um @@ -10114,6 +11668,9 @@ Der Name des Empfängers. + + Anzahl Anhänge + Enddatum @@ -10136,7 +11693,7 @@ Lade nur noch nicht gesendete E-Mails - Zeige E-Mails nur aus der Warteschlange. + Lade nur noch nicht gesendete E-Mails. Maximale Sendeversuche @@ -10144,6 +11701,12 @@ Gibt die Anzahl der Zustellversuche an. + + Lade nur manuell zu sendende E-Mails + + + Lade nur manuell zu sendende E-Mails. + Anfangsdatum @@ -10160,82 +11723,169 @@ Wiederholen - Die Nachricht wird erneut gesendet + Die Nachricht wurde erfolgreich neu eingereiht. Die E-Mail wurde erfolgreich bearbeitet + + Geplante Aufgabe + - Aufgabenplanung + Geplante Aufgaben + + + Abbruch erzwungen durch Herunterfahren der Anwendung + + + Die geplante Aufgabe "{0}" wurde abgebrochen + + + Abbruchanforderung wurde übermittelt. + + + Cron Ausdruck + + + Ein Ausdruck, der den Zeitplan für die automatische Ausführung der Aufgabe festlegt. + + + Hilfe zu <a href='{0}' target='_blank'>Cron-Ausdrücken</a> + + + Dauer der letzten Ausführung ([Std.]:[Min.]:[Sek.]) + + + Aufgabe bearbeiten Aktiviert + + Aktiviert die geplante Ausführung der Aufgabe gemäß Cron Ausdruck + + + Zukünftige Zeitpläne + + + Der Cron-Ausdruck ist ungültig + Zuletzt beendet Letzte Ausführung + + Startdatum der letzten Ausführung + Letzte erfolgreiche Ausführung + + Startdatum der letzten erfolgreichen Ausführung + Name Ein Name für die Aufgabe ist erforderlich. - - Nach dem eine Aufgabe verändert wurde, ist ein Neustart erforderlich. + + Nächste Ausführung in + + + Datum der nächsten geplanten Ausführung + + + Fehler beim Ausführen der Aufgabe "{0}" Jetzt ausführen - - Ausführung der Aufgabe abgeschlossen - Wird ausgeführt... Aufgabe wird jetzt im Hintergrund ausgeführt - - Sekunden (Intervall) + + Die Aufgabe wird jetzt im Hintergrund ausgeführt. Sie erhalten eine E-Mail, sobald sie abgeschlossen ist. Den Fortschritt können Sie in der Exportprofilliste verfolgen. + + + Die Aufgabe wird jetzt im Hintergrund ausgeführt. Sie erhalten eine E-Mail, sobald sie abgeschlossen ist. Den Fortschritt können Sie in der Importprofilliste verfolgen. - - Sekunden müssen größer als 0 sein. + + Aufgabe wurde erfolgreich ausgeführt + + + Ausführung planen - Bei Fehler anhalten + Bei Fehler deaktivieren + + + Aktivieren Sie das Kästchen, wenn die Aufgabe bei Auftreten eines Fehlers während der Ausführung deaktiviert werden soll + + + Die Aufgabe kann nicht bearbeitet werden, während sie ausgeführt wird. + + + Die Aufgabe wurde erfolgreich bearbeitet SEO Namen - - Lösche die ausgewählten Elemente + + Pro Sprache darf nur ein aktiver SEO Name festgelegt werden. - ID der Entität + Objekt-ID + + + Legt die ID des zugehörigen Objektes fest. - Name der Entität + Objekt + + + Legt den Namen der zugehörigen Objektes fest. Ist aktiv + + Legt fest, ob der SEO Name aktiv oder inaktiv ist. + Sprache + + Legt die Sprache des SEO Namens fest. + Standard - Name + SEO Name - Der zu findende Name. + Legt den SEO Namen fest. + + + Namen pro Objekt + + + Die Anzahl der SEO Namen pro Objekt. + + + Geplante Aufgabe + + + PDF-Konvertierer + + + Suchmaschine Systeminformation @@ -10270,6 +11920,12 @@ Der Name des Daten-Providers. + + Aufräumen + + + Der Arbeitsspeicher wurde erfolgreich aufgeräumt. + HTTP_HOST @@ -10306,6 +11962,9 @@ Zeitzone des Servers + + Benutzter Speicher (RAM) + GMT/UTC @@ -10315,6 +11974,12 @@ Warnungen + + Zugriffsverweigerung durch anonyme Anfrage bei {0}. + + + Zugriffsverweigerung durch Kunde #{0} '{1}' bei {2}. + Die Standard-Maßeinheit wurde nicht festgelegt. @@ -10333,6 +11998,9 @@ Die Standard-Gewichtseinheit wurde festgelegt. + + Bitte nur Ziffern eingeben. + Alle Verzeichnisberechtigungen sind OK. @@ -10357,11 +12025,20 @@ '{0}' Plugin ist nicht kompatibel mit Ihrer SmartStore.NET-Version. Löschen Sie es oder installieren Sie die richtige Version. + + Es sind keine Kundengruppen festgelegt. + + + Es sind keine Zugriffsrechte festgelegt. + + + Keine Versand-Artikel + - Es gibt keine aktiven Zahlungsmethoden. + Es existieren keine aktiven Zahlungsarten. - Die Zahlungsmethoden sind OK. + Die Zahlungsarten sind OK. Die Hauptwährung für den Shop wurde nicht festgelegt. @@ -10372,6 +12049,15 @@ Es wird empfohlen, nur eine Versandkosten-Offline-Berechnungsmethode zu benutzen. + + Die Erreichbarkeit der Sitemap konnte nicht überprüft werden. + + + Die Sitemap für den Shop ist erreichbar. + + + Die Sitemap für den Shop ist nicht erreichbar. + Die angegebene URL stimmt mit der URL des Shops überein. @@ -10657,6 +12343,21 @@ Tabellen/Container + + Bitte geben Sie eine gültige E-Mail Adresse ein + + + Bitte geben Sie einen Namen ein + + + Bitte geben Sie eine gültige URL ein + + + Bitte geben Sie Benutzername und Passwort ein + + + Der Wert muss größer 0 sein. + API ist nicht erreichbar. @@ -10819,6 +12520,9 @@ Zur Kasse + + Ein anonymer Checkout ist nicht zulässig. + Rechnungsanschrift @@ -10832,16 +12536,19 @@ Kaufen - Bitte prüfen Sie die Gesamtsumme und die Rechnungsadresse. Bei abweichender Lieferanschrift prüfen Sie bitte auch diese. Änderungen können Sie jederzeit mit einem Klick auf <STRONG>[Zurück]</STRONG> vornehmen. Sind alle Daten richtig, bestätigen Sie bitte mit einem Klick auf <STRONG>[Jetzt kostenpflichtig Kaufen]</STRONG> Ihre Bestellung. + Bitte prüfen Sie die Gesamtsumme und die Rechnungsadresse. Bei abweichender Lieferanschrift prüfen Sie bitte auch diese. Änderungen können Sie jederzeit mit einem Klick auf <strong>zurück</strong> vornehmen. Sind alle Daten richtig, bestätigen Sie bitte mit einem Klick auf <strong>kaufen</strong> Ihre Bestellung. Auftrag bestätigen - Bitte Bestätigen Sie Ihren Auftrag. + Bitte bestätigen Sie Ihren Auftrag. - Zurück zum Shop + Weiter einkaufen + + + Bitte stimmen Sie der Nutzungsvereinbarung für herunterladbare Produkte zu. Rechnungsanschrift eingeben @@ -10849,6 +12556,12 @@ Versandanschrift eingeben + + Ja, ich möchte sofort Zugang zu dem digitalen Inhalt und weiß, dass mein Widerrufsrecht mit dem Zugang erlischt. + + + Bitte bestätigen Sie, dass Sie sofort Zugang zu dem digitalen Inhalt wünschen. + Bitte warten Sie einige Sekunden, bevor Sie einen neuen Auftrag platzieren. @@ -10865,10 +12578,13 @@ Weiter - Keine Zahlungsmethoden verfügbar. + Es sind keine Zahlungsarten verfügbar. + + + Ihre Bestellung ist angekommen - Auftragsnummer + Ihre Bestellnummer Auftragszusammenfassung @@ -10880,10 +12596,10 @@ Zahlungsinformation - Zahlungsmethode + Zahlungsarten - Auftragsdetails + Bestelldetails Adresse @@ -10939,6 +12655,9 @@ Übermittele Auftragsinformationen + + Newsletter abonnieren + Geschäftsbedingungen (AGB) @@ -10952,14 +12671,11 @@ Lesen - Danke! + Vielen Dank für Ihren Einkauf! Benutze meine Bonuspunkte, {0} Bonuspunkte ({1}) verfügbar - - Ihr Auftrag wurde erfolgreich übermittelt. - Aktiv @@ -11026,6 +12742,9 @@ Abgebrochen + + Der API-Aufruf zur Prüfung eines CAPTCHAs ist fehlgeschlagen. + Produkt bearbeiten @@ -11047,6 +12766,15 @@ Fortsetzen + + In die Zwischenablage kopieren + + + Kopieren ist fehlgeschlagen. + + + Kopiert! + Anzahl @@ -11059,6 +12787,12 @@ Querverweise + + Die von Ihnen gewählte Kundennummer existiert bereits. Bitte geben Sie eine andere Kundennummer an. + + + Datum + Tag @@ -11074,30 +12808,87 @@ Sind Sie sicher "{0}" zu löschen? + + Bereitstellung + Beschreibung Beschreibung + + Detailbeschreibung + Reihenfolge + + Download + + + Für die Produktvariante ist der Download einer Beispieldatei nicht verfügbar. + + + Sie haben die maximale Anzahl an Downloads {0} erreicht. + + + Es sind keine Daten zum Herunterladen mehr verfügbar. + + + Downloads sind nicht gestattet. + + + Der Download ist nicht mehr verfügbar. + + + Der Download einer Beispieldatei ist nicht mehr verfügbar. + Dauer Bearbeiten + + Aktiviert + Wert + + Klicken Sie auf ein Element, um es aus- bzw. abzuwählen und OK, um die Auswahl zu übernehmen. + + + Es wurden keine weiteren Elemente gefunden. + + + Klicken Sie auf ein Element, um es auszuwählen und OK, um es zu übernehmen. + Fehler + + Die E-Mail-Adresse ist ungültig. + + + Es wurde keine aktive Sprache gefunden. + + + Es wurde kein E-Mail-Konto gefunden. + + + Die gewählte Zahlungsart verursachte leider einen Fehler. Bitte korrigieren Sie Ihre Eingaben, versuchen Sie es erneut oder wählen Sie eine andere Zahlungsart. + Fehler beim Versenden der Email. Bitte versuchen Sie es später erneut. + + Beispiel + + + Ausführung + Exportieren @@ -11122,12 +12913,21 @@ Dateien für den Upload hier ablegen + + URL eingeben + Fehlgeschlagen Datei hochladen + + Filter + + + Versandkostenfrei + Anzeigename @@ -11143,6 +12943,9 @@ Startseite + + Bild + Importieren @@ -11152,18 +12955,33 @@ Aktiv + + Sprache + Liste + + Lade + Lade nächsten Schritt… + + Diese Funktion steht für Gäste nicht zur Verfügung. + Verschiedenes Monat + + Mehr Info + + + Mein + Navigation @@ -11179,6 +12997,12 @@ Nein, abbrechen + + Es sind keine Dateien vorhanden. + + + Es wurde keine Datei hochgeladen. + Der Vorgang wurde aus Sicherheitsgründen nicht ausgeführt. @@ -11188,6 +13012,9 @@ Benachrichtigung + + Nicht auswählbar + Aus @@ -11203,6 +13030,12 @@ Optional + + Optionen + + + Aufteilung + Capture wird nicht unterstützt @@ -11227,6 +13060,24 @@ Artikel + + Profil + + + Projektion + + + Provider + + + öffentlich + + + Veröffentlicht + + + Veröffentlichung + Frage @@ -11239,9 +13090,21 @@ Entfernen + + Ersetzen + + + Die Anfrage konnte nicht ausgeführt werden.<br />Controller: {0}, Action: {1}, Grund: {2}. + + + Regel + Speichern + + Geplant + Suchen @@ -11275,17 +13138,41 @@ Anzeigen + + Alle anzeigen + + + Mehr anzeigen + + + Verkleinern + + + Die Datenbank wurde erfolgreich verkleinert + Absenden Systemname + + Nicht verfügbar + undefiniert + + Unbekannt + + + Unveröffentlicht + + + Ungeplant + - nicht spezifiziert + Nicht spezifiziert Geändert am @@ -11302,11 +13189,17 @@ Bitte warten... + + Wartend + Warnung + + Web-Seite + - Die von Ihnen eingegebenen Zeichen stimmen nicht mit dem Bild überein. Bitte versuchen Sie es erneut. + Bitte bestätigen Sie, dass Sie kein "Roboter" sind. Falsche E-Mail @@ -11356,6 +13249,15 @@ Namen eingeben + + Einwilligungserklärung Datenschutz + + + Ja, ich habe die <a href="{0}">Datenschutzerklärung</a> 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. + + + Bitte stimmen Sie der Speicherung Ihrer Daten zu. + Ihre Anfrage wurde erfolgreich übermittelt. @@ -11365,15 +13267,15 @@ Währungen + + Der Kunde existiert nicht. + Gast Rechtshinweise - - Datum - Download @@ -11389,6 +13291,9 @@ Produkt + + Das Produkt besitzt keine Nutzungsvereinbarung. + Ich bin einverstanden. @@ -11398,6 +13303,9 @@ Nutzungsvereinbarung + + Ja, ich stimme der <a href='javascript:void(0)' data-id='{0}' class='download-user-agreement'>Nutzungsvereinbarung</a> für dieses Produkt zu. + Kontrollkästchen @@ -11464,6 +13372,18 @@ Lagerbestand mit Attributen führen + + Keine Preisanzeige + + + Minimal realisierbarer Preis + + + Auf der Detailseite vorgewählter Preis + + + Preis ohne Rabatte und Attribute + Erstellt am @@ -11515,6 +13435,15 @@ Jahre + + Über der Produktliste + + + Am Seitenende + + + Nicht anzeigen + Benutze CONTAINS und AND mit prefix_term @@ -11545,6 +13474,27 @@ Benutzernamen anzeigen + + Automatisch vergeben + + + Deaktiviert + + + Aktiviert + + + Anzeigen + + + Immer editierbar + + + Editierbar falls leer + + + Nicht anzeigen + Lesbar @@ -11554,6 +13504,102 @@ Gehashed + + Alle Werte an den Produktnamen anhängen + + + Nicht spezifiziert + + + E-Mail + + + Dateisystem + + + FTP + + + HTTP POST + + + Öffentlicher Ordner + + + Detailbeschreibung + + + Hersteller + Produktname + Detailbeschreibung + + + Hersteller + Produktname + Kurzbeschreibung + + + Produktname + Detailbeschreibung + + + Produktname + Kurzbeschreibung + + + Keine + + + Kurzbeschreibung + + + Kurzbeschreibung oder Name falls leer + + + Warengruppe + + + Kunde + + + Hersteller + + + Newsletter Abonnenten + + + Auftrag + + + Produkt + + + Multipart-Form-Data POST + + + Einfacher POST + + + Komplett + + + Keine + + + Wird bearbeitet + + + Warengruppe + + + Kunde + + + Newsletter Abonnent + + + Produkt + + + Trennzeichen getrennte Werte (.csv, .txt, .tab) + + + Excel (.xlsx) + Nur N mal @@ -11638,6 +13684,24 @@ Warnung + + Aktiviert anzeigen + + + Deaktiviert anzeigen + + + Nicht anzeigen + + + Aktiviert anzeigen + + + Deaktiviert anzeigen + + + Nicht anzeigen + Abgebrochen @@ -11710,6 +13774,15 @@ Standard-Konto-Eröffnung + + Nicht anwenden + + + www-Präfix weglassen + + + www-Präfix erzwingen + Geliefert @@ -11786,10 +13859,11 @@ Nicht unterstützt - Unser Support-Team wurde über diesen Fehler informiert und wird sich in Kürze um die Behebung kümmern. Wir entschuldigen uns für diese Unannehmlichkeit! Bitte probieren Sie den Vorgang zu einem späteren Zeitpunkt erneut. + Leider ist ein Serverfehler aufgetreten, und das hat nichts mit Ihrem Computer oder Ihrem Internetanschluss zu tun. + Unser Support Team wurde bereits benachrichtigt und wird sich sehr bald um die Behebung kümmern. - Leider ist ein interner Fehler aufgetreten. + Oops! Resource name will be 'ExampleOfNestedResources' @@ -11917,9 +13991,6 @@ Forenthemen mit den neuesten Beiträgen - - {0} - Forum: {1} - Forumname @@ -12160,6 +14231,9 @@ Informationen + + Die Installationssprache '{0}' ist nicht registriert. + Bitte geben Sie eine gültige Kreditkartennummer ein. @@ -12314,7 +14388,7 @@ Zurück - Volle Ansicht der Site + Desktop Version Ansicht der mobilen Version @@ -12400,15 +14474,57 @@ Eine Bestätigungs-E-Mail wurde versandt. Vielen Dank. + + Tut uns leid! Diese Adresse gibt es auf unserer Website nicht. + + + 404 + Rechnungsanschrift + + Die Rechnungsanschrift fehlt. + + + Die Auftragssumme konnte nicht berechnet werden. + + + Die Versandkosten konnten nicht berechnet werden. + + + Der Auftrag kann nicht storniert werden. + + + Der Auftrag kann nicht gebucht werden. + + + Der Auftrag kann nicht als abgeschlossen markiert werden. + + + Der Auftrag kann nicht als bezahlt markiert werden. + + + Eine Teilrückerstattung ist für diesen Auftrag nicht möglich. + + + Eine Rückerstattung ist für diesen Auftrag nicht möglich. + + + Eine Stornierung dieses Auftrages ist nicht möglich. + Zahlung veranlassen Der Auftrag wurde noch nicht bezahlt. Um die Zahlung nun vorzunehmen, klicken Sie den Button *Zahlung veranlassen* + + Eine Rechnungslegung ist für das Land '{0}' unzulässig. + + + Ein Versand ist für das Land '{0}' unzulässig. + E-Mail @@ -12421,6 +14537,12 @@ Geschenkgutschein ({0}) + + Für die wiederkehrende Zahlung existiert kein Ausgangsauftrag. + + + Keine wiederkehrenden Produkte. + Notizen @@ -12430,6 +14552,9 @@ Notiz + + Der Auftrag {0} wurde nicht gefunden. + Auftragsnummer @@ -12446,11 +14571,14 @@ Gesamtsumme - Zahlungsmethode + Zahlungsart Zahlungsgebühr + + bestellung-{0}.pdf + Telefon @@ -12550,9 +14678,6 @@ Land - - Datum - Ereignis @@ -12565,6 +14690,9 @@ Versandanschrift + + Die Lieferanschrift fehlt. + Versandart @@ -12658,9 +14786,6 @@ Auftragsdetails - - Seite nicht gefunden - Passwort-Wiederherstellung @@ -12718,6 +14843,9 @@ Wunschliste per E-Mail an einen Freund senden + + Das Datum der nächsten Zahlung kann nicht ermittelt werden. + Kartencode @@ -12742,6 +14870,9 @@ Falsche Kartennummer + + Die Zahlungsart konnte nicht geladen werden. + Gültig bis @@ -12751,35 +14882,32 @@ Ablaufjahr ist erforderlich - - Wähle Kreditkarte aus - - - Zahlungsarten + + Die Zahlungsart steht nicht zur Verfügung. - - Strasse: {0} + + Mindestens ein Zahlungsart-Provider muss aktiviert sein. - - Adressszusatz: {0} + + Leider können wir diesen Einkauf nicht über die gewünschte Zahlungsart abwickeln. Bitte wählen Sie eine alternative Zahlungsoption aus, um Ihre Bestellung abzuschließen. - - Zahlungsinformation + + Wiederkehrende Zahlung ist inaktiv. - - Handelsregister: + + Wiederkehrende Zahlungen sind für die gewählte Zahlungsart nicht möglich. - - Firma: {0} + + Der Typ von wiederkehrender Zahlung wird nicht unterstützt. - - Erstellt am + + Wähle Kreditkarte aus - - Rabatt: + + Zahlungsarten - - Fax: {0} + + Handelsregister: Inhaber: {0} @@ -12788,7 +14916,7 @@ Kontonummer: {0} - Bankleitzahl: {0} + BLZ: {0} BIC: {0} @@ -12808,20 +14936,8 @@ Url: {0} - - Geschenkgutschein ({0}): - - - Name: {0} - - - Notiz - - Auftrag# {0} - - - Datum: {0} + Auftrag# Auftragsnotizen @@ -12832,18 +14948,6 @@ Notiz - - Auftragssumme gesamt: - - - Zahlungsmethode: {0} - - - Zahlungsgebühr - - - Telefon: {0} - Produkte @@ -12859,42 +14963,15 @@ Gesamt - - Auftragsnummer: {0} - - - {0} Bonuspunkte: - - - Versand: - - - Versandinformation: - - - Versand-Methode: {0} - - Artikelnummer - - - Zwischensumme: - - - Steuer: + Artikelnr. Steuer-Nr.: - - Steuer {0}%: - Ust-Id: - - Steuernummer: {0} - Strasse: {0} @@ -12904,6 +14981,9 @@ Firma: {0} + + Lieferdatum + Name: {0} @@ -12913,6 +14993,9 @@ Telefon: {0} + + Gelieferte Produkte + Produktname @@ -12923,22 +15006,40 @@ Sendung #{0} - Versandmethode: {0} + Versandart Artikelnummer - - Preis + + Trackingnummer - - Artikelnummer + + Gewicht + + + E-Mail + + + Fax + + + Telefon + + + Anschrift + + + Kontaktdaten Lagermenge - - Gewicht + + Eine Umfrageantwort {0} wurde nicht gefunden. + + + Die Umfrage ist nicht verfügbar. Nur registrierte Benutzer können abstimmen @@ -12961,12 +15062,12 @@ Datenschutzerklärung + + Private Nachrichten sind deaktiviert. + Posteingang - - Datum - Ausgewählte löschen @@ -13012,9 +15113,6 @@ An: - - Datum: - Ausgewählte löschen @@ -13090,11 +15188,17 @@ {0} am Lager. + + Nicht im Sortiment + Nicht am Lager - Grundpreis: {0} pro {1} + Inhalt: {0} {1} ({2} / {3} {1}) + + + {0} {1} ({2} / {3} {1}) Home @@ -13295,7 +15399,10 @@ Dieses Produkt ist ausverkauft. - Dieses Produkt ist ausverkauft. + Keine Bundle-Bestandteile vorhanden + + + Das Produkt {0} wurde nicht gefunden. Preis: @@ -13375,6 +15482,9 @@ Menge + + Die Produktvariante {0} wurde nicht gefunden. + Gewicht @@ -13420,6 +15530,9 @@ Der EZB-Wechselkursdienst kann nur genutzt werden, wenn der Wechselkurs-Währungscode auf EUR gesetzt ist. + + Der EZB-Wechselkursdienst kann nur genutzt werden, wenn der Wechselkurs-Währungscode auf EUR gesetzt ist. + EZB-Wechselkursdienst @@ -13468,9 +15581,6 @@ Bewertungen - - Datum - Vorliegende Bewertungen @@ -13513,6 +15623,9 @@ Sie können nicht Ihre eigene Bewertung beurteilen. + + Die Produktbewertung {0} wurde nicht gefunden. + Nur registrierte Benutzer können eine Bewertung verfassen. @@ -13546,9 +15659,6 @@ Sie haben aktuell {0} Bonuspunkte ({1}). - - Datum - Nachricht @@ -13630,6 +15740,18 @@ Die Mindestlänge für den Suchbegriff beträgt {0} Buchstaben. + + Diese Sendung wird bereits zugestellt. + + + Diese Sendung wird bereits ausgeliefert. + + + Die Berechnungsmethode für Versandkosten konnte nicht geladen werden. + + + Mindestens ein Provider zur Berechnung von Versandkosten muss aktiviert sein. + Versandinfos @@ -13648,6 +15770,9 @@ Erlaubte Mengen für dieses Produkt: {0} + + Ein unbekannter Fehler ist bei der Prüfung der ausgewählten Produktvariante aufgetreten. + Der Bundle-Bestandteil "{0}" wurde nicht gefunden. @@ -13759,6 +15884,9 @@ Die eingegebene Nummer des Geschenkgutscheines konnte nicht angewendet werden. + + Der Warenkorb ist deaktiviert. + Gesamt @@ -13862,7 +15990,7 @@ Dieses Produkt erfordert, dass das folgende Produkt auch bestellt wird: {0}. - Bitte auswählen {0} + Bitte '{0}' auswählen. Geben Sie eine gültige Absender-E-Mail-Adresse ein. @@ -13871,7 +15999,7 @@ Geben Sie einen gültigen Absender-Namen ein. - Die maximale Anzahl von Produkten in Ihrer Wunschliste wurde erreicht + Eine vollständige Liste aller Versandkosten finden Sie <a href="{0}">hier</a>. Artikelnummer @@ -13952,10 +16080,10 @@ Shop Name hinter Seiten Name - Dieser Shop ist zur Zeit geschlossen. + Wir sind bald wieder da. - Bitte Besuchen Sie uns später. + Wir aktualisieren gerade das Angebot in unserem Online-Shop. Die Seite ist demnächst wieder verfügbar. Theme für den Shop auswählen. @@ -13975,9 +16103,15 @@ * Alle Preise {0}, zzgl. <a href="{1}">Versandkosten</a> + + * Alle Preise {0}, zzgl. Versandkosten + {0} {1} {2} zzgl. <a href="{3}">Versandkosten</a> + + {0} {1} {2} zzgl. Versandkosten + {0} {1} {2} zzgl. <a href="{3}">Versandkosten</a> @@ -14344,15 +16478,57 @@ Well-Hintergrundfarbe + + Tag + + + Tg. + + + Tage + + + Tg. + Vor {0} Tagen + + Stunde + + + Std. + + + Stunden + + + Std. + Vor {0} Stunden + + Minute + + + Min. + + + Minuten + + + Min. + Vor {0} Minuten + + Monat + + + Monate + Vor {0} Monaten @@ -14374,9 +16550,33 @@ Vor einem Jahr + + Sekunde + + + Sek. + + + Sekunden + + + Sek. + Vor {0} Sekunden + + Woche + + + Wochen + + + Jahr + + + Jahre + Vor {0} Jahren @@ -14437,6 +16637,9 @@ Bitte geben Sie Ihre E-Mail-Adresse ein. + + Die Wunschliste ist deaktiviert. + Wunschliste ansehen diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt index 7e2473f0e7..f822a9cb76 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt @@ -1 +1 @@ -201411182202431_V21Final \ No newline at end of file +201605201911421_ExportRevision \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml index 1e48c85f6a..734f05e1de 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml @@ -135,6 +135,9 @@ No orders + + This is not your order. + Order Date @@ -240,6 +243,9 @@ Country + + Customer number + Date of birth @@ -486,6 +492,15 @@ Register + + The customer is already registered. + + + A search engine can't be registered. + + + A background task account can't be registered. + The specified email already exists @@ -594,6 +609,9 @@ Deleted a manufacturer ('{0}') + + Deleted order {0} + Deleted a product ('{0}') @@ -756,6 +774,9 @@ Email is required. + + Reenter email address + Fax number @@ -780,6 +801,9 @@ Phone number is required. + + * Input elements with asterisk are required and have to be filled out. + State / province @@ -870,6 +894,15 @@ Email is required. + + Reenter email address + + + The email address has to match the email address entered before + + + Please reenter your email address + Fax number @@ -1225,7 +1258,7 @@ Disallow filtering - Show not on product page + Don't show on product page Show on product page @@ -1293,6 +1326,9 @@ You need to save the specification attribute before you can add options for this specification attribute page. + + Number options + The attribute has been updated successfully. @@ -1380,6 +1416,9 @@ The category has been deleted successfully. + + Show other description + Discounts @@ -1410,6 +1449,12 @@ Whether customers are allowed to select the page size from a predefined list of options. + + Bottom description + + + Optional second description displayed below products on the category page. + Category template @@ -1423,10 +1468,10 @@ Deleted - Description + Top description - The description of the category. + Description of the category that is displayed above products on the category page. Display order @@ -1434,6 +1479,12 @@ Set the category's display order. 1 represents the top of the list. + + Complete name + + + Complete name displayed as title on the category page. + Meta description @@ -1479,6 +1530,9 @@ Select a parent category for this category. Leave this field empty to make this the root level category + + Parent category + Picture @@ -1495,7 +1549,7 @@ Published - Check to publish this category (visible in store). Uncheck to unpublish (category not available in store). + Check the box to publish this category (visible in store). Uncheck to unpublish (category not available in store). URL alias @@ -1507,7 +1561,7 @@ Show on home page - Check if you want to show a category on home page. + Check the box if you want to show a category on home page. Subject to ACL @@ -1675,7 +1729,7 @@ Published - Check to publish this manufacturer (visible in store). Uncheck to unpublish (manufacturer not available in store). + Check the box to publish this manufacturer (visible in store). Uncheck to unpublish (manufacturer not available in store). URL alias @@ -1957,6 +2011,9 @@ Products with attribute values of type "product" cannot be part of a bundle. + + Notes on product bundles + You need to save the product before you can add bundled products for this product page. @@ -1988,7 +2045,7 @@ Copy images - Check to copy the images. + Check the box the box to copy the images. New product name @@ -2006,7 +2063,7 @@ Cross-sells - Add new cross-sell product + Add checkout-selling product Product @@ -2015,7 +2072,7 @@ Product - You need to save the product before you can add cross-sell products for this product page. + You need to save the product before you can add checkout-selling products for this product page. The product has been deleted successfully. @@ -2060,7 +2117,7 @@ Allow customer reviews - Check to allow customers to review this product. + Check the box to allow customers to review this product. Allowed quantities @@ -2068,6 +2125,12 @@ Enter a comma separated list of quantities you want this product to be restricted to. Instead of a quantity textbox that allows them to enter any quantity, they will receive a dropdown list of the values you enter here. + + Approved rating sum + + + Approved total reviews + Associated to product @@ -2078,7 +2141,7 @@ Automatically add these products to the cart - Check to automatically add these products to the cart. + Check the box to automatically add these products to the cart. Available end date @@ -2090,7 +2153,7 @@ Available for pre-order - Check if this item is available for Pre-Order. It also displays "Pre-order" button instead of "Add to cart". + Check the box if this item is available for Pre-Order. It also displays "Pre-order" button instead of "Add to cart". Available start date @@ -2132,7 +2195,7 @@ {0} per unit (base price: {1} per {2}) - "Measure unit" is required to calculate the base price. + Base price measure unit "Measure unit" is required to calculate the base price. @@ -2165,7 +2228,7 @@ Call for price - Check to show "Call for Pricing" or "Call for quote" instead of price. + Check the box to show "Call for Pricing" or "Call for quote" instead of price. Customer enters price @@ -2183,25 +2246,25 @@ Disable buy button - Check to disable the buy button for this product. This may be necessary for products that are "available upon request". + Check the box to disable the buy button for this product. This may be necessary for products that are "available upon request". Disable wishlist button - Check to disable the wishlist button for this product. + Check the box to disable the wishlist button for this product. Display stock availability - Check to display stock availability. When enabled, customers will see stock availability. + Check the box to display stock availability. When enabled, customers will see stock availability. Display stock quantity - Check to display stock quantity. When enabled, customers will see stock quantity. + Check the box to display stock quantity. When enabled, customers will see stock quantity. Download file @@ -2239,17 +2302,23 @@ Enter global trade item number (GTIN). These identifiers include UPC (in North America), EAN (in Europe), JAN (in Japan), and ISBN (for books). + + Has discounts applied + Has sample download file - Check if this product has a sample download file that can be downloaded before checkout. + Check the box if this product has a sample download file that can be downloaded before checkout. + + + Has tier prices Has user agreement - Check if the product has a user agreement. + Check the box if the product has a user agreement. Height @@ -2257,6 +2326,12 @@ The height of the product. + + Homepage display order + + + Specifies the display order for products displayed on homepage. 1 represents the first element in the list. + ID @@ -2267,7 +2342,7 @@ Downloadable product - Check if this product variant is a downloadable product. When a customer purchases a download product, they can download the item direct from your store by viewing their completed order. + Check the box if this product variant is a downloadable product. When a customer purchases a download product, they can download the item direct from your store by viewing their completed order. Is Electronic Service @@ -2279,19 +2354,19 @@ Free shipping - Check if this product comes with FREE shipping. + Check the box if this product comes with FREE shipping. Is gift card - Check if it is a gift card. + Check the box if this product is a gift card. Recurring product - Check if this product is a recurring product. + Check the box if this product is a recurring product. Shipping enabled @@ -2311,6 +2386,9 @@ The length of the product. + + Lowest attribute combination price + Low stock activity @@ -2380,6 +2458,12 @@ Please provide a name. + + Not approved rating sum + + + Not approved total reviews + Notify admin for quantity below @@ -2447,7 +2531,10 @@ Published - Check to publish this product (visible in store). Uncheck to unpublish (product not available in store). + Check the box to publish this product (visible in store). Uncheck to unpublish (product not available in store). + + + Quantity unit Cycle length @@ -2477,7 +2564,7 @@ Require other products are added to the cart - Check if this product requires that other products are added to the cart. + Check the box if this product requires that other products are added to the cart. Sample download file @@ -2501,7 +2588,7 @@ Show on home page - Check to display this product on your store's home page. Recommended for your most popular products. + Check the box to display this product on your store's home page. Recommended for your most popular products. SKU @@ -2561,7 +2648,7 @@ Visible individually - Check it if you want this product to be visible in catalog or search results. You can use this field (just uncheck) to hide associated products from catalog and make them accessible only from a parent "grouped" product details page. + Check the box it if you want this product to be visible in catalog or search results. You can use this field (just uncheck) to hide associated products from catalog and make them accessible only from a parent "grouped" product details page. Weight @@ -2602,6 +2689,18 @@ Search by a specific category. + + Showed on home page + + + Filters for products displayed or not displayed on homepage. + + + Published + + + Filters for published or unpublished products. + Manufacturer @@ -2620,6 +2719,18 @@ Search by a product type. + + Without category mapping + + + Filters for products without category mapping. + + + Without manufacturer mapping + + + Filters for products without manufacturer mapping. + Manage products @@ -2729,7 +2840,7 @@ Pictures - Check the images that shows this attribute combination + Check the box of the images that show this attribute combination # @@ -2861,7 +2972,7 @@ Related products - Add new related product + Add cross-selling product Display order @@ -2873,7 +2984,7 @@ Product - You need to save the product before you can add related products for this product page. + You need to save the product before you can add cross-selling products for this product page. Specification attributes @@ -2882,7 +2993,7 @@ Add attribute - Add a new product specification attribute + Assign a specification attribute Allow filtering @@ -2903,7 +3014,7 @@ Show on product page - The value of the specification attribute. Be visible on the product page + Check the box to display the attribute in the public product detail page Attribute @@ -2980,11 +3091,44 @@ Tagged products + + Check for update + + + Unknown error during package download. Please try again later. + + + AutoUpdate possible + + + <p>This update can be installed automatically. For this SmartStore.NET downloads an installation package to your webserver, executes it and restarts the application. Before the installation your shop directory is backed up, except the folders <i>App_Data</i> and <i>Media</i>, as well as the SQL Server database file. </p><p>Click the <b>Update now</b> button to download and install the package. As an alternative to this, you can download the package to your local PC further below and perform the installation at a later time manually.</p> + + + Current version + + + Don't notify anymore + + + SmartStore.NET is up to date + + + Release Notes + + + Update available + + + Update now + + + Your version + Actions - About + About SmartStore.NET Actions @@ -2995,12 +3139,21 @@ Add new + + Admin comment + + + Admin comment for internal use. Won't be published. + All Are you sure? + + Do you want to create all mutual associations? + Would you like to proceed? @@ -3034,12 +3187,21 @@ Copy of - - CSV file + + Create all mutual associations + + + There were {0} mutual association(s) created. + + + CSV Configuration The data was successfully changed. + + Data exchange + The data were saved successfully. @@ -3052,29 +3214,59 @@ Delete (selected) + + Delete all + Are you sure you want to delete this item? Are you sure you want to delete "{0}"? + + Deleted + + + Delete selected + Edit + + The email has been successfully sent. + + + Please enter an email address. + + + Object + ID The unique numeric id of the entity - - Excel file + + Errors + + + Error sending an email + + + PDF Export + + + Please wait while the export is being executed + + + Export all No data to export. - - Export to CSV + + Export selected Export to Excel @@ -3091,14 +3283,29 @@ Export to PDF (selected) + + Too many items! The PDF conversion is limited to 500 items. Please reduce the amount of selected records. + Export to XML - - Export to XML (all) + + The file is in use and cannot be opened. - - Export to XML (selected) + + File not found + + + {0} files were deleted + + + The file must be of the type {0}. + + + {0} folders were deleted + + + FTP status {0} ({1}). General @@ -3124,41 +3331,17 @@ Hide - - Import from CSV - - - Import from Excel - - - Active since: {0}. - - - Cancel import + + HTTP status {0} ({1}). - - Import process has been cancelled + + Ignore - - Do you really want to cancel the import? Products imported so far will not be removed. + + Import file - - Download full report... - - - The import is being performed in the background now. You can view the progress or the result of the last completed import in the import dialog at any time. - - - <b>Last import</b>: {0}{1}. - - - No report available - - - {0} of {1} rows processed. - - - {0} new, {1} updated - with {2} warning(s) und {3} error(s). + + Import files Please wait while the import is being executed @@ -3166,33 +3349,78 @@ Info + + Last run + License + + Licensed + List You are going to lose any unsaved changes. Are you sure? + + New records + No No, cancel + + No entries have been selected. + + + of + + + Placeholder + Please select Preview + + Cannot load the provider {0}. + + + Public files + + + {0} records were deleted. + + + Skip + + + Specifies the number of records to be skipped. + + + Limit + + + Specifies the maximum number of records to be processed. + Restore defaults + + The resource was not found. + You must restart the application for the changes to take effect. + + Restrictions + Save @@ -3202,6 +3430,12 @@ Search + + Selected + + + Send now + Search engines (SEO) @@ -3211,6 +3445,12 @@ Show + + Values for skip and limit must be greater than or equal to 0. + + + Skipped + Standard @@ -3241,15 +3481,30 @@ All stores + + Successful on + The task was successfully processed. + + Total rows + Tree view + + Unlicensed + + + Unsupported entity type '{0}' + Update + + Updated + Please upload a file @@ -3259,6 +3514,9 @@ Please wait while processing is running... + + Warnings + Wrong email @@ -3271,6 +3529,9 @@ Access control list + + No customer roles defined + Permission name @@ -3292,9 +3553,6 @@ The Activity Log Type. - - Activity Log Type - Message @@ -3319,6 +3577,15 @@ Customer Email + + Filters results by customer email address. + + + Customer system account + + + Filters results by customer system accounts. + Activity Types @@ -3334,6 +3601,24 @@ The types have been updated successfully. + + Transfer this ACL configuration to children + + + This function assigns the ACL configuration of this category to all subcategories and products included in this category.<br /> + Please keep in mind you have to save changes in the ACL configuration <br/> + before you can assign them to all subcategories and products. <br/> + <b>Attention:</b> Please keep in mind that <b>existing ACL records will be deleted</b> + + + Transfer this store configuration to children + + + This function assigns the store configuration of this category to all subcategories and products included in this category.<br /> + Please keep in mind you have to save changes in the store configuration <br/> + before you can assign them to all subcategories and products. <br/> + <b>Attention:</b> Please keep in mind that <b>existing store mappings will be deleted</b> + Content Slider @@ -3484,6 +3769,9 @@ back to country list + + The country cannot be deleted because it has associated addresses. + The country has been deleted successfully. @@ -3575,7 +3863,7 @@ Add a new state/province - The state can't be deleted. It has associated addresses. + The state\province cannot be deleted because it has associated addresses. Edit state/province @@ -3631,15 +3919,12 @@ back to currency list - - The primary exchange rate currency can't be deleted. - - - The primary store currency can't be deleted. - The currency has been deleted successfully. + + The currency cannot be deleted or deactivated because it is attached to the store "{0}" as primary or exchange rate currency. + Edit currency details @@ -3701,16 +3986,10 @@ Current exchange rate provider - Is primary exchange rate currency + Exchange rate currency - Is primary store currency - - - Mark as primary exchange rate currency - - - Mark as primary store currency + Primary currency Name @@ -3724,6 +4003,18 @@ Please provide a name. + + Is exchange rate currency for + + + A list of stores where the currency is primary exchange rate currency. + + + Is primary store currency for + + + A list of stores where the currency is primary store currency. + Published @@ -3848,7 +4139,7 @@ SSL - Check to use Secure Sockets Layer (SSL) to encrypt the SMTP connection. + Check the box to use Secure Sockets Layer (SSL) to encrypt the SMTP connection. Host @@ -3887,7 +4178,7 @@ Use default credentials - Check to use default credentials for the connection + Check the box to use default credentials for the connection User @@ -3904,6 +4195,9 @@ Email has been successfully sent. + + Testing email functionality. + The email account has been updated successfully. @@ -3950,10 +4244,10 @@ The display order of this language. 1 represents the top of the list. - Flag image file name + Flag image - The flag image file name. The image should be saved into \\images\\flags\\ directory. + Specifies the flag image. The files for the flag images must be stored in /Content/Images/flags/. Language culture @@ -3983,7 +4277,7 @@ Right-to-left - Check to enable right-to-left support for this language. The active theme should support RTL (have appropriate CSS style file). And it affects only public store. + Check the box to enable right-to-left support for this language. The active theme should support RTL (have appropriate CSS style file). And it affects only public store. Unique SEO code @@ -3995,7 +4289,7 @@ Two letter SEO code should be 2 characters long. - Please provide a unique SEO code. + Please select a SEO language code. Import resources @@ -4144,6 +4438,9 @@ Payment + + Activating this payment method is forbidden by the plugin. + Payment methods @@ -4157,23 +4454,41 @@ Display order - Recurring support + Recurring payments Supports capture - Partial refund + Supports partial refund - Refund + Supports refund - Void + Supports void + + + Full description + + + Specifies a full description of the payment method. It appears in the payment list in checkout. + + + There were no possibilities found to restrict payment methods. + + + Short description + + + Specifies a short description of the payment method. Plugins + + Please check whether the licensing is done for the right store URL! Proceed with the licensing? + Install or update plugins @@ -4258,6 +4573,18 @@ Exchange rate providers + + The plugin has been successfully licensed. + + + License key + + + Please enter the license key for the plugin. + + + The license key cannot be activated for the entered store URL. Please enter the right store URL in your store details before activating the key. + back to plugin list @@ -4294,9 +4621,42 @@ The plugin has been uninstalled. + + An unknown error occurred when calling a plugin. Please refer to the following message for details. + + + Quantity units + + + Quantity unit successfully added + + + Add new quantity unit + + + Edit quantity unit + + + Default quantity unit + + + Sets the default quantity unit + + + Name + + + Set the name of quantity unit + + + Quantity unit successfully updated + Regional Settings + + You need to save before you can specify restrictions. + Settings @@ -4331,19 +4691,25 @@ Allow not registered users to leave comments - Check to allow not registered users to leave comments. + Check the box to allow not registered users to leave comments. Blog enabled - Check to enable the blog in your store. + Check the box to enable the blog in your store. + + + Maximum age (in days) + + + Specifies the maximum news age in days. Older blog posts are not exported in the RSS feed. Notify about new blog comments - Check to notify store owner about new blog comments. + Check the box to notify store owner about new blog comments. Number of tags (cloud) @@ -4361,7 +4727,7 @@ Display blog RSS feed link in the browser address bar - Check to enable the blog RSS feed link in customers browser address bar + Check to enable the blog RSS feed link in customers browser address bar. Catalog settings @@ -4370,31 +4736,31 @@ Allow anonymous users to email a friend - Check if you want to allow anonymous users to email a friend. + Check the box if you want to allow anonymous users to email a friend. Allow anonymous users to write product reviews - Check to allow anonymous users to write product reviews. + Check the box to allow anonymous users to write product reviews. Allow product sorting - Check to enable product sorting option on category/manufacturer details page. + Check the box to enable product sorting option on category/manufacturer details page. Allow view mode changing - Check to enable the option to change view mode on category/manufacturer details page. + Check the box to enable the option to change view mode on category/manufacturer details page. 'Ask question' enabled - Check to allow customers to send an inquiry concerning a product + Check the box to allow customers to send an inquiry concerning a product Base price for bundle items @@ -4406,26 +4772,38 @@ Category breadcrumb enabled - Check to show category breadcrumb. + Check the box the box to show category breadcrumb. 'Compare Products' enabled - Check to allow customers to use the 'Compare Products' option in your store + Check the box to allow customers to use the 'Compare Products' option in your store - Amount of displayed products per page + Number of displayed products per page Determines the amount of displayed products per page. + + Default product sort order + + + Specifies the default product sort order. + Default view mode Specifies how product lists should be displayed by default. The customer can also change the appearance manually. + + Delivery time dislayed when stock is empty + + + Delivery time to be dislayed when the stock quantity of a product is equal or less 0. + Number from which images are only displayed on variation change @@ -4436,13 +4814,13 @@ 'Email a friend' enabled - Check to allow customers to use the 'Email a friend' option in your store + Check the box to allow customers to use the 'Email a friend' option in your store Enable dynamic price update - Check if you want to enable dynamic price update on product details page in case a product has product attributes with price adjustments. + Check the box if you want to enable dynamic price update on product details page in case a product has product attributes with price adjustments. Truncate long texts @@ -4450,17 +4828,41 @@ Option to truncate long texts and to only show on click in full length. + + Expand all filter groups + + + Determines whether all filter groups should be displayed expanded + - Activate filter + Display filter sidebar widget - Activates the filter function for products within categories + Displays the filter sidebar widget for products within categories Hide buy-button in product lists - Check to hide the buy-button in product lists. + Check the box to hide the buy-button in product lists. + + + Hide default picture for categories + + + Specifies whether to hide the default image for categories. The default image is shown when no image is assigned to a category. + + + Hide default picture for manufacturers + + + Specifies whether to hide the default image for manufacturers. The default image is shown when no image is assigned to a manufacturer. + + + Hide default picture for products + + + Specifies whether to hide the default image for products. The default image is shown when no image is assigned to a product. Height of truncated long text @@ -4472,25 +4874,31 @@ Ignore discounts (sitewide) - Check to ignore discounts (sitewide). It can significantly improve performance. + Check the box to ignore discounts (sitewide). It can significantly improve performance. Ignore featured products (sitewide) - Check to ignore featured products (sitewide). It can significantly improve performance. + Check the box to ignore featured products (sitewide). It can significantly improve performance. + + + Show featured products in lists + + + Specifies whether to display featured products in product and filter lists. Otherwise they only appear in the top featured product list. Include full description in compare products - Check to display product full description on the compare products page. + Check the box to display product full description on the compare products page. Include short description in compare products - Check to display product short description on the compare products page. + Check the box to display product short description on the compare products page. Label product as "new" for max. [x] days @@ -4504,6 +4912,12 @@ Enter the number of manufacturers to display in manufacturer navigation block. + + Maximum filter items + + + Determines the maximum amount of filter items to display + General @@ -4511,7 +4925,7 @@ Notify about new product reviews - Check to notify store owner about new product reviews. + Check the box to notify store owner about new product reviews. Number of best sellers on home page @@ -4525,6 +4939,12 @@ The number of product tags that appear in the tag cloud + + Price display + + + Specifies whether or what type of price to be displayed in product lists. + Product detail @@ -4535,13 +4955,13 @@ Product reviews must be approved - Check if product reviews must be approved by administrator. + Check the box if product reviews must be approved by administrator. 'Products also purchased' enabled - Check to allow customers to view a list of products purchased by other customers who purchased the above + Check the box to allow customers to view a list of products purchased by other customers who purchased the above Number of also purchased products to display @@ -4577,7 +4997,7 @@ Search autocomplete enabled - Check to enabled autocomplete in the search box. + Check the box to enabled autocomplete in the search box. Number of 'autocomplete' products to display @@ -4610,7 +5030,7 @@ 'Recently added products' enabled - Check to allow customers to use the 'Recently added products' feature in your store + Check the box to allow customers to use the 'Recently added products' feature in your store Number of 'Recently added products' @@ -4634,7 +5054,7 @@ 'Recently viewed products' enabled - Check to allow customers to use the 'Recently viewed products' feature in your store + Check the box to allow customers to use the 'Recently viewed products' feature in your store Number of 'Recently viewed products' @@ -4642,6 +5062,12 @@ The number of 'Recently viewed products' to display when 'Recently viewed products' option is enabled. + + Search product description + + + Specifies whether the product description should be included in the search. + Search page. Products per page @@ -4658,19 +5084,19 @@ Show best sellers on home page - Check to show best sellers on home page. + Check the box to show best sellers on home page. Show the number of distinct products besides each category - Check to show the number of products besides each category (category navigation block). + Check the box to show the number of products besides each category (category navigation block). Include subcategories (number of distinct products) - Check to including when showing the number of products besides each category. + Check the box to include subcategories when showing the number of products besides each category. Show color squares in product lists @@ -4678,6 +5104,12 @@ Specifies whether the colors of the first color type attribute should be displayed in product lists + + Show default quantity unit + + + Show default quantity unit if the product has no quantity unit set. + Show delivery times @@ -4694,7 +5126,7 @@ Display dimensions - Check to display dimensions. When enabled, customers will see the dimensions of the product. + Check the box to display dimensions. When enabled, customers will see the dimensions of the product. Show discount sign @@ -4706,7 +5138,7 @@ Show GTIN - Check to show GTIN in public store. + Check the box to show GTIN in public store. Show image of linked product @@ -4724,7 +5156,25 @@ Show manufacturer part number - Check to show manufacturer part numbers in public store. + Check the box to show manufacturer part numbers in public store. + + + Show manufacturer pictures on homepage + + + Specifies whether manufacturers will be displayed as images or textual links on the homepage. + + + Show manufacturer pictures + + + Specifies whether to show manufacturer pictures on product detail page. + + + Show manufacturers on homepage + + + Specifies whether manufacturers will be displayed on the homepage. Show product images in autocomplete box @@ -4732,6 +5182,12 @@ Determines whether product images should be displayed in the autocomplete search box. + + Show product reviews in product detail view + + + Determines whether product reviews are shown in product detail view. + Show product reviews in product lists @@ -4742,19 +5198,19 @@ Include products from subcategories - Check if you want a category details page to include products from subcategories. + Check the box if you want a category details page to include products from subcategories. Show SKU - Check to show product SKU in public store. + Check the box to show product SKU in public store. Show a share button - Check to show share button on product details page. + Check the box to show share button on product details page. Show variant combination price adjustments @@ -4766,13 +5222,25 @@ Display weight - Check to display the weight. When enabled, customers will see the weight of the product. + Check the box to display the weight. When enabled, customers will see the weight of the product. + + + Sort filter results by number of matches + + + Specifies to sort filter results by number of matches in descending order. If this option is deactivated then the result is sorted by the display order of the values. + + + Show subcategories + + + Indicates whether and where to show subcategories on a category page. Suppress SKU search - Check to disable the searching of product SKUs. This setting can increase the performance of searching. + Check the box to disable the searching of product SKUs. This setting can increase the performance of searching. Customers @@ -4793,7 +5261,7 @@ 'City' required - Check if 'City' is required. + Check the box if 'City' is required. 'Company' enabled @@ -4805,7 +5273,7 @@ 'Company' required - Check if 'Company' is required. + Check the box if 'Company' is required. 'Country' enabled @@ -4826,7 +5294,7 @@ 'Fax number' required - Check if 'Fax number' is required. + Check the box if 'Fax number' is required. 'Phone number' enabled @@ -4838,7 +5306,7 @@ 'Phone number' required - Check if 'Phone number' is required. + Check the box if 'Phone number' is required. 'State/province' enabled @@ -4856,7 +5324,7 @@ 'Street address 2' required - Check if 'Street address 2' is required. + Check the box if 'Street address 2' is required. 'Street address' enabled @@ -4868,7 +5336,7 @@ 'Street address' required - Check if 'Street address' is required. + Check the box if 'Street address' is required. 'Zip / postal code' enabled @@ -4880,13 +5348,13 @@ 'Zip / postal code' required - Check if 'Zip / postal code' is required. + Check the box if 'Zip / postal code' is required. Allow customers to select time zone - Check to allow customers to select time zone. If checked, then time zone can be selected on the public store (account page). If not, then default time zone will be used. + Check the box to allow customers to select a time zone. If checked, then the time zone can be selected on the public store (account page). If not, then the default time zone will be used. Allow customers to upload avatars @@ -4922,7 +5390,7 @@ 'City' required - Check if 'City' is required. + Check the box if 'City' is required. 'Company' enabled @@ -4934,7 +5402,7 @@ 'Company' required - Check if 'Company' is required. + Check the box if 'Company' is required. 'Country' enabled @@ -4942,6 +5410,12 @@ Set if 'Country' is enabled. + + Customers can enter a customer number + + + Specifies whether customers can enter a customer number if the customer number doesn't contain a value yet. + Customer form fields @@ -4960,6 +5434,24 @@ Determines the maximum length of the displayed customer name. + + Save customer number + + + Specifies whether customer numbers can be saved. + + + Customer numbers + + + Specifies whether to assign customer numbers and whether they should be created automatically. + + + Customer number presentation + + + Specifies the presentation and handling of the customer number to the customer. + Customer settings @@ -4990,11 +5482,23 @@ The default store time zone used to display dates. + + Display customer numbers in frontend + + + Specifies whether customer numbers will be displayed to customers in their account area. + + + Get privacy consent for contact requests + + + Specifies whether a checkbox will be displayed on the contact page which requests the user to agree on storage of his data. + Auto register enabled - Check to enable auto registration when using external authentication. + Check the box to enable auto registration when using external authentication. External authentication settings @@ -5009,7 +5513,7 @@ 'Fax number' required - Check if 'Fax number' is required + Check the box if 'Fax number' is required 'Gender' enabled @@ -5021,19 +5525,19 @@ Hide 'Back in stock subscriptions' tab - Check to hide 'Back in stock subscriptions' tab on 'My account' page + Check the box to hide 'Back in stock subscriptions' tab on 'My account' page Hide 'Downloadable products' tab - Check to hide 'Downloadable products' tab on 'My account' page + Check the box to hide 'Downloadable products' tab on 'My account' page Hide newsletter box - Check if you want to hide the newsletter subscription box. + Check the box if you want to hide the newsletter subscription box. 'Newsletter' enabled @@ -5057,7 +5561,13 @@ 'Phone number' required - Check if 'Phone number' is required. + Check the box if 'Phone number' is required. + + + Customer role at registrations + + + Specifies a customer role that will be assigned to newly registered customers. Show customers' join date @@ -5093,7 +5603,7 @@ 'Street address 2' required - Check if 'Street address 2' is required. + Check the box if 'Street address 2' is required. 'Street address' enabled @@ -5105,19 +5615,25 @@ 'Street address' required - Check if 'Street address' is required. + Check the box if 'Street address' is required. 'Usernames' enabled - Check to use usernames for login/registration instead of emails. WARNING: not recommended to change in production environment. + Check the box to use usernames for login/registration instead of E-Mails. WARNING: not recommended to change in production environment. Registration method - Determines customer registration method. Standard - mode where visitors can register and no approval is required. Email Validation - mode where user must respond to validation email that is sent to them before they are activated. Admin Approval - mode where visitors can register but admin approval is required. Disabled - mode where registration is disabled + Determines customer registration method. Standard - mode where visitors can register and no approval is required. E-mail Validation - mode where user must respond to validation email that is sent to them before they are activated. Admin Approval - mode where visitors can register but admin approval is required. Disabled - mode where registration is disabled + + + Validate customer email address + + + Determines whether the customers email address is validated during the checkout process 'Zip / postal code' enabled @@ -5129,7 +5645,25 @@ 'Zip / postal code' required - Check if 'Zip / postal code' is required. + Check the box if 'Zip / postal code' is required. + + + Timeout for image download (minutes) + + + Specifies the timeout for the image download in minutes. + + + Image folder (relative path) + + + Specifies a relative path to a folder with images to be imported (e.g. Content\Images). + + + Maximum length of file and folder names + + + Specifies the maximum length of file and folder names created during an import or export. Forum settings @@ -5162,7 +5696,7 @@ Allow customers to manage forum subscriptions - Check if you want to allow customers to manage forum subscriptions + Check the box if you want to allow customers to manage forum subscriptions Allow guests to create posts @@ -5204,7 +5738,7 @@ Forums enabled - Check to enable forums. + Check the box to enable forums. Notify about private messages @@ -5275,6 +5809,24 @@ IP addresses allowed to access the Back End. Leave this field empty if you do not want to restrict access to the Back End. Use comma to separate them (e.g. 127.0.0.10,232.18.204.16) + + Allow unicode characters + + + Check whether SEO names can contain letters that are classified as unicode characters. + + + Attach order PDF to 'Order Completed' email + + + Dynamically creates and attaches the order PDF to the 'Order Completed' customer notification email. + + + Attach order PDF to 'Order Placed' email + + + Dynamically creates and attaches the order PDF to the 'Order Placed' customer notification email. + Bank connection @@ -5314,6 +5866,12 @@ Enter the IBAN of your bank + + Canonical host name rule + + + Enforces permanent redirection to a single domain name for a better page rank (e.g. mystore.com > www.mystore.com or vice versa) + Enable canonical URLs @@ -5324,7 +5882,7 @@ CAPTCHA enabled - Check to enable CAPTCHA + Check the box to enable CAPTCHA Captcha is enabled but the appropriate keys are not entered. @@ -5333,55 +5891,55 @@ Show on 'ask question' page - Check to show CAPTCHA on 'ask question' page + Check the box the box to show CAPTCHA on 'ask question' page Show on blog page (comments) - Check to show CAPTCHA on blog page when writing a comment. + Check the box to show CAPTCHA on blog page when writing a comment. Show on contact us page - Check to show CAPTCHA on contact us page. + Check the box to show CAPTCHA on contact us page. Show on 'email product to a friend' page - Check to show CAPTCHA on 'email product to a friend' page. + Check the box to show CAPTCHA on 'email product to a friend' page. Show on 'email wishlist to a friend' page - Check to show CAPTCHA on 'email wishlist to a friend' page. + Check the box to show CAPTCHA on 'email wishlist to a friend' page. Show on login page - Check to show CAPTCHA on login page. + Check the box to show CAPTCHA on login page. Show on news page (comments) - Check to show CAPTCHA on news page when writing a comment. + Check the box to show CAPTCHA on news page when writing a comment. Show on product reviews page - Check to show CAPTCHA on product reviews page when writing a review. + Check the box to show CAPTCHA on product reviews page when writing a review. Show on registration page - Check to show CAPTCHA on registration page. + Check the box to show CAPTCHA on registration page. Company @@ -5567,7 +6125,7 @@ Convert non-western chars - Check to take out the accent marks in the letters of SEO names while keeping the letter. + Check the box to take out the accent marks in the letters of SEO names while keeping the letter. Default language redirect behaviour @@ -5614,6 +6172,12 @@ Encryption private key must be 16 characters long + + Extra Disallows for robots.txt + + + Enter additional paths that should be included as Disallow entries in your robots.txt. Each entry has to be entered in a new line. + Full-Text settings @@ -5668,6 +6232,12 @@ Localization settings + + Meta robots + + + Specifies if and how search engines indexing the pages of your store. + Page title SEO adjustment @@ -5684,7 +6254,7 @@ Enabled - Check to enabled PDF. + Check the box to enabled PDF. Use Letter page size @@ -5722,6 +6292,12 @@ When enabled, your URLs will be http://www.yourStore.com/en/ or http://www.yourStore.com/ru/ (SEO friendly) + + Characters to be converted + + + Allows an individual conversion of characters for SEO name creation. Enter the old and the new character separated by a semicolon, e.g. ä;ae. Each entry has to be entered in a new line. + SEO settings @@ -5765,22 +6341,28 @@ Store closed - Check to close the store. Uncheck to re-open. + Check the box to close the store. Uncheck to re-open. Allow an admin to view the closed store - Check to allow a user with admin access to view the store while it is set to closed. + Check the box to allow a user with admin access to view the store while it is set to closed. Store information + + Check string + + + Enter any string to check the SEO name creation. Changed settings must be saved before. + Use images for language selection - Check if you want to use images for language selection. + Check the box if you want to use images for language selection. Media settings @@ -5836,12 +6418,27 @@ The maximum image size (longest side) allowed for image uploads. + + Thumbnail size of products in emails + + + Specifies the thumbnail image size (pixels) of products in emails. Enter 0 to not display thumbnails. + Mini-shopping cart thumbnail image size The default size (pixels) for product thumbnail images in the mini-shopping cart block. + + 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 images. + + + Move to database + + + Move to file system + Pictures are stored into... @@ -5854,9 +6451,6 @@ file system - - A value indicating whether pictures are stored in database or file system. - Zoom type @@ -5894,13 +6488,13 @@ Allow not registered users to leave comments - Check to allow not registered users to leave comments. + Check the box to allow not registered users to leave comments. News enabled - Check to enable the news in your store. + Check the box to enable the news in your store. Number of items to display @@ -5908,6 +6502,12 @@ The number of news items to display on your home page. + + Maximum age (in days) + + + Specifies the maximum news age in days. Older news are not exported in the RSS feed. + News archive page size @@ -5918,19 +6518,19 @@ Notify about new news comments - Check to notify store owner about new news comments. + Check the box to notify store owner about new news comments. Display news RSS feed link in the browser address bar - Check to enable the news RSS feed link in customers browser address bar + Check the box to enable the news RSS feed link in customers browser address bar Show on home page - Check to display your news items on your store home page. + Check the box to display your news items on your store home page. No setting could be loaded with the specified ID. @@ -5942,7 +6542,7 @@ Anonymous checkout allowed - Check to enable anonymous checkout (customers are not required to login/register when purchasing products) + Check the box to enable anonymous checkout (customers are not required to login/register when purchasing products) Disable "Order completed" page @@ -5950,6 +6550,12 @@ When disabled, customers will be automatically redirected to the order details page. + + Display orders of all stores + + + Specifies whether to display the orders of all stores to the customer. If this option is disabled, only the orders of the current store are displayed. + Gift card activation order status @@ -5972,7 +6578,7 @@ Is re-order allowed - Check if you want to allow customers to make re-orders. + Check the box if you want to allow customers to make re-orders. Min order sub-total amount @@ -6004,6 +6610,12 @@ Set the order ID counter. This is useful if you want your orders to start at a certain number. This only affects orders created going forward. The value must be greater than the current maximum order ID. + + Number of displayed orders per page + + + Specifies the number of displayed orders per page. + Order settings @@ -6029,7 +6641,7 @@ Enable Returns System - Check if you want to allow customers to submit return requests for items they've previously purchased. + Check the box if you want to allow customers to submit return requests for items they've previously purchased. Return request settings @@ -6062,7 +6674,7 @@ Enabled - Check if you want to enable the Reward Points Program. + Check the box if you want to enable the Reward Points Program. Exchange rate @@ -6109,6 +6721,12 @@ Specify number of points awarded for registration. + + Round down points + + + Specifies whether to round down calculated points. Otherwise the bonus points will be rounded up. + Shipping settings @@ -6116,25 +6734,25 @@ Display shipment events - Check if you want your customers to see shipment events on their shipment details pages (if supported by your shipping rate computation method). + Check the box if you want your customers to see shipment events on their shipment details pages (if supported by your shipping rate computation method). Estimate shipping enabled - Check to allow customers to estimate shipping on shopping cart page + Check the box to allow customers to estimate shipping on shopping cart page Free shipping over 'X' - Check to enable free shipping for all orders over 'X'. Set the value for X below. + Check the box to enable free shipping for all orders over 'X'. Set the value for X below. Calculate 'X' including tax - Check to calculate 'X' value including tax; otherwise excluding tax. + Check the box to calculate 'X' value including tax; otherwise excluding tax. Value of 'X' @@ -6155,17 +6773,20 @@ Allow guests to email their wishlists - Check to allow guests to email their wishlists to friends. + Check the box to allow guests to email their wishlists to friends. Allow 'out of stock' items to be added to wishlist - Check to allow 'out of stock' products to be added to wishlist. + Check the box to allow 'out of stock' products to be added to wishlist. Shopping cart + + Checkout + Number of 'Cross-Sells' @@ -6188,7 +6809,7 @@ Allow customers to email their wishlists - Check to allow customers to email their wishlists to friends. + Check the box to allow customers to email their wishlists to friends. Maximum shopping cart items @@ -6206,7 +6827,7 @@ Show mini-shopping cart - Check to enable mini-shopping cart. + Check the box to enable mini-shopping cart. Mini-shopping cart product number @@ -6218,7 +6839,16 @@ Move items from wishlist to cart - Check to move products from wishlist to the cart when clicking "Add to cart" button. Otherwise, they are copied. + Check the box to move products from wishlist to the cart when clicking "Add to cart" button. Otherwise, they are copied. + + + Subscribe to newsletters + + + Specifies id customers can subscribe to newsletters when ordering and if the checkbox is enabled by default. + + + Order confirmation page Round prices during calculation @@ -6233,16 +6863,16 @@ Determines whether base price should be displayed in the shopping cart. - Show comment box on confirm order page + Show comment box - Determines whether comment box is displayed on confirm order page + Specifies whether comment box is displayed on the order confirmation page. - Show legal hints in order summary on the confirm order page + Show legal hints in order summary - Determines whether to show hints in order summary on the confirm order page. This text can be altered in the language resources. + Specifies whether to show hints in order summary on the confirm order page. This text can be altered in the language resources. Display delivery times @@ -6254,13 +6884,19 @@ Show discount box - Check if you want the discount coupon box to be displayed on shopping cart page + Check the box if you want the discount coupon box to be displayed on shopping cart page - + + Show revocation waiver box for electronic services + + + Specifies whether the customer must agree a revocation waiver for electronic services on the order confirmation page. + + Show gift card box - Check if you want the gift card coupon box to be displayed on shopping cart page + Check the box if you want the gift card coupon box to be displayed on shopping cart page Show quantity of linked product @@ -6310,6 +6946,21 @@ Determines whether the product weight is shown in shopping cart + + Consent for email transfer to third parties + + + 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. + + + Text for email transfer consent + + + I agree to the transfer and storage of my email address by third parties. + + + Specifies the text to be displayed to the customer. Please choose a specific reason, e.g. 'I agree to the transfer and storage of my email address for TrustedShops buyer protection.' + Wishlist @@ -6353,19 +7004,19 @@ Allow VAT exemption - Check if this store will exempt eligible VAT-registered customers from VAT. + Check the box if this store will exempt eligible VAT-registered customers from VAT. Notify admin when a new VAT number is submitted - Check if you want to receive a notification (email) when a new VAT number is submitted. + Check the box if you want to receive a notification (email) when a new VAT number is submitted. EU VAT enabled - Check to enable EU VAT (the European Union Value Added Tax) + Check the box to enable EU VAT (the European Union Value Added Tax) Your shop country @@ -6377,7 +7028,7 @@ Use web service - Check if you want to use the EU web service to validate VAT numbers. WARNING: If this option is enabled, then DO NOT disable country form field available during registration (public store). + Check the box if you want to use the EU web service to validate VAT numbers. WARNING: If this option is enabled, then DO NOT disable country form field available during registration (public store). Hide tax in order summary @@ -6437,25 +7088,25 @@ Show legal information in footer. - Check to show legal information in footer. + Check the box to show legal information in footer. Show legal information in product detail page. - Check to show legal information in product detail page. + Check the box to show legal information in product detail page. Show legal information in product grid. - Check to show legal information in product grid. + Check the box to show legal information in product grid. Show legal information in product list. - Check to show legal information in product list. + Check the box to show legal information in product list. Tax based on @@ -6472,6 +7123,9 @@ Shipping + + Activating this shipping rate computation method is forbidden by the plugin. + Shipping methods @@ -6520,6 +7174,12 @@ Please provide a name. + + No shipping methods could be loaded. + + + There were no possibilities found to restrict shipping methods. + The shipping method has been updated successfully. @@ -6538,19 +7198,6 @@ Display order - - Shipping method restrictions - - - Country - - - Please mark the checkbox(es) for the country or countries in which you want the - shipping method(s) not available - - - The settings have been updated successfully. - SMS providers @@ -6623,6 +7270,18 @@ Please provide a name. + + Exchange rate currency + + + Specifies the primary exchange rate currency for this store. + + + Primary store currency + + + Specifies the the primary store currency. + Secure URL @@ -6630,10 +7289,10 @@ The secure URL of your store e.g. https://www.yourstore.com/ or http://sharedssl.yourstore.com/. Leave it empty if you want secure URL to be detected automatically. - SSL enabled + SSL - Check if your store will be SSL secured. + Specifies whether the store should be SSL secured. Store logo @@ -6731,6 +7390,9 @@ Name + + Theme requires no configuration + Variables were successfully updated. @@ -6759,7 +7421,7 @@ Allow customers to select a theme - Check to allow customers to select a store theme. + Check the box to allow customers to select a store theme. Enable asset bundling @@ -6783,7 +7445,13 @@ Mobile devices supported - Check to enable mobile devices support. + Check the box to enable mobile devices support. + + + Save theme choice in cookie + + + If unchecked, user's theme choice is associated to the account, which may be undesirable when, for example, multiple users share a guest account. Disable resource bundling and caching in order to test and debug theme changes more easily. @@ -7013,6 +7681,12 @@ Forum group name is required. + + URL-Alias + + + Set a search engine friendly page name e.g. 'the-best-forum' to make your page URL 'http://www.yourStore.com/the-best-forum'. Leave empty to generate it automatically based on the name of the forum. + The forum has been updated successfully. @@ -7061,6 +7735,12 @@ Forum Group Name is required. + + URL-Alias + + + Set a search engine friendly page name e.g. 'the-best-forumgroup' to make your page URL 'http://www.yourStore.com/the-best-forumgroup'. Leave empty to generate it automatically based on the name of the forum group. + The forum group has been updated successfully. @@ -7091,6 +7771,24 @@ This is a list of the message tokens you can use in your emails. + + Attachment 1 + + + A file that is to be appended to each sent email (eg Terms, Conditions etc.) + + + Attachment 2 + + + A file that is to be appended to each sent email (eg Terms, Conditions etc.) + + + Attachment 3 + + + A file that is to be appended to each sent email (eg Terms, Conditions etc.) + BCC @@ -7124,6 +7822,12 @@ The name of this template (read only). + + Only send manually + + + Indicates whether emails derived from this message template should only be send manually. + Subject @@ -7323,7 +8027,7 @@ Allow guests to vote - Check to allow guests to vote. + Check the box to allow guests to vote. Display order @@ -7362,7 +8066,7 @@ Show on home page - Check if you want to show poll on home page. + Check the box if you want to show poll on home page. Start date @@ -7413,13 +8117,13 @@ Include in sitemap - Check to include this topic in the sitemap. + Check the box to include this topic in the sitemap. Password protected - Check if this topic is password protected + Check the box if this topic is password protected Meta description @@ -7478,6 +8182,12 @@ The title of your topic. + + Title tag + + + Determines the title tag of the topic + URL @@ -7493,6 +8203,12 @@ Specifies whether the title should be displayed as the widget header. + + Add wrapper around content + + + Adds an HTML wrapper around widget content + Widget zone @@ -7583,6 +8299,9 @@ back to customer role list + + The customer role "{0}" cannot be found. + The customer role has been deleted successfully. @@ -7599,13 +8318,13 @@ System customer roles can't be disabled. - Check to make this role active. + Check the box to make this role active. Free shipping - Check to allow customers in this role to get free shipping on their orders. + Check the box to allow customers in this role to get free shipping on their orders. Is system role @@ -7638,7 +8357,7 @@ Tax exempt - Check to allow customers in this role to make tax free purchases. + Check the box to allow customers in this role to make tax free purchases. The customer role has been updated successfully. @@ -7775,6 +8494,9 @@ The date the customer record is created. + + Customer GUID + Customer roles @@ -7832,6 +8554,9 @@ The last used IP address. + + Is system account + Is tax exempt @@ -7844,6 +8569,9 @@ The last activity date. + + Last login date + Last name @@ -7865,6 +8593,9 @@ The customer's password. + + Password salt + Phone @@ -8075,9 +8806,6 @@ Enter points to add. - - Date - Message @@ -8240,6 +8968,729 @@ Store Statistics + + New profile + + + Default Value + + + Object property + + + Import Field + + + You can optionally set for each field of the import file whether and for which object property the data should be imported. Fields with equal names are always imported as long as they are not explicitly ignored. Not yet selected properties are highlighted in the selection list. It is also possible to define a default value which is applied when the import field is empty. Stored assignments becomes invalid and reset when the delimiter changes. + + + The stored field assignments are invalid due to the change of the delimiter and were reset. + + + Delimiter + + + Specifies the field separator. + + + Please enter a valid delimiter. + + + Inner quote character + + + Specifies the inner quote character used for escaping. + + + Please enter a valid inner quote character (escaping). + + + Delimiter and inner quote character cannot be equal in CSV files. + + + Quote character + + + Specifies the quotation character. + + + Please enter a valid quote character. + + + Quote all fields + + + Specifies whether to set quotation marks around all field values. + + + Delimiter and quote character cannot be equal in CSV files. + + + Supports multilines + + + Specifies whether field values with multilines are supported. + + + Trim values + + + Specifies whether to remove space characters at start and end of a field value. + + + Batch size + + + Specifies the maximum number of records per export file. 0 is the default and means that all the records are exported in one file. + + + Cannot delete a system export profile. + + + Clean up after successful deployment + + + Specifies whether to delete unneeded files after all deployments succeeded. + + + Apply settings from + + + Specifies an export profile from which to apply the settings. + + + This is an automatic notification of store "{0}" about a recent data export. + + + Export of profile "{0}" has been finished + + + Email addresses to + + + Specifies the email addresses where to send the notification message. + + + The following specific information will be taken into account by the provider during the export. + + + The export provider <b>{0}</b> requires no further configuration. + + + Create ZIP archive + + + Specifies whether to combine the export files in temporary a ZIP archive. The archive remains in the temporary folder of the export profile without further processing. + + + At least one file could not be copied. + + + Specifies whether to combine the export files in a ZIP archive and only to deploy the archive. + + + Publishing type + + + Specifies the type of publishing. + + + Email account + + + Specifies the email account used to sent the data. + + + Email addresses to + + + Specifies the email addresses where to send the data. + + + Email subject + + + Specifies the subject of the email. + + + Directory path + + + Specifies the path (relative or absolute) where to deploy the data. + + + HTTP transmission type + + + Specifies how to transmit the export files via HTTP. + + + Specifies whether to copy the exported data into a folder that is accessible through the internet. + + + Name of profile + + + Specifies the name of the publishing profile. + + + There are no publishing profiles. + + + Click <b>New profile</b> to add one or multiple publishing profiles to specify how to further proceed with the export files. + + + Passive mode + + + Specifies whether to exchange data in active or passive mode. + + + Password + + + Specifies the password. + + + Publishing profiles + + + Publishing target + + + Name of subfolder + + + Specifies the name of a subfolder where to publish the data. + + + URL\Host + + + Specifies the URL or host name where to send the data. + + + User name + + + Specifies the user name. + + + Use SSL + + + Specifies whether to use a SSL (Secure Sockets Layer) connection. + + + If there are a large number of export files, it is recommended to use the option <b>Create ZIP archive</b>. This saves time and avoids problems, such as a full email mailbox. + + + Email notification + + + Specifies the email account used to send a notification message of the completion of the export. + + + The export profile is disabled. It must be enabled to preview the export data. + + + Object + + + The object type the provider processes. + + + Export files + + + File type + + + The file type of the exported data. + + + Pattern for file names + + + Specifies the pattern for creating file names. + + + Please enter a valid pattern for file names. Example for file names: %Store.Id%-%Profile.Id%-%File.Index%-%Profile.SeoName% + + + ID of export profil;Folder name of export profil;SEO name of export profil;Store ID;SEO name of store;One based file index;Random number;UTC timestamp + + + Availability to + + + Filter by availability quantity. + + + Availability from + + + Filter by availability quantity. + + + Billing countries + + + Filter by billing countries. + + + Categories + + + Filter by categtories. + + + Created from + + + Filter by created date. + + + Created to + + + Filter by created date. + + + Customer roles + + + Filter by customer roles. + + + Only featured products + + + Filter by featured products. Is only applied when the filtering by categories and manufacturers. + + + Has placed x orders + + + Filter by number of placed orders. + + + Has spent amount x + + + Filter by spent amount. + + + Product Id to + + + Filter by product identifier. + + + Product Id from + + + Filter by product identifier. + + + Only active customers + + + Filter by active or inactive customers. + + + Only active subscribers + + + Filter by active or inactive newsletter subscribers. + + + Published + + + Filter by publishing. + + + Only tax exempt customers + + + Filter by tax exempt customers. + + + Last activity from + + + Filter by date of last store activity. + + + Last active until + + + Filter by date of last store activity. + + + Manufacturer + + + Filter by manufacturer. + + + Specify individual filters to limit the exported data. + + + Order status + + + Filter by order status. + + + Payment status + + + Filter by payment status. + + + Price to + + + Filter by price. + + + Price from + + + Filter by price. + + + Product tag + + + Filter by product tag. + + + Product type + + + Filter by product type. + + + Shipping countries + + + Filter by shipping countries. + + + Shipping status + + + Filter by shipping status. + + + Store + + + Filter by store. + + + Without category mapping + + + Filter by missing category mapping. + + + Without manufacturer mapping + + + Filter by missing manufacturer mapping. + + + Folder path + + + Specifies the relative path of the folder where to export the data. + + + Please enter a valid, relative folder path for the export data. + + + System profile + + + Indicates whether the export profile is a system profile. System profiles cannot be removed. + + + The system export profile {0} was not found. + + + Name of profile + + + Specifies the name of the export profile. + + + There were no export provider found. + + + There is no filtering available. + + + The export provider does not explicit support any file type. Therefore, the export provider is responsible for futher deployment of export data. + + + There is no preview available for this entity type. + + + There were no export profiles found. + + + There was no export profile of type <b>{0}</b> found. Create now a <a href="{1}">new export profile</a>. + + + There is no projection available. + + + This option is not taken into account in the preview. + + + With the following settings you can partition the data to be exported. This includes<ul><li>Skipping the first n records</li><li>The maximum number of records to be exported</li><li>The number of records per export file</li><li>Export data for each shop in a separate file</li></ul>By default, all data of a store will be exported into one file. + + + Partitioning setting values must be greater than or equal to 0. + + + Per store + + + Specifies whether to start a separate run-through for each store. For each shop a new file will be created. + + + Export profile + + + The export profile for this export provider. + + + {0} of {1} records exported + + + Text to be appended + + + Specifies the text to be attached to the product description. If there are multiple texts then one of it is selected randomly. + + + Export attribute combinations + + + Specifies whether to export a standalone product for each active attribute combination. + + + Attribute values + + + Specifies if and how to further process the attribute values. + + + Manufacturer\Brand + + + Specifies the manufacturer or brand to be exported, if a product has no manufacturer assigned. + + + Convert net into gross prices + + + Specifies to convert net into gross prices. + + + Critical characters + + + List with characters to be removed from the detail description. + + + Currency + + + Specifies the currency to be applied to the export. + + + Customer ID + + + Specifies the ID of the customer to be applied to the export. Is taken into account for price calculations for example. + + + Product description + + + Specifies what information to use for the description of the product. + + + Remove HTML from description + + + Specifies whether to remove all HTML from the product description for the export. + + + Free shipping threshold + + + Specifies the amount as from shipping is free. + + + Language + + + Specifies the language to be applied to the export. + + + Do not export grouped products + + + Specifies whether to export grouped products. If this option is activated, then the associated products will be exported. + + + The following information will be taken into account during the export and integrated in the process. + + + Number of pictures + + + Specifies the number of images per object to be exported. + + + Change order status to + + + Specifies if and how to change the status of the exported orders. + + + Product picture size + + + Specifies the size (in pixel) of the product image. + + + Product price + + + Specifies the product price to be exported. + + + Remove critical characters + + + Specifies whether to remove critical characters (like ½) from the detail description. + + + Shipping costs + + + The shipping costs to be exported. + + + Shipping time + + + Specifies the shipping time for products where it is unspecified. + + + Store + + + Specifies the store to be applied to the export. + + + Provider + + + Specifies the export provider. It is responsible for the individual formatting of the export data. + + + System name of profile + + + The system name of the export profile. + + + The following list contains system profiles, which are provided by plugins such as the <a href='http://community.smartstore.com/index.php?/files/file/85-smartstorenet-common-export-providers/' target='_blank'>Data Export Plugin</a>. You can customize system profiles as desired, but cannot create new ones. These profiles also add additional action buttons. You will find these above data lists, such as the product or order list. + + + System profiles + + + User profiles + + + Add import file... + + + Assignment of import fields + + + This is an automatic notification of store "{0}" about a recent data import. Summary: + + + Import of "{0}" has been finished + + + My product import;My category import;My customer import;My newsletter subscription import + + + Upload import file... + + + Key fields + + + Existing records can be identified for updates on the basis of key fields. The key fields are processed in the order how they are defined here. + + + Please only use the ID field as a key field, if the data sourced from the same database to which it will be imported. Otherwise it is possible that the wrong records are updated. + + + Last import result + + + Please upload an import file. + + + For multiple import files please make sure that they are of the same file type and that the content follows the same pattern (e.g. same column headings). + + + Name of profile + + + Specifies the name of the import profile. + + + There were no import profiles found. + + + Number of pictures + + + Specifies the number of images per object to be imported. + + + Please select the import object and upload an import file. + + + {0} of {1} records processed + + + Create new assignment here + + + Only update + + + If this option is enabled, only existing data is updated but no new records are added. + + + At least one key field is required. + Download uploaded file @@ -8426,8 +9877,11 @@ Help + + Documentation + - Community forums + Community SmartStore.NET is a fork of the ASP.NET open source e-commerce solution {0}. @@ -8453,6 +9907,9 @@ The customer has added the following comment to his order + + An order with this number was not found. + "Order cancelled" email (to customer) has been queued. Queued email identifier: {0} @@ -8474,6 +9931,12 @@ "Order placed" email (to store owner) has been queued. Queued email identifier: {0} + + Subscribed to newsletter + + + Newsletter subscriber has been removed + Order cancelled @@ -8481,7 +9944,7 @@ Order has been captured - Error capturing order #{0}. Error: {1} + Unable to capture payment for order {0}. Order has been deleted @@ -8505,7 +9968,7 @@ Order has been partially refunded. Amount = {0} - Unable to partially refund order. {0} + Unable to partially refund order {0}. Order placed @@ -8514,7 +9977,7 @@ Order has been refunded. Amount = {0} - Unable to refund order. {0} + Unable to refund order {0}. Order status has been changed to {0} @@ -8523,10 +9986,10 @@ Order has been voided - Unable to void order. {0} + Unable to void payment transaction of order {0}. - Unable to cancel recurring payment. {0} + Unable to cancel recurring payment for order {0}. Recurring payment has been cancelled @@ -8594,6 +10057,12 @@ Affiliate + + Accepts transfer of email + + + Indicates whether the customer has agreed to a transfer of his email address to third parties. + Affiliate @@ -8627,6 +10096,9 @@ Capture + + Books a previously authorised payment through the payment provider. + Capture transaction ID @@ -8801,11 +10273,14 @@ Mark as paid + + Sets the payment status to 'paid' without contacting the payment provider. + - Order GUID + Order reference number - Internal reference for order. + The internal order reference number. In contrast to the order number it already exists during checkout that is before order creation. Order Number @@ -8888,12 +10363,18 @@ Max amount is {0} {1} + + Initiates the refund of a partial amount of the total order value at the payment provider. + Partial refund for order #{0} Partial refund (Offline) + + Setzt the payment status to 'partially refunded' including the refunded amount without contacting the payment provider. + Payment method @@ -8942,6 +10423,9 @@ Refund + + Initiates a refund of the total order value at the payment provider. + Refunded amount @@ -8951,6 +10435,9 @@ Refund (Offline) + + Setzt the payment status to 'refunded' without contacting the payment provider. + Save credit card @@ -9006,10 +10493,16 @@ Used VAT number (the European Union Value Added Tax) - Void + Cancel + + + Initiates the cancellation of the payment transaction at the payment provider. - Void (Offline) + Cancel (Offline) + + + Setzt the payment status to 'canceled' without contacting the payment provider. Info @@ -9131,6 +10624,12 @@ Note + + New IPN + + + A new notification from the payment provider has arrived in the order notes. + Order as PDF @@ -9173,6 +10672,12 @@ Enter total (incl tax). + + Tax rate + + + The tax rate for the product + Add a new product to order #{0} @@ -9461,6 +10966,9 @@ Plugins + + Add-on licensing + Administration @@ -9518,9 +11026,24 @@ Tax + + Demo expired + + + Demo + + + Demo {0} day(s) remaining + + + Renew check + Manage plugins + + Please keep in mind, the base price is depending on several factors and will therefore only be calculated reliable in the front end. + Promotion feeds @@ -9779,8 +11302,8 @@ Email is required. - - {0} email(s) were imported and {1} updated. + + Subscription GUID Providing plugin @@ -9974,6 +11497,12 @@ Chosen requested action. + + Last update of requested action + + + Date when the requested action was updated the last time. + Staff notes @@ -10322,6 +11851,9 @@ Insert your SQL query here + + The SQL command was executed successfully. + Execute @@ -10334,6 +11866,9 @@ back to message queue + + Could not download e-mail attachment: no data. + The queued email has been deleted successfully. @@ -10343,9 +11878,21 @@ Edit message queue item + + An error occured while creating e-mail attachment + + + The e-mail attachment data could not be downloaded from path '{0}' + + + The content type of the e-mail attachment must be 'application/pdf' + Bcc + + Attachments + Bcc @@ -10406,6 +11953,12 @@ Enter priority. + + Only send manually + + + Indicates whether the email should only be send manually. + Sent on @@ -10448,6 +12001,9 @@ End date + + Number of attachments + End date @@ -10470,7 +12026,7 @@ Load not sent emails only - Only load emails into queue that have not yet been sent. + Load not sent emails only. Maximum send attempts @@ -10478,6 +12034,12 @@ The maximum number of attempts to send a message. + + Load emails manually send only + + + Load emails manually send only. + Start date @@ -10499,77 +12061,164 @@ The queued email has been updated successfully. + + Scheduled task + - Schedule tasks + Scheduled Tasks + + + Abnormally aborted due to application shutdown + + + The scheduled task "{0}" has been canceled + + + Cancellation request has been submitted. + + + Cron Expression + + + An expression that defines the schedule for the automatic execution of the task. + + + <a href='{0}' target='_blank'>Cron Expressions</a> help + + + Duration of the latest execution ([h]:[min]:[sec]) + + + Edit task Enabled + + Enables the scheduled execution of the task in accordance with the cron expression + + + Future schedules + + + The cron expression is invalid + Last end - Last run + Last Run + + + Start date of the last execution Last success + + Start date of the last successful execution + Name Name is required - - Do not forgot to restart the application once a task has been modified. + + Next Run in + + + Date of the next scheduled execution + + + Error while running scheduled task "{0}" Run now - - Task execution completed - Is running... Task is now running in the background - - Seconds (run period) + + The task is now running in the background. You will receive an email as soon as it is completed. The progress can be tracked in the export profile list. + + + The task is now running in the background. You will receive an email as soon as it is completed. The progress can be tracked in the import profile list. - - Seconds should be positive. + + The task has been executed successfully + + + Schedule execution - Stop on error + Disable on error + + + Check the box if the task should be disabled automatically when an error occurs during execution + + + The task can not be edited while it is running. + + + The task has been updated successfully - Search engine friendly page names + SEO Names - - Delete selected + + Only one active SEO name should be set per language. - Entity ID + Object ID + + + Specifies the ID of the associated object. - Entity name + Object + + + Specifies the name of the associated object. Is active + + Specifies whether the SEO name is active or inactive. + Language + + Specifies the language of the SEO name. + Standard - Name + SEO Name - A name to find. + Specifies the SEO name. + + + Names per object + + + The number of SEO names per object. + + + Background Task + + + PDF Converter + + + Search Engine System information @@ -10604,6 +12253,12 @@ The name of the data provider. + + Collect + + + The memory has been successfully cleaned up. + HTTP_HOST @@ -10640,6 +12295,9 @@ Server time zone + + Used memory (RAM) + Greenwich Mean Time (GMT/UTC) @@ -10649,6 +12307,12 @@ Warnings + + Access denied to anonymous request on {0}. + + + Access denied to user #{0} '{1}' on {2}. + Default dimension is not set @@ -10673,6 +12337,9 @@ Default weight is set + + Please enter digits only. + All directory permissions are OK @@ -10706,6 +12373,15 @@ '{0}' plugin is incompatible with your SmartStore.NET version. Delete it or update to the latest version. + + There are no customer roles defined. + + + There are no permissions defined. + + + No shipment items + You don't have active payment methods @@ -10730,6 +12406,15 @@ Only one offline shipping rate computation method is recommended to use + + The reachability of the sitemap could not be validated. + + + The sitemap for the store is reachable. + + + The sitemap for the store is not reachable. + Specified store URL matches this store URL @@ -11033,6 +12718,21 @@ Tables/Container + + Please enter a valid email address + + + Please enter a name + + + Please enter a valid URL + + + Please enter username and password + + + The value must be greater than 0. + API not available. @@ -11195,6 +12895,9 @@ Checkout + + An anonymous checkout is not allowed. + Billing address @@ -11208,16 +12911,19 @@ Confirm - Please verify the order total and the specifics regarding the billing address and, if need be, the shipping address. You can make corrections to your entry anytime by clicking on <STRONG>[Back]</STRONG> If everything's as it should be, deliver your order to us by clicking <STRONG>[Confirm]</STRONG>. + Please verify the order total and the specifics regarding the billing address and, if required, the shipping address. You can make corrections to your entry anytime by clicking on <strong>back</strong>. If everything is as it should be, deliver your order to us by clicking <strong>confirm</strong>. Confirm order - Confirm your order + Please confirm your order. - Continue + Continue shopping + + + Please agree to the user agreement for downloadable products. Enter billing address @@ -11225,6 +12931,12 @@ Enter shipping address + + Yes, I want access to the digital content immediately and know that my right of revocation expires with the access. + + + Please confirm that you would like access to the digital content immediately. + Please wait several seconds before placing a new order (already placed another order several seconds ago). @@ -11243,8 +12955,11 @@ No payment methods available + + Your order has been received + - Order number + Your order number Order summary @@ -11259,7 +12974,7 @@ Payment method - Click here for order details. + Order details Address @@ -11315,6 +13030,9 @@ Submitting order information... + + Subscribed to newsletter + Terms of service @@ -11328,14 +13046,11 @@ (read) - Thank you + Thank you for your purchase! Use my reward points, {0} reward points ({1}) available - - Your order has been successfully processed! - Activated @@ -11402,6 +13117,9 @@ Cancelled + + The API call to verify a CAPTCHA has failed. + Edit product @@ -11423,6 +13141,15 @@ Continue + + Copy to clipboard + + + Failed to copy. + + + Copied! + Count @@ -11435,6 +13162,12 @@ Cross references + + Customer number already exists, please choose another. + + + Date + Day @@ -11450,30 +13183,87 @@ Are you sure you want to delete "{0}"? + + Deployment + Description Description + + Detail description + Display order + + Download + + + The product variant doesn't have a sample download. + + + You have reached the maximum number of downloads {0}. + + + Download data is not available anymore. + + + Downloads are not allowed. + + + Download is not available any more. + + + Sample download is not available anymore. + Duration Edit + + Enabled + Enter value + + Click on an item to select or deselect it and OK to apply the selection. + + + There were no more items found. + + + Click on an item to select it and OK to apply it. + Error + + The email address is not valid. + + + No active language could be loaded. + + + No email account could be loaded. + + + Unfortunately the selected payment method caused an error. Please correct your entries, try it again or select another payment method. + Error while sending the email. Please try again later. + + Example + + + Execution + Export @@ -11498,12 +13288,21 @@ Drop files here to upload + + Enter URL + Failed Upload a file + + Filter + + + Free shipping + Friendly name @@ -11519,6 +13318,9 @@ Home + + Image + Import @@ -11528,18 +13330,33 @@ Is active + + Language + List + + Loading + Loading next step... + + This function is not available for guests. + Miscellaneous Month + + More info + + + My + Navigation @@ -11555,6 +13372,12 @@ No, cancel + + There are no files available. + + + There was no file uploaded. + The operation was not carried out for security reasons. @@ -11564,6 +13387,9 @@ Alert + + Not selectable + Off @@ -11579,6 +13405,12 @@ Optional + + Options + + + Partition + Capture method not supported @@ -11603,6 +13435,24 @@ Product + + Profile + + + Projection + + + Provider + + + public + + + Published + + + Publishing + Question @@ -11615,9 +13465,21 @@ Remove + + Replace + + + The request could not be processed.<br />Controller: {0}, Action: {1}, Reason: {2}. + + + Rule + Save + + Scheduled + Search @@ -11651,15 +13513,39 @@ Show + + Show all + + + Show more + + + Shrink + + + The database has been successfully shrinked + Submit System name + + Unavailable + Undefined + + Unknown + + + Unpublished + + + Unscheduled + Unspecified @@ -11678,11 +13564,17 @@ Wait... + + Waiting + Warning + + Website + - The characters didn't match the picture. Please try again. + Please confirm that you are not a "robot". Wrong email @@ -11732,6 +13624,15 @@ Enter your name + + Privacy consent + + + Yes I've read the <a href="{0}">privacy terms</a> and agree that my data given by me can be stored electronically. My data will thereby only be used to process my inquiry. + + + Please agree to the storage of your data. + Your enquiry has been successfully sent to the store owner. @@ -11741,15 +13642,15 @@ Currencies + + The customer does not exist. + Guest Disclaimer - - Date - Download @@ -11765,6 +13666,9 @@ Product + + The product has no user agreement. + I agree @@ -11774,6 +13678,9 @@ User agreement + + Yes, I agree to the <a href='javascript:void(0)' data-id='{0}' class='download-user-agreement'>user agreement</a> for this product. + Checkboxes @@ -11837,6 +13744,18 @@ Track inventory by product attributes + + No price indication + + + Minimum feasible price + + + Price preselected on detail page + + + Price without discounts and attributes + Created on @@ -11888,6 +13807,15 @@ Years + + Above product list + + + At bottom of page + + + Do not display + Using CONTAINS and AND with prefix_term @@ -11918,6 +13846,27 @@ Show usernames + + Automatically assigned + + + Disabled + + + Enabled + + + Display + + + Always editable + + + Editable if empty + + + Do not display + Clear @@ -11927,6 +13876,102 @@ Hashed + + Append all values to the product name + + + Not specified + + + Email + + + File system + + + FTP + + + HTTP POST + + + Public folder + + + Description + + + Manufacturer + Product name + long description + + + Manufacturer + Product name + short description + + + Product name + long description + + + Product name + short description + + + None + + + Short description + + + Short description or name if empty + + + Category + + + Customer + + + Manufacturer + + + Newsletter Subscribers + + + Order + + + Product + + + Multipart form data POST + + + Simple POST + + + Complete + + + None + + + Processing + + + Category + + + Customer + + + Newsletter Subscriber + + + Product + + + Delimiter separated values (.csv, .txt, .tab) + + + Excel (.xlsx) + N times only @@ -12011,6 +14056,24 @@ Warning + + Show activated + + + Show deactivated + + + Do not show + + + Show activated + + + Show deactivated + + + Do not show + Cancelled @@ -12083,6 +14146,15 @@ Standard account creation + + Don't apply + + + Omit www prefix + + + Require www prefix + Delivered @@ -12135,10 +14207,12 @@ Not supported - Our supporting staff has been notified with this error and will address this issue shortly. We profusely apologize for the + We apologize, a server error ocurred while handling your request, this is not a problem with your computer or internet connection. + The details have been sent to our support team and we will investigate the issue very soon.<br /><br /> + In the meantime, please retry your request as it may have been temporary. - We're sorry, an internal error occurred that prevents the request to complete. + Oops! Resouce name will be 'ExampleOfNestedResources' @@ -12266,9 +14340,6 @@ Forum's topics with newest posts. - - {0} - Forum: {1} - Forum Name @@ -12509,6 +14580,9 @@ Information + + The install language '{0}' is not registered. + Please enter a valid credit card number. @@ -12663,7 +14737,7 @@ Previous - View Full Site + Desktop version View mobile version @@ -12749,15 +14823,57 @@ A verification email has been sent. Thank you! + + Sorry! The page you were looking for could not be found. + + + 404 + Billing Address + + The billing address is missing. + + + The order total could not be calculated. + + + The shipping total could not be calculated. + + + Cannot cancel order. + + + Cannot capture order. + + + Cannot mark order as completed. + + + Cannot mark order as paid. + + + Cannot do partial refund for order. + + + Cannot do refund for order. + + + Cannot do void for order. + Complete payment This order is not yet paid for. To pay now click the "Complete payment" button. + + The country '{0}' is not allowed for billing. + + + The country '{0}' is not allowed for shipping. + Email @@ -12770,6 +14886,12 @@ Gift card ({0}) + + No initial order exists for the recurring payment. + + + There are no recurring products. + Note(s) @@ -12779,6 +14901,9 @@ Note + + The order {0} was not found. + Order # @@ -12800,6 +14925,9 @@ Payment method additional fee + + order-{0}.pdf + Phone @@ -12899,9 +15027,6 @@ Country - - Date - Event @@ -12914,6 +15039,9 @@ Shipping Address + + The shipping address is missing. + Shipping Method @@ -13007,9 +15135,6 @@ Order Details - - Page not found - Password Recovery @@ -13067,6 +15192,9 @@ Email wishlist to a friend + + The next payment date could not be calculated. + Card code @@ -13091,6 +15219,9 @@ Wrong card number + + The payment method could not be loaded. + Valid until @@ -13100,35 +15231,32 @@ Expire year is required - - Select credit card - - - Payment info + + The payment method is not available. - - Address: {0} + + At least one payment method provider is required to be active. - - Address 2: {0} + + Unfortunately we can not handle this purchasing via your preferred payment method. Please select an alternate payment option to complete your order. - - Billing Information: + + Recurring payment is not active. - - Commercial register: + + Recurring payments are not supported by selected payment method. - - Company: {0} + + The recurring payment type is not supported. - - Created on + + Select credit card - - Discount: + + Payment info - - fax: {0} + + Commercial register: Account holder: {0} @@ -13157,20 +15285,8 @@ Url: {0} - - Gift card ({0}): - - - Name: {0} - - - Note - - Order# {0} - - - Date: {0} + Order# Order notes: @@ -13181,18 +15297,6 @@ Note - - Order total: - - - Payment method: {0} - - - Payment Method Additional Fee: - - - fon: {0} - Product(s) @@ -13208,42 +15312,15 @@ Total - - Purchase Order Number: {0} - - - {0} reward points: - - - Shipping: - - - Shipping Information: - - - Shipping method: {0} - SKU - - Sub-total: - - - Tax: - - tax number: {0} - - - Tax {0}%: + Tax Number: {0} Vat-ID: {0} - - VAT number: {0} - Address: {0} @@ -13253,6 +15330,9 @@ Company: {0} + + Delivery date + Name: {0} @@ -13262,6 +15342,9 @@ Phone: {0} + + Shipped products + Product Name @@ -13272,22 +15355,40 @@ Shipment #{0} - Shipping method: {0} + Shipping method SKU - - Price + + Tracking number - - SKU + + Weight + + + Mail + + + Fax + + + Phone + + + Address + + + Contact data Stock quantity - - Weight + + The poll answer {0} was not found. + + + The poll is not available. Only registered users can vote. @@ -13310,12 +15411,12 @@ Privacy Notice + + Private messages are disabled. + Inbox - - Date - Delete selected @@ -13361,9 +15462,6 @@ To: - - Date - Delete selected @@ -13439,11 +15537,17 @@ {0} in stock + + Not in assortment + Out of stock - base price: {0} per {1} + Content: {0} {1} ({2} / {3} {1}) + + + {0} {1} ({2} / {3} {1}) Top @@ -13644,7 +15748,10 @@ This product is sold out. - This product is sold out. + No bundle items available + + + The product {0} was not found. Price: @@ -13721,6 +15828,9 @@ Quantity + + The product variant {0} was not found. + Weight @@ -13766,6 +15876,9 @@ You can use ECB (European central bank) exchange rate provider only when exchange rate currency code is set to EURO + + You can use ECB (European central bank) exchange rate provider only when exchange rate currency code is set to EURO. + ECB exchange rate provider @@ -13814,9 +15927,6 @@ Reviews - - Date - Existing reviews @@ -13859,6 +15969,9 @@ You cannot vote for your own review + + The product review {0} was not found. + Only registered users can write reviews @@ -13892,9 +16005,6 @@ Your current balance is {0} reward points ({1}). - - Date - Message @@ -13976,6 +16086,18 @@ Search term minimum length is {0} characters + + This shipment is already delivered. + + + This shipment is already shipped. + + + The shipping rate computation method could not be loaded. + + + At least one shipping rate computation method provider is required to be active. + Shipping & Returns @@ -13994,6 +16116,9 @@ Allowed quantities for this product: {0} + + An unknown error has occurred in the examination of the selected product variant. + The bundle item "{0}" cannot be found. @@ -14105,6 +16230,9 @@ The coupon code you entered couldn't be applied to your order + + The shoping cart is disabled. + Total @@ -14208,7 +16336,7 @@ This product requires the following product is added to the cart: {0} - Please select {0} + Please select '{0}'. Enter valid sender email @@ -14217,7 +16345,7 @@ Enter valid sender name - For a complete listing of all shipping costs please click here. + For a complete listing of all shipping costs please click <a href="{0}">here</a>. SKU @@ -14298,10 +16426,10 @@ Store name comes after page name - This store is currently closed + We'll be back. - Please check back in a little while. + We're busy updating our online store for you and will be back soon. Select store theme @@ -14321,9 +16449,15 @@ * All prices {0}, plus <a href="{1}">shipping</a> + + * All prices {0}, plus shipping + {0} {1} {2}plus <a href="{3}">shipping</a> + + {0} {1} {2}plus shipping + {0} {1} {2}plus <a href="{3}">shipping</a> @@ -14690,15 +16824,57 @@ Well background + + Day + + + d + + + Days + + + d + {0} days ago + + Hour + + + h + + + Hours + + + h + {0} hours ago + + Minute + + + min + + + Minutes + + + min + {0} minutes ago + + Month + + + Months + {0} months ago @@ -14720,9 +16896,33 @@ one year ago + + Second + + + sec + + + Seconds + + + sec + {0} seconds ago + + Week + + + Weeks + + + Year + + + Years + {0} years ago @@ -14783,6 +16983,9 @@ Enter your email + + The wishlist is disabled. + View Wishlist diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt index 7e2473f0e7..f822a9cb76 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt @@ -1 +1 @@ -201411182202431_V21Final \ No newline at end of file +201605201911421_ExportRevision \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.de.xml b/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.de.xml index acd910374c..3582e6f7d8 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.de.xml +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.de.xml @@ -183,6 +183,9 @@ Schreibe Beispiel Daten - + + + Optionen + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.en.xml b/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.en.xml index a11658d45b..4603606855 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.en.xml +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.en.xml @@ -183,5 +183,9 @@ Creating sample data - + + + Options + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/App_Data/ua-parser.regexes.yaml b/src/Presentation/SmartStore.Web/App_Data/ua-parser.regexes.yaml new file mode 100644 index 0000000000..c160ade711 --- /dev/null +++ b/src/Presentation/SmartStore.Web/App_Data/ua-parser.regexes.yaml @@ -0,0 +1,4798 @@ +user_agent_parsers: + #### SPECIAL CASES TOP #### + + # @note: iOS / OSX Applications + - regex: '(CFNetwork)(?:/(\d+)\.(\d+)\.?(\d+)?)?' + family_replacement: 'CFNetwork' + + # Pingdom + - regex: '(Pingdom.com_bot_version_)(\d+)\.(\d+)' + family_replacement: 'PingdomBot' + + # Facebook + - regex: '(facebookexternalhit)/(\d+)\.(\d+)' + family_replacement: 'FacebookBot' + + # Google Plus + - regex: 'Google.*/\+/web/snippet' + family_replacement: 'GooglePlusBot' + + # Twitter + - regex: '(Twitterbot)/(\d+)\.(\d+)' + family_replacement: 'TwitterBot' + + # Bots Pattern '/name-0.0' + - regex: '/((?:Ant-)?Nutch|[A-z]+[Bb]ot|[A-z]+[Ss]pider|Axtaris|fetchurl|Isara|ShopSalad|Tailsweep)[ \-](\d+)(?:\.(\d+)(?:\.(\d+))?)?' + # Bots Pattern 'name/0.0' + - regex: '(008|Altresium|Argus|BaiduMobaider|BoardReader|DNSGroup|DataparkSearch|EDI|Goodzer|Grub|INGRID|Infohelfer|LinkedInBot|LOOQ|Nutch|PathDefender|Peew|PostPost|Steeler|Twitterbot|VSE|WebCrunch|WebZIP|Y!J-BR[A-Z]|YahooSeeker|envolk|sproose|wminer)/(\d+)(?:\.(\d+)(?:\.(\d+))?)?' + + # MSIECrawler + - regex: '(MSIE) (\d+)\.(\d+)([a-z]\d?)?;.* MSIECrawler' + family_replacement: 'MSIECrawler' + + # Downloader ... + - regex: '(Google-HTTP-Java-Client|Apache-HttpClient|http%20client|Python-urllib|HttpMonitor|TLSProber|WinHTTP|JNLP)(?:[ /](\d+)(?:\.(\d+)(?:\.(\d+))?)?)?' + + # Bots + - regex: '(1470\.net crawler|50\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]+-Agent|AdsBot-Google(?:-[a-z]+)?|altavista|AppEngine-Google|archive.*?\.org_bot|archiver|Ask Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]+)*|bingbot|BingPreview|blitzbot|BlogBridge|BoardReader(?: [A-Za-z]+)*|boitho.com-dc|BotSeer|\b\w*favicon\w*\b|\bYeti(?:-[a-z]+)?|Catchpoint bot|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\(S\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher)?|Feed Seeker Bot|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]+-)?Googlebot(?:-[a-zA-Z]+)?|Google SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile)?|IconSurf|IlTrovatore(?:-Setaccio)?|InfuzApp|Innovazion Crawler|InternetArchive|IP2[a-z]+Bot|jbot\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\.RU_Bot|masidani_bot|Mediapartners-Google|Microsoft .*? Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media *)?|msrbot|netresearch|Netvibes|NewsGator[^/]*|^NING|Nutch[^/]*|Nymesis|ObjectsSearch|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo Screenshot Bot|PlantyNet_WebRobot|Pompos|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slurp|snappy|Speedy Spider|Squrl Java|TheUsefulbot|ThumbShotsBot|Thumbshots\.ru|TwitterBot|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]+|WhatWeb|WIRE|WordPress|Wotbox|www\.almaden\.ibm\.com|Xenu(?:.s)? Link Sleuth|Xerka [A-z]+Bot|yacy(?:bot)?|Yahoo[a-z]*Seeker|Yahoo! Slurp|Yandex\w+|YodaoBot(?:-[A-z]+)?|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\.ze\.bz|ZooShot|ZyBorg)(?:[ /]v?(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?' + + # Bots General matcher 'name/0.0' + - regex: '(?:\/[A-Za-z0-9\.]+)? *([A-Za-z0-9 \-_\!\[\]:]*(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]*))/(\d+)(?:\.(\d+)(?:\.(\d+))?)?' + # Bots General matcher 'name 0.0' + - regex: '(?:\/[A-Za-z0-9\.]+)? *([A-Za-z0-9 _\!\[\]:]*(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]*)) (\d+)(?:\.(\d+)(?:\.(\d+))?)?' + # Bots containing spider|scrape|bot(but not CUBOT)|Crawl + - regex: '((?:[A-z0-9]+|[A-z\-]+ ?)?(?: the )?(?:[Ss][Pp][Ii][Dd][Ee][Rr]|[Ss]crape|[A-Za-z0-9-]*(?:[^C][^Uu])[Bb]ot|[Cc][Rr][Aa][Ww][Ll])[A-z0-9]*)(?:(?:[ /]| v)(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?' + + # HbbTV standard defines what features the browser should understand. + # but it's like targeting "HTML5 browsers", effective browser support depends on the model + # See os_parsers if you want to target a specific TV + - regex: '(HbbTV)/(\d+)\.(\d+)\.(\d+) \(' + + # must go before Firefox to catch Chimera/SeaMonkey/Camino + - regex: '(Chimera|SeaMonkey|Camino)/(\d+)\.(\d+)\.?([ab]?\d+[a-z]*)?' + + # Social Networks + # Facebook + - regex: '\[FB.*;(FBAV)/(\d+)(?:\.(\d+)(?:\.(\d)+)?)?' + family_replacement: 'Facebook' + # Pinterest + - regex: '\[(Pinterest)/[^\]]+\]' + - regex: '(Pinterest)(?: for Android(?: Tablet)?)?/(\d+)(?:\.(\d+)(?:\.(\d)+)?)?' + + # Firefox + - regex: '(Pale[Mm]oon)/(\d+)\.(\d+)\.?(\d+)?' + family_replacement: 'Pale Moon (Firefox Variant)' + - regex: '(Fennec)/(\d+)\.(\d+)\.?([ab]?\d+[a-z]*)' + family_replacement: 'Firefox Mobile' + - regex: '(Fennec)/(\d+)\.(\d+)(pre)' + family_replacement: 'Firefox Mobile' + - regex: '(Fennec)/(\d+)\.(\d+)' + family_replacement: 'Firefox Mobile' + - regex: '(?:Mobile|Tablet);.*(Firefox)/(\d+)\.(\d+)' + family_replacement: 'Firefox Mobile' + - regex: '(Namoroka|Shiretoko|Minefield)/(\d+)\.(\d+)\.(\d+(?:pre)?)' + family_replacement: 'Firefox ($1)' + - regex: '(Firefox)/(\d+)\.(\d+)(a\d+[a-z]*)' + family_replacement: 'Firefox Alpha' + - regex: '(Firefox)/(\d+)\.(\d+)(b\d+[a-z]*)' + family_replacement: 'Firefox Beta' + - regex: '(Firefox)-(?:\d+\.\d+)?/(\d+)\.(\d+)(a\d+[a-z]*)' + family_replacement: 'Firefox Alpha' + - regex: '(Firefox)-(?:\d+\.\d+)?/(\d+)\.(\d+)(b\d+[a-z]*)' + family_replacement: 'Firefox Beta' + - regex: '(Namoroka|Shiretoko|Minefield)/(\d+)\.(\d+)([ab]\d+[a-z]*)?' + family_replacement: 'Firefox ($1)' + - regex: '(Firefox).*Tablet browser (\d+)\.(\d+)\.(\d+)' + family_replacement: 'MicroB' + - regex: '(MozillaDeveloperPreview)/(\d+)\.(\d+)([ab]\d+[a-z]*)?' + - regex: '(FxiOS)/(\d+)\.(\d+)(\.(\d+))?(\.(\d+))?' + family_replacement: 'Firefox iOS' + + # e.g.: Flock/2.0b2 + - regex: '(Flock)/(\d+)\.(\d+)(b\d+?)' + + # RockMelt + - regex: '(RockMelt)/(\d+)\.(\d+)\.(\d+)' + + # e.g.: Fennec/0.9pre + - regex: '(Navigator)/(\d+)\.(\d+)\.(\d+)' + family_replacement: 'Netscape' + + - regex: '(Navigator)/(\d+)\.(\d+)([ab]\d+)' + family_replacement: 'Netscape' + + - regex: '(Netscape6)/(\d+)\.(\d+)\.?([ab]?\d+)?' + family_replacement: 'Netscape' + + - regex: '(MyIBrow)/(\d+)\.(\d+)' + family_replacement: 'My Internet Browser' + + # Opera will stop at 9.80 and hide the real version in the Version string. + # see: http://dev.opera.com/articles/view/opera-ua-string-changes/ + - regex: '(Opera Tablet).*Version/(\d+)\.(\d+)(?:\.(\d+))?' + - regex: '(Opera Mini)(?:/att)?/?(\d+)?(?:\.(\d+))?(?:\.(\d+))?' + - regex: '(Opera)/.+Opera Mobi.+Version/(\d+)\.(\d+)' + family_replacement: 'Opera Mobile' + - regex: '(Opera)/(\d+)\.(\d+).+Opera Mobi' + family_replacement: 'Opera Mobile' + - regex: 'Opera Mobi.+(Opera)(?:/|\s+)(\d+)\.(\d+)' + family_replacement: 'Opera Mobile' + - regex: 'Opera Mobi' + family_replacement: 'Opera Mobile' + - regex: '(Opera)/9.80.*Version/(\d+)\.(\d+)(?:\.(\d+))?' + + # Opera 14 for Android uses a WebKit render engine. + - regex: '(?:Mobile Safari).*(OPR)/(\d+)\.(\d+)\.(\d+)' + family_replacement: 'Opera Mobile' + + # Opera >=15 for Desktop is similar to Chrome but includes an "OPR" Version string. + - regex: '(?:Chrome).*(OPR)/(\d+)\.(\d+)\.(\d+)' + family_replacement: 'Opera' + + # Opera Coast + - regex: '(Coast)/(\d+).(\d+).(\d+)' + family_replacement: 'Opera Coast' + + # Opera Mini for iOS (from version 8.0.0) + - regex: '(OPiOS)/(\d+).(\d+).(\d+)' + family_replacement: 'Opera Mini' + + # Palm WebOS looks a lot like Safari. + - regex: '(hpw|web)OS/(\d+)\.(\d+)(?:\.(\d+))?' + family_replacement: 'webOS Browser' + + # LuaKit has no version info. + # http://luakit.org/projects/luakit/ + - regex: '(luakit)' + family_replacement: 'LuaKit' + + # Snowshoe + - regex: '(Snowshoe)/(\d+)\.(\d+).(\d+)' + + # Lightning (for Thunderbird) + # http://www.mozilla.org/projects/calendar/lightning/ + - regex: '(Lightning)/(\d+)\.(\d+)\.?((?:[ab]?\d+[a-z]*)|(?:\d*))' + + # Swiftfox + - regex: '(Firefox)/(\d+)\.(\d+)\.(\d+(?:pre)?) \(Swiftfox\)' + family_replacement: 'Swiftfox' + - regex: '(Firefox)/(\d+)\.(\d+)([ab]\d+[a-z]*)? \(Swiftfox\)' + family_replacement: 'Swiftfox' + + # Rekonq + - regex: '(rekonq)/(\d+)\.(\d+)\.?(\d+)? Safari' + family_replacement: 'Rekonq' + - regex: 'rekonq' + family_replacement: 'Rekonq' + + # Conkeror lowercase/uppercase + # http://conkeror.org/ + - regex: '(conkeror|Conkeror)/(\d+)\.(\d+)\.?(\d+)?' + family_replacement: 'Conkeror' + + # catches lower case konqueror + - regex: '(konqueror)/(\d+)\.(\d+)\.(\d+)' + family_replacement: 'Konqueror' + + - regex: '(WeTab)-Browser' + + - regex: '(Comodo_Dragon)/(\d+)\.(\d+)\.(\d+)' + family_replacement: 'Comodo Dragon' + + - regex: '(Symphony) (\d+).(\d+)' + + - regex: '(Minimo)' + + - regex: 'PLAYSTATION 3.+WebKit' + family_replacement: 'NetFront NX' + - regex: 'PLAYSTATION 3' + family_replacement: 'NetFront' + - regex: '(PlayStation Portable)' + family_replacement: 'NetFront' + - regex: '(PlayStation Vita)' + family_replacement: 'NetFront NX' + + - regex: 'AppleWebKit.+ (NX)/(\d+)\.(\d+)\.(\d+)' + family_replacement: 'NetFront NX' + - regex: '(Nintendo 3DS)' + family_replacement: 'NetFront NX' + + # Amazon Silk, should go before Safari and Chrome Mobile + - regex: '(Silk)/(\d+)\.(\d+)(?:\.([0-9\-]+))?' + family_replacement: 'Amazon Silk' + + + # @ref: http://www.puffinbrowser.com + - regex: '(Puffin)/(\d+)\.(\d+)(?:\.(\d+))?' + + # Edge Mobile + - regex: 'Windows Phone .*(Edge)/(\d+)\.(\d+)' + family_replacement: 'Edge Mobile' + + # Samsung Internet (based on Chrome, but lacking some features) + - regex: '(SamsungBrowser)/(\d+)\.(\d+)' + family_replacement: 'Samsung Internet' + + # Chrome Mobile + - regex: '(CrMo)/(\d+)\.(\d+)\.(\d+)\.(\d+)' + family_replacement: 'Chrome Mobile' + - regex: '(CriOS)/(\d+)\.(\d+)\.(\d+)\.(\d+)' + family_replacement: 'Chrome Mobile iOS' + - regex: '(Chrome)/(\d+)\.(\d+)\.(\d+)\.(\d+) Mobile' + family_replacement: 'Chrome Mobile' + + # Chrome Frame must come before MSIE. + - regex: '(chromeframe)/(\d+)\.(\d+)\.(\d+)' + family_replacement: 'Chrome Frame' + + # UC Browser + - regex: '(UCBrowser)[ /](\d+)\.(\d+)\.(\d+)' + family_replacement: 'UC Browser' + - regex: '(UC Browser)[ /](\d+)\.(\d+)\.(\d+)' + - regex: '(UC Browser|UCBrowser|UCWEB)(\d+)\.(\d+)\.(\d+)' + family_replacement: 'UC Browser' + + # Tizen Browser (second case included in browser/major.minor regex) + - regex: '(SLP Browser)/(\d+)\.(\d+)' + family_replacement: 'Tizen Browser' + + # Sogou Explorer 2.X + - regex: '(SE 2\.X) MetaSr (\d+)\.(\d+)' + family_replacement: 'Sogou Explorer' + + # Baidu Browsers (desktop spoofs chrome & IE, explorer is mobile) + - regex: '(baidubrowser)[/\s](\d+)' + family_replacement: 'Baidu Browser' + - regex: '(FlyFlow)/(\d+)\.(\d+)' + family_replacement: 'Baidu Explorer' + + # QQ Browsers + - regex: '(MQQBrowser/Mini)(?:(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?' + family_replacement: 'QQ Browser Mini' + - regex: '(MQQBrowser)(?:/(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?' + family_replacement: 'QQ Browser Mobile' + - regex: '(QQBrowser)(?:/(\d+)(?:\.(\d+)\.(\d+)(?:\.(\d+))?)?)?' + family_replacement: 'QQ Browser' + + # Rackspace Monitoring + - regex: '(Rackspace Monitoring)/(\d+)\.(\d+)' + family_replacement: 'RackspaceBot' + + # PyAMF + - regex: '(PyAMF)/(\d+)\.(\d+)\.(\d+)' + + # Yandex Browser + - regex: '(YaBrowser)/(\d+)\.(\d+)\.(\d+)' + family_replacement: 'Yandex Browser' + + # Mail.ru Amigo/Internet Browser (Chromium-based) + - regex: '(Chrome)/(\d+)\.(\d+)\.(\d+).* MRCHROME' + family_replacement: 'Mail.ru Chromium Browser' + + # AOL Browser (IE-based) + - regex: '(AOL) (\d+)\.(\d+); AOLBuild (\d+)' + + #### END SPECIAL CASES TOP #### + + #### MAIN CASES - this catches > 50% of all browsers #### + + # Browser/major_version.minor_version.beta_version + - regex: '(AdobeAIR|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Epiphany|Shiira|Sunrise|Spotify|Flock|Netscape|Lunascape|WebPilot|NetFront|Netfront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Iris|UP\.Browser|Bunjalloo|Google Earth|Raven for Mac|Openwave)/(\d+)\.(\d+)\.(\d+)' + + # Outlook 2007 + - regex: 'Microsoft Office Outlook 12\.\d+\.\d+|MSOffice 12' + family_replacement: 'Outlook' + v1_replacement: '2007' + + # Outlook 2010 + - regex: 'Microsoft Outlook 14\.\d+\.\d+|MSOffice 14' + family_replacement: 'Outlook' + v1_replacement: '2010' + + # Outlook 2013 + - regex: 'Microsoft Outlook 15\.\d+\.\d+' + family_replacement: 'Outlook' + v1_replacement: '2013' + + # Outlook 2016 + - regex: 'Microsoft Outlook (?:Mail )?16\.\d+\.\d+' + family_replacement: 'Outlook' + v1_replacement: '2016' + + # Windows Live Mail + - regex: 'Outlook-Express\/7\.0.*' + family_replacement: 'Windows Live Mail' + + # Apple Air Mail + - regex: '(Airmail) (\d+)\.(\d+)(?:\.(\d+))?' + + # Thunderbird + - regex: '(Thunderbird)/(\d+)\.(\d+)\.(\d+(?:pre)?)' + family_replacement: 'Thunderbird' + + # Vivaldi uses "Vivaldi" + - regex: '(Vivaldi)/(\d+)\.(\d+)\.(\d+)' + + # Edge/major_version.minor_version + - regex: '(Edge)/(\d+)\.(\d+)' + + # Brave Browser https://brave.com/ + - regex: '(brave)/(\d+)\.(\d+)\.(\d+) Chrome' + family_replacement: 'Brave' + + # Chrome/Chromium/major_version.minor_version.beta_version + - regex: '(Chromium|Chrome)/(\d+)\.(\d+)\.(\d+)' + + # Dolphin Browser + # @ref: http://www.dolphin.com + - regex: '\b(Dolphin)(?: |HDCN/|/INT\-)(\d+)\.(\d+)\.?(\d+)?' + + # Browser/major_version.minor_version + - regex: '(bingbot|Bolt|Jasmine|IceCat|Skyfire|Midori|Maxthon|Lynx|Arora|IBrowse|Dillo|Camino|Shiira|Fennec|Phoenix|Chrome|Flock|Netscape|Lunascape|Epiphany|WebPilot|Opera Mini|Opera|NetFront|Netfront|Konqueror|Googlebot|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|iCab|iTunes|MacAppStore|NetNewsWire|Space Bison|Stainless|Orca|Dolfin|BOLT|Minimo|Tizen Browser|Polaris|Abrowser|Planetweb|ICE Browser|mDolphin|qutebrowser|Otter|QupZilla)/(\d+)\.(\d+)\.?(\d+)?' + + # Chrome/Chromium/major_version.minor_version + - regex: '(Chromium|Chrome)/(\d+)\.(\d+)' + + ########## + # IE Mobile needs to happen before Android to catch cases such as: + # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920)... + # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; ANZ821)... + # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; Orange)... + # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; Vodafone)... + ########## + + # IE Mobile + - regex: '(IEMobile)[ /](\d+)\.(\d+)' + family_replacement: 'IE Mobile' + + # Browser major_version.minor_version.beta_version (space instead of slash) + - regex: '(iRider|Crazy Browser|SkipStone|iCab|Lunascape|Sleipnir|Maemo Browser) (\d+)\.(\d+)\.(\d+)' + # Browser major_version.minor_version (space instead of slash) + - regex: '(iCab|Lunascape|Opera|Android|Jasmine|Polaris) (\d+)\.(\d+)\.?(\d+)?' + + # Kindle WebKit + - regex: '(Kindle)/(\d+)\.(\d+)' + + # weird android UAs + - regex: '(Android) Donut' + v1_replacement: '1' + v2_replacement: '2' + + - regex: '(Android) Eclair' + v1_replacement: '2' + v2_replacement: '1' + + - regex: '(Android) Froyo' + v1_replacement: '2' + v2_replacement: '2' + + - regex: '(Android) Gingerbread' + v1_replacement: '2' + v2_replacement: '3' + + - regex: '(Android) Honeycomb' + v1_replacement: '3' + + # desktop mode + # http://www.anandtech.com/show/3982/windows-phone-7-review + - regex: '(MSIE) (\d+)\.(\d+).*XBLWP7' + family_replacement: 'IE Large Screen' + + #### END MAIN CASES #### + + #### SPECIAL CASES #### + - regex: '(Obigo)InternetBrowser' + - regex: '(Obigo)\-Browser' + - regex: '(Obigo|OBIGO)[^\d]*(\d+)(?:.(\d+))?' + family_replacement: 'Obigo' + + - regex: '(MAXTHON|Maxthon) (\d+)\.(\d+)' + family_replacement: 'Maxthon' + - regex: '(Maxthon|MyIE2|Uzbl|Shiira)' + v1_replacement: '0' + + - regex: '(BrowseX) \((\d+)\.(\d+)\.(\d+)' + + - regex: '(NCSA_Mosaic)/(\d+)\.(\d+)' + family_replacement: 'NCSA Mosaic' + + # Polaris/d.d is above + - regex: '(POLARIS)/(\d+)\.(\d+)' + family_replacement: 'Polaris' + - regex: '(Embider)/(\d+)\.(\d+)' + family_replacement: 'Polaris' + + - regex: '(BonEcho)/(\d+)\.(\d+)\.?([ab]?\d+)?' + family_replacement: 'Bon Echo' + + # @note: iOS / OSX Applications + - regex: '(iPod|iPhone|iPad).+Version/(\d+)\.(\d+)(?:\.(\d+))?.* Safari' + family_replacement: 'Mobile Safari' + - regex: '(iPod|iPhone|iPad).+Version/(\d+)\.(\d+)(?:\.(\d+))?' + family_replacement: 'Mobile Safari UI/WKWebView' + - regex: '(iPod|iPhone|iPad);.*CPU.*OS (\d+)_(\d+)(?:_(\d+))?.*Mobile.* Safari' + family_replacement: 'Mobile Safari' + - regex: '(iPod|iPhone|iPad);.*CPU.*OS (\d+)_(\d+)(?:_(\d+))?.*Mobile' + family_replacement: 'Mobile Safari UI/WKWebView' + - regex: '(iPod|iPhone|iPad).* Safari' + family_replacement: 'Mobile Safari' + - regex: '(iPod|iPhone|iPad)' + family_replacement: 'Mobile Safari UI/WKWebView' + + - regex: '(AvantGo) (\d+).(\d+)' + + - regex: '(OneBrowser)/(\d+).(\d+)' + family_replacement: 'ONE Browser' + + - regex: '(Avant)' + v1_replacement: '1' + + # This is the Tesla Model S (see similar entry in device parsers) + - regex: '(QtCarBrowser)' + v1_replacement: '1' + + - regex: '^(iBrowser/Mini)(\d+).(\d+)' + family_replacement: 'iBrowser Mini' + - regex: '^(iBrowser|iRAPP)/(\d+).(\d+)' + + # nokia browsers + # based on: http://www.developer.nokia.com/Community/Wiki/User-Agent_headers_for_Nokia_devices + - regex: '^(Nokia)' + family_replacement: 'Nokia Services (WAP) Browser' + - regex: '(NokiaBrowser)/(\d+)\.(\d+).(\d+)\.(\d+)' + family_replacement: 'Nokia Browser' + - regex: '(NokiaBrowser)/(\d+)\.(\d+).(\d+)' + family_replacement: 'Nokia Browser' + - regex: '(NokiaBrowser)/(\d+)\.(\d+)' + family_replacement: 'Nokia Browser' + - regex: '(BrowserNG)/(\d+)\.(\d+).(\d+)' + family_replacement: 'Nokia Browser' + - regex: '(Series60)/5\.0' + family_replacement: 'Nokia Browser' + v1_replacement: '7' + v2_replacement: '0' + - regex: '(Series60)/(\d+)\.(\d+)' + family_replacement: 'Nokia OSS Browser' + - regex: '(S40OviBrowser)/(\d+)\.(\d+)\.(\d+)\.(\d+)' + family_replacement: 'Ovi Browser' + - regex: '(Nokia)[EN]?(\d+)' + + # BlackBerry devices + - regex: '(PlayBook).+RIM Tablet OS (\d+)\.(\d+)\.(\d+)' + family_replacement: 'BlackBerry WebKit' + - regex: '(Black[bB]erry|BB10).+Version/(\d+)\.(\d+)\.(\d+)' + family_replacement: 'BlackBerry WebKit' + - regex: '(Black[bB]erry)\s?(\d+)' + family_replacement: 'BlackBerry' + + - regex: '(OmniWeb)/v(\d+)\.(\d+)' + + - regex: '(Blazer)/(\d+)\.(\d+)' + family_replacement: 'Palm Blazer' + + - regex: '(Pre)/(\d+)\.(\d+)' + family_replacement: 'Palm Pre' + + # fork of Links + - regex: '(ELinks)/(\d+)\.(\d+)' + - regex: '(ELinks) \((\d+)\.(\d+)' + - regex: '(Links) \((\d+)\.(\d+)' + + - regex: '(QtWeb) Internet Browser/(\d+)\.(\d+)' + + #- regex: '\(iPad;.+(Version)/(\d+)\.(\d+)(?:\.(\d+))?.*Safari/' + # family_replacement: 'iPad' + + # Phantomjs, should go before Safari + - regex: '(PhantomJS)/(\d+)\.(\d+)\.(\d+)' + + # WebKit Nightly + - regex: '(AppleWebKit)/(\d+)\.?(\d+)?\+ .* Safari' + family_replacement: 'WebKit Nightly' + + # Safari + - regex: '(Version)/(\d+)\.(\d+)(?:\.(\d+))?.*Safari/' + family_replacement: 'Safari' + # Safari didn't provide "Version/d.d.d" prior to 3.0 + - regex: '(Safari)/\d+' + + - regex: '(OLPC)/Update(\d+)\.(\d+)' + + - regex: '(OLPC)/Update()\.(\d+)' + v1_replacement: '0' + + - regex: '(SEMC\-Browser)/(\d+)\.(\d+)' + + - regex: '(Teleca)' + family_replacement: 'Teleca Browser' + + - regex: '(Phantom)/V(\d+)\.(\d+)' + family_replacement: 'Phantom Browser' + + - regex: 'Trident(.*)rv.(\d+)\.(\d+)' + family_replacement: 'IE' + + # Espial + - regex: '(Espial)/(\d+)(?:\.(\d+))?(?:\.(\d+))?' + + # Apple Mail + + # apple mail - not directly detectable, have it after Safari stuff + - regex: '(AppleWebKit)/(\d+)\.(\d+)\.(\d+)' + family_replacement: 'Apple Mail' + + # AFTER THE EDGE CASES ABOVE! + # AFTER IE11 + # BEFORE all other IE + - regex: '(Firefox)/(\d+)\.(\d+)\.(\d+)' + - regex: '(Firefox)/(\d+)\.(\d+)(pre|[ab]\d+[a-z]*)?' + + - regex: '([MS]?IE) (\d+)\.(\d+)' + family_replacement: 'IE' + + - regex: '(python-requests)/(\d+)\.(\d+)' + family_replacement: 'Python Requests' + + - regex: '(Java)[/ ]{0,1}\d+\.(\d+)\.(\d+)[_-]*([a-zA-Z0-9]+)*' + + # Roku Digital-Video-Players https://www.roku.com/ + - regex: '^(Roku)/DVP-(\d+)\.(\d+)' + +os_parsers: + ########## + # HbbTV vendors + ########## + + # starts with the easy one : Panasonic seems consistent across years, hope it will continue + #HbbTV/1.1.1 (;Panasonic;VIERA 2011;f.532;0071-0802 2000-0000;) + #HbbTV/1.1.1 (;Panasonic;VIERA 2012;1.261;0071-3103 2000-0000;) + #HbbTV/1.2.1 (;Panasonic;VIERA 2013;3.672;4101-0003 0002-0000;) + #- regex: 'HbbTV/\d+\.\d+\.\d+ \(;(Panasonic);VIERA ([0-9]{4});' + + # Sony is consistent too but do not place year like the other + # Opera/9.80 (Linux armv7l; HbbTV/1.1.1 (; Sony; KDL32W650A; PKG3.211EUA; 2013;); ) Presto/2.12.362 Version/12.11 + # Opera/9.80 (Linux mips; U; HbbTV/1.1.1 (; Sony; KDL40HX751; PKG1.902EUA; 2012;);; en) Presto/2.10.250 Version/11.60 + # Opera/9.80 (Linux mips; U; HbbTV/1.1.1 (; Sony; KDL22EX320; PKG4.017EUA; 2011;);; en) Presto/2.7.61 Version/11.00 + #- regex: 'HbbTV/\d+\.\d+\.\d+ \(; (Sony);.*;.*; ([0-9]{4});\)' + + + # LG is consistent too, but we need to add manually the year model + #Mozilla/5.0 (Unknown; Linux armv7l) AppleWebKit/537.1+ (KHTML, like Gecko) Safari/537.1+ HbbTV/1.1.1 ( ;LGE ;NetCast 4.0 ;03.20.30 ;1.0M ;) + #Mozilla/5.0 (DirectFB; Linux armv7l) AppleWebKit/534.26+ (KHTML, like Gecko) Version/5.0 Safari/534.26+ HbbTV/1.1.1 ( ;LGE ;NetCast 3.0 ;1.0 ;1.0M ;) + - regex: 'HbbTV/\d+\.\d+\.\d+ \( ;(LG)E ;NetCast 4.0' + os_v1_replacement: '2013' + - regex: 'HbbTV/\d+\.\d+\.\d+ \( ;(LG)E ;NetCast 3.0' + os_v1_replacement: '2012' + + # Samsung is on its way of normalizing their user-agent + # HbbTV/1.1.1 (;Samsung;SmartTV2013;T-FXPDEUC-1102.2;;) WebKit + # HbbTV/1.1.1 (;Samsung;SmartTV2013;T-MST12DEUC-1102.1;;) WebKit + # HbbTV/1.1.1 (;Samsung;SmartTV2012;;;) WebKit + # HbbTV/1.1.1 (;;;;;) Maple_2011 + - regex: 'HbbTV/1.1.1 \(;;;;;\) Maple_2011' + os_replacement: 'Samsung' + os_v1_replacement: '2011' + # manage the two models of 2013 + - regex: 'HbbTV/\d+\.\d+\.\d+ \(;(Samsung);SmartTV([0-9]{4});.*FXPDEUC' + os_v2_replacement: 'UE40F7000' + - regex: 'HbbTV/\d+\.\d+\.\d+ \(;(Samsung);SmartTV([0-9]{4});.*MST12DEUC' + os_v2_replacement: 'UE32F4500' + # generic Samsung (works starting in 2012) + #- regex: 'HbbTV/\d+\.\d+\.\d+ \(;(Samsung);SmartTV([0-9]{4});' + + # Philips : not found any other way than a manual mapping + # Opera/9.80 (Linux mips; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/4.1.3 PHILIPSTV/1.1.1; en) Presto/2.10.250 Version/11.60 + # Opera/9.80 (Linux mips ; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/3.2.1; en) Presto/2.6.33 Version/10.70 + - regex: 'HbbTV/1.1.1 \(; (Philips);.*NETTV/4' + os_v1_replacement: '2013' + - regex: 'HbbTV/1.1.1 \(; (Philips);.*NETTV/3' + os_v1_replacement: '2012' + - regex: 'HbbTV/1.1.1 \(; (Philips);.*NETTV/2' + os_v1_replacement: '2011' + + # the HbbTV emulator developers use HbbTV/1.1.1 (;;;;;) firetv-firefox-plugin 1.1.20 + - regex: 'HbbTV/\d+\.\d+\.\d+.*(firetv)-firefox-plugin (\d+).(\d+).(\d+)' + os_replacement: 'FireHbbTV' + + # generic HbbTV, hoping to catch manufacturer name (always after 2nd comma) and the first string that looks like a 2011-2019 year + - regex: 'HbbTV/\d+\.\d+\.\d+ \(.*; ?([a-zA-Z]+) ?;.*(201[1-9]).*\)' + + ########## + # @note: Windows Phone needs to come before Windows NT 6.1 *and* before Android to catch cases such as: + # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920)... + # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; ANZ821)... + # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; Orange)... + # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; Vodafone)... + ########## + + - regex: '(Windows Phone) (?:OS[ /])?(\d+)\.(\d+)' + + ########## + # Android + # can actually detect rooted android os. do we care? + ########## + - regex: '(Android)[ \-/](\d+)\.(\d+)(?:[.\-]([a-z0-9]+))?' + + - regex: '(Android) Donut' + os_v1_replacement: '1' + os_v2_replacement: '2' + + - regex: '(Android) Eclair' + os_v1_replacement: '2' + os_v2_replacement: '1' + + - regex: '(Android) Froyo' + os_v1_replacement: '2' + os_v2_replacement: '2' + + - regex: '(Android) Gingerbread' + os_v1_replacement: '2' + os_v2_replacement: '3' + + - regex: '(Android) Honeycomb' + os_v1_replacement: '3' + + # UCWEB + - regex: '^UCWEB.*; (Adr) (\d+)\.(\d+)(?:[.\-]([a-z0-9]+))?;' + os_replacement: 'Android' + - regex: '^UCWEB.*; (iPad OS|iPh OS) (\d+)_(\d+)(?:_(\d+))?;' + os_replacement: 'iOS' + - regex: '^UCWEB.*; (wds) (\d+)\.(\d+)(?:\.(\d+))?;' + os_replacement: 'Windows Phone' + # JUC + - regex: '^(JUC).*; ?U; ?(?:Android)?(\d+)\.(\d+)(?:[\.\-]([a-z0-9]+))?' + os_replacement: 'Android' + + ########## + # Kindle Android + ########## + - regex: '(Silk-Accelerated=[a-z]{4,5})' + os_replacement: 'Android' + + ########## + # Windows + # http://en.wikipedia.org/wiki/Windows_NT#Releases + # possibility of false positive when different marketing names share same NT kernel + # e.g. windows server 2003 and windows xp + # lots of ua strings have Windows NT 4.1 !?!?!?!? !?!? !? !????!?! !!! ??? !?!?! ? + # (very) roughly ordered in terms of frequency of occurence of regex (win xp currently most frequent, etc) + ########## + + # ie mobile desktop mode + # spoofs nt 6.1. must come before windows 7 + - regex: '(XBLWP7)' + os_replacement: 'Windows Phone' + + # @note: This needs to come before Windows NT 6.1 + - regex: '(Windows ?Mobile)' + os_replacement: 'Windows Mobile' + + - regex: '(Windows (?:NT 5\.2|NT 5\.1))' + os_replacement: 'Windows XP' + + - regex: '(Windows NT 6\.1)' + os_replacement: 'Windows 7' + + - regex: '(Windows NT 6\.0)' + os_replacement: 'Windows Vista' + + - regex: '(Win 9x 4\.90)' + os_replacement: 'Windows ME' + + - regex: '(Windows 98|Windows XP|Windows ME|Windows 95|Windows CE|Windows 7|Windows NT 4\.0|Windows Vista|Windows 2000|Windows 3.1)' + + - regex: '(Windows NT 6\.2; ARM;)' + os_replacement: 'Windows RT' + - regex: '(Windows NT 6\.2)' + os_replacement: 'Windows 8' + + - regex: '(Windows NT 6\.3; ARM;)' + os_replacement: 'Windows RT 8.1' + - regex: '(Windows NT 6\.3)' + os_replacement: 'Windows 8.1' + + - regex: '(Windows NT 6\.4)' + os_replacement: 'Windows 10' + - regex: '(Windows NT 10\.0)' + os_replacement: 'Windows 10' + + - regex: '(Windows NT 5\.0)' + os_replacement: 'Windows 2000' + + - regex: '(WinNT4.0)' + os_replacement: 'Windows NT 4.0' + + - regex: '(Windows ?CE)' + os_replacement: 'Windows CE' + + - regex: 'Win ?(95|98|3.1|NT|ME|2000)' + os_replacement: 'Windows $1' + + - regex: 'Win16' + os_replacement: 'Windows 3.1' + + - regex: 'Win32' + os_replacement: 'Windows 95' + + ########## + # Tizen OS from Samsung + # spoofs Android so pushing it above + ########## + - regex: '(Tizen)/(\d+)\.(\d+)' + + ########## + # Mac OS + # @ref: http://en.wikipedia.org/wiki/Mac_OS_X#Versions + # @ref: http://www.puredarwin.org/curious/versions + ########## + - regex: '((?:Mac ?|; )OS X)[\s/](?:(\d+)[_.](\d+)(?:[_.](\d+))?|Mach-O)' + os_replacement: 'Mac OS X' + # Leopard + - regex: ' (Dar)(win)/(9).(\d+).*\((?:i386|x86_64|Power Macintosh)\)' + os_replacement: 'Mac OS X' + os_v1_replacement: '10' + os_v2_replacement: '5' + # Snow Leopard + - regex: ' (Dar)(win)/(10).(\d+).*\((?:i386|x86_64)\)' + os_replacement: 'Mac OS X' + os_v1_replacement: '10' + os_v2_replacement: '6' + # Lion + - regex: ' (Dar)(win)/(11).(\d+).*\((?:i386|x86_64)\)' + os_replacement: 'Mac OS X' + os_v1_replacement: '10' + os_v2_replacement: '7' + # Mountain Lion + - regex: ' (Dar)(win)/(12).(\d+).*\((?:i386|x86_64)\)' + os_replacement: 'Mac OS X' + os_v1_replacement: '10' + os_v2_replacement: '8' + # Mavericks + - regex: ' (Dar)(win)/(13).(\d+).*\((?:i386|x86_64)\)' + os_replacement: 'Mac OS X' + os_v1_replacement: '10' + os_v2_replacement: '9' + # Yosemite is Darwin/14.x but patch versions are inconsistent in the Darwin string; + # more accurately covered by CFNetwork regexes downstream + + # IE on Mac doesn't specify version number + - regex: 'Mac_PowerPC' + os_replacement: 'Mac OS' + + # builds before tiger don't seem to specify version? + + # ios devices spoof (mac os x), so including intel/ppc prefixes + - regex: '(?:PPC|Intel) (Mac OS X)' + + ########## + # iOS + # http://en.wikipedia.org/wiki/IOS_version_history + ########## + # keep this above generic iOS, since AppleTV UAs contain 'CPU OS' + - regex: '(Apple\s?TV)(?:/(\d+)\.(\d+))?' + os_replacement: 'ATV OS X' + + - regex: '(CPU OS|iPhone OS|CPU iPhone) +(\d+)[_\.](\d+)(?:[_\.](\d+))?' + os_replacement: 'iOS' + + # remaining cases are mostly only opera uas, so catch opera as to not catch iphone spoofs + - regex: '(iPhone|iPad|iPod); Opera' + os_replacement: 'iOS' + + # few more stragglers + - regex: '(iPhone|iPad|iPod).*Mac OS X.*Version/(\d+)\.(\d+)' + os_replacement: 'iOS' + + # CFNetwork/Darwin - The specific CFNetwork or Darwin version determines + # whether the os maps to Mac OS, or iOS, or just Darwin. + # See: http://user-agents.me/cfnetwork-version-list + - regex: '(CFNetwork)/(5)48\.0\.3.* Darwin/11\.0\.0' + os_replacement: 'iOS' + - regex: '(CFNetwork)/(5)48\.(0)\.4.* Darwin/(1)1\.0\.0' + os_replacement: 'iOS' + - regex: '(CFNetwork)/(5)48\.(1)\.4' + os_replacement: 'iOS' + - regex: '(CFNetwork)/(4)85\.1(3)\.9' + os_replacement: 'iOS' + - regex: '(CFNetwork)/(6)09\.(1)\.4' + os_replacement: 'iOS' + - regex: '(CFNetwork)/(6)(0)9' + os_replacement: 'iOS' + - regex: '(CFNetwork)/6(7)2\.(1)\.13' + os_replacement: 'iOS' + - regex: '(CFNetwork)/6(7)2\.(1)\.(1)4' + os_replacement: 'iOS' + - regex: '(CF)(Network)/6(7)(2)\.1\.15' + os_replacement: 'iOS' + os_v1_replacement: '7' + os_v2_replacement: '1' + - regex: '(CFNetwork)/6(7)2\.(0)\.(?:2|8)' + os_replacement: 'iOS' + - regex: '(CFNetwork)/709\.1' + os_replacement: 'iOS' + os_v1_replacement: '8' + os_v2_replacement: '0.b5' + - regex: '(CF)(Network)/711\.(\d)' + os_replacement: 'iOS' + os_v1_replacement: '8' + - regex: '(CF)(Network)/(720)\.(\d)' + os_replacement: 'Mac OS X' + os_v1_replacement: '10' + os_v2_replacement: '10' + - regex: '(CF)(Network)/758\.(\d)' + os_replacement: 'iOS' + os_v1_replacement: '9' + + ########## + # CFNetwork iOS Apps + # @ref: https://en.wikipedia.org/wiki/Darwin_(operating_system)#Release_history + ########## + - regex: 'CFNetwork/.* Darwin/(9)\.\d+' + os_replacement: 'iOS' + os_v1_replacement: '1' + - regex: 'CFNetwork/.* Darwin/(10)\.\d+' + os_replacement: 'iOS' + os_v1_replacement: '4' + - regex: 'CFNetwork/.* Darwin/(11)\.\d+' + os_replacement: 'iOS' + os_v1_replacement: '5' + - regex: 'CFNetwork/.* Darwin/(13)\.\d+' + os_replacement: 'iOS' + os_v1_replacement: '6' + - regex: 'CFNetwork/6.* Darwin/(14)\.\d+' + os_replacement: 'iOS' + os_v1_replacement: '7' + - regex: 'CFNetwork/7.* Darwin/(14)\.\d+' + os_replacement: 'iOS' + os_v1_replacement: '8' + os_v2_replacement: '0' + - regex: 'CFNetwork/7.* Darwin/(15)\.\d+' + os_replacement: 'iOS' + os_v1_replacement: '9' + os_v2_replacement: '0' + # iOS Apps + - regex: '\b(iOS[ /]|iPhone(?:/| v|[ _]OS[/,]|; | OS : |\d,\d/|\d,\d; )|iPad/)(\d{1,2})[_\.](\d{1,2})(?:[_\.](\d+))?' + os_replacement: 'iOS' + + ########## + # Apple TV + ########## + - regex: '(tvOS)/(\d+).(\d+)' + os_replacement: 'tvOS' + + ########## + # Chrome OS + # if version 0.0.0, probably this stuff: + # http://code.google.com/p/chromium-os/issues/detail?id=11573 + # http://code.google.com/p/chromium-os/issues/detail?id=13790 + ########## + - regex: '(CrOS) [a-z0-9_]+ (\d+)\.(\d+)(?:\.(\d+))?' + os_replacement: 'Chrome OS' + + ########## + # Linux distros + ########## + - regex: '([Dd]ebian)' + os_replacement: 'Debian' + - regex: '(Linux Mint)(?:/(\d+))?' + - regex: '(Mandriva)(?: Linux)?/(?:[\d.-]+m[a-z]{2}(\d+).(\d))?' + + ########## + # Symbian + Symbian OS + # http://en.wikipedia.org/wiki/History_of_Symbian + ########## + - regex: '(Symbian[Oo][Ss])[/ ](\d+)\.(\d+)' + os_replacement: 'Symbian OS' + - regex: '(Symbian/3).+NokiaBrowser/7\.3' + os_replacement: 'Symbian^3 Anna' + - regex: '(Symbian/3).+NokiaBrowser/7\.4' + os_replacement: 'Symbian^3 Belle' + - regex: '(Symbian/3)' + os_replacement: 'Symbian^3' + - regex: '\b(Series 60|SymbOS|S60Version|S60V\d|S60\b)' + os_replacement: 'Symbian OS' + - regex: '(MeeGo)' + - regex: 'Symbian [Oo][Ss]' + os_replacement: 'Symbian OS' + - regex: 'Series40;' + os_replacement: 'Nokia Series 40' + - regex: 'Series30Plus;' + os_replacement: 'Nokia Series 30 Plus' + + ########## + # BlackBerry devices + ########## + - regex: '(BB10);.+Version/(\d+)\.(\d+)\.(\d+)' + os_replacement: 'BlackBerry OS' + - regex: '(Black[Bb]erry)[0-9a-z]+/(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?' + os_replacement: 'BlackBerry OS' + - regex: '(Black[Bb]erry).+Version/(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?' + os_replacement: 'BlackBerry OS' + - regex: '(RIM Tablet OS) (\d+)\.(\d+)\.(\d+)' + os_replacement: 'BlackBerry Tablet OS' + - regex: '(Play[Bb]ook)' + os_replacement: 'BlackBerry Tablet OS' + - regex: '(Black[Bb]erry)' + os_replacement: 'BlackBerry OS' + + ########## + # Firefox OS + ########## + - regex: '\((?:Mobile|Tablet);.+Gecko/18.0 Firefox/\d+\.\d+' + os_replacement: 'Firefox OS' + os_v1_replacement: '1' + os_v2_replacement: '0' + os_v3_replacement: '1' + + - regex: '\((?:Mobile|Tablet);.+Gecko/18.1 Firefox/\d+\.\d+' + os_replacement: 'Firefox OS' + os_v1_replacement: '1' + os_v2_replacement: '1' + + - regex: '\((?:Mobile|Tablet);.+Gecko/26.0 Firefox/\d+\.\d+' + os_replacement: 'Firefox OS' + os_v1_replacement: '1' + os_v2_replacement: '2' + + - regex: '\((?:Mobile|Tablet);.+Gecko/28.0 Firefox/\d+\.\d+' + os_replacement: 'Firefox OS' + os_v1_replacement: '1' + os_v2_replacement: '3' + + - regex: '\((?:Mobile|Tablet);.+Gecko/30.0 Firefox/\d+\.\d+' + os_replacement: 'Firefox OS' + os_v1_replacement: '1' + os_v2_replacement: '4' + + - regex: '\((?:Mobile|Tablet);.+Gecko/32.0 Firefox/\d+\.\d+' + os_replacement: 'Firefox OS' + os_v1_replacement: '2' + os_v2_replacement: '0' + + - regex: '\((?:Mobile|Tablet);.+Gecko/34.0 Firefox/\d+\.\d+' + os_replacement: 'Firefox OS' + os_v1_replacement: '2' + os_v2_replacement: '1' + + # Firefox OS Generic + - regex: '\((?:Mobile|Tablet);.+Firefox/\d+\.\d+' + os_replacement: 'Firefox OS' + + + ########## + # BREW + # yes, Brew is lower-cased for Brew MP + ########## + - regex: '(BREW)[ /](\d+)\.(\d+)\.(\d+)' + - regex: '(BREW);' + - regex: '(Brew MP|BMP)[ /](\d+)\.(\d+)\.(\d+)' + os_replacement: 'Brew MP' + - regex: 'BMP;' + os_replacement: 'Brew MP' + + ########## + # Google TV + ########## + - regex: '(GoogleTV)(?: (\d+)\.(\d+)(?:\.(\d+))?|/[\da-z]+)' + + - regex: '(WebTV)/(\d+).(\d+)' + + ########## + # Misc mobile + ########## + - regex: '(hpw|web)OS/(\d+)\.(\d+)(?:\.(\d+))?' + os_replacement: 'webOS' + - regex: '(VRE);' + + ########## + # Generic patterns + # since the majority of os cases are very specific, these go last + ########## + - regex: '(Fedora|Red Hat|PCLinuxOS|Puppy|Ubuntu|Kindle|Bada|Lubuntu|BackTrack|Slackware|(?:Free|Open|Net|\b)BSD)[/ ](\d+)\.(\d+)(?:\.(\d+)(?:\.(\d+))?)?' + + # Gentoo Linux + Kernel Version + - regex: '(Linux)[ /](\d+)\.(\d+)(?:\.(\d+))?.*gentoo' + os_replacement: 'Gentoo' + + # Opera Mini Bada + - regex: '\((Bada);' + + # just os + - regex: '(Windows|Android|WeTab|Maemo)' + - regex: '(Ubuntu|Kubuntu|Arch Linux|CentOS|Slackware|Gentoo|openSUSE|SUSE|Red Hat|Fedora|PCLinuxOS|Mageia|(?:Free|Open|Net|\b)BSD)' + # Linux + Kernel Version + - regex: '(Linux)(?:[ /](\d+)\.(\d+)(?:\.(\d+))?)?' + - regex: 'SunOS' + os_replacement: 'Solaris' + + # Roku Digital-Video-Players https://www.roku.com/ + - regex: '^(Roku)/DVP-(\d+)\.(\d+)' + +device_parsers: + + ######### + # Mobile Spiders + # Catch the mobile crawler before checking for iPhones / Androids. + ######### + - regex: '(?:(?:iPhone|Windows CE|Android).*(?:(?:Bot|Yeti)-Mobile|YRSpider|bots?/\d|(?:bot|spider)\.html)|AdsBot-Google-Mobile.*iPhone)' + regex_flag: 'i' + device_replacement: 'Spider' + brand_replacement: 'Spider' + model_replacement: 'Smartphone' + - regex: '(?:DoCoMo|\bMOT\b|\bLG\b|Nokia|Samsung|SonyEricsson).*(?:(?:Bot|Yeti)-Mobile|bots?/\d|(?:bot|crawler)\.html|(?:jump|google|Wukong)bot|ichiro/mobile|/spider|YahooSeeker)' + regex_flag: 'i' + device_replacement: 'Spider' + brand_replacement: 'Spider' + model_replacement: 'Feature Phone' + + ######### + # WebBrowser for SmartWatch + # @ref: https://play.google.com/store/apps/details?id=se.vaggan.webbrowser&hl=en + ######### + - regex: '\bSmartWatch *\( *([^;]+) *; *([^;]+) *;' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + + ###################################################################### + # Android parsers + # + # @ref: https://support.google.com/googleplay/answer/1727131?hl=en + ###################################################################### + + # Android Application + - regex: 'Android Application[^\-]+ - (Sony) ?(Ericsson)? (.+) \w+ - ' + device_replacement: '$1 $2' + brand_replacement: '$1$2' + model_replacement: '$3' + - regex: 'Android Application[^\-]+ - (?:HTC|HUAWEI|LGE|LENOVO|MEDION|TCT) (HTC|HUAWEI|LG|LENOVO|MEDION|ALCATEL)[ _\-](.+) \w+ - ' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + - regex: 'Android Application[^\-]+ - ([^ ]+) (.+) \w+ - ' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + + ######### + # 3Q + # @ref: http://www.3q-int.com/ + ######### + - regex: '; *([BLRQ]C\d{4}[A-Z]+) +Build/' + device_replacement: '3Q $1' + brand_replacement: '3Q' + model_replacement: '$1' + - regex: '; *(?:3Q_)([^;/]+) +Build' + device_replacement: '3Q $1' + brand_replacement: '3Q' + model_replacement: '$1' + + ######### + # Acer + # @ref: http://us.acer.com/ac/en/US/content/group/tablets + ######### + - regex: 'Android [34].*; *(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700(?: Lite| 3G)?|A701|B1-A71|A1-\d{3}|B1-\d{3}|V360|V370|W500|W500P|W501|W501P|W510|W511|W700|Slider SL101|DA22[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Acer' + model_replacement: '$1' + - regex: '; *Acer Iconia Tab ([^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Acer' + model_replacement: '$1' + - regex: '; *(Z1[1235]0|E320[^/]*|S500|S510|Liquid[^;/]*|Iconia A\d+) Build' + device_replacement: '$1' + brand_replacement: 'Acer' + model_replacement: '$1' + - regex: '; *(Acer |ACER )([^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'Acer' + model_replacement: '$2' + + ######### + # Advent + # @ref: https://en.wikipedia.org/wiki/Advent_Vega + # @note: VegaBean and VegaComb (names derived from jellybean, honeycomb) are + # custom ROM builds for Vega + ######### + - regex: '; *(Advent )?(Vega(?:Bean|Comb)?).* Build' + device_replacement: '$1$2' + brand_replacement: 'Advent' + model_replacement: '$2' + + ######### + # Ainol + # @ref: http://www.ainol.com/plugin.php?identifier=ainol&module=product + ######### + - regex: '; *(Ainol )?((?:NOVO|[Nn]ovo)[^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'Ainol' + model_replacement: '$2' + + ######### + # Airis + # @ref: http://airis.es/Tienda/Default.aspx?idG=001 + ######### + - regex: '; *AIRIS[ _\-]?([^/;\)]+) *(?:;|\)|Build)' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'Airis' + model_replacement: '$1' + - regex: '; *(OnePAD[^;/]+) Build' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'Airis' + model_replacement: '$1' + + ######### + # Airpad + # @ref: ?? + ######### + - regex: '; *Airpad[ \-]([^;/]+) Build' + device_replacement: 'Airpad $1' + brand_replacement: 'Airpad' + model_replacement: '$1' + + ######### + # Alcatel - TCT + # @ref: http://www.alcatelonetouch.com/global-en/products/smartphones.html + ######### + - regex: '; *(one ?touch) (EVO7|T10|T20) Build' + device_replacement: 'Alcatel One Touch $2' + brand_replacement: 'Alcatel' + model_replacement: 'One Touch $2' + - regex: '; *(?:alcatel[ _])?(?:(?:one[ _]?touch[ _])|ot[ \-])([^;/]+);? Build' + regex_flag: 'i' + device_replacement: 'Alcatel One Touch $1' + brand_replacement: 'Alcatel' + model_replacement: 'One Touch $1' + - regex: '; *(TCL)[ _]([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + # operator specific models + - regex: '; *(Vodafone Smart II|Optimus_Madrid) Build' + device_replacement: 'Alcatel $1' + brand_replacement: 'Alcatel' + model_replacement: '$1' + - regex: '; *BASE_Lutea_3 Build' + device_replacement: 'Alcatel One Touch 998' + brand_replacement: 'Alcatel' + model_replacement: 'One Touch 998' + - regex: '; *BASE_Varia Build' + device_replacement: 'Alcatel One Touch 918D' + brand_replacement: 'Alcatel' + model_replacement: 'One Touch 918D' + + ######### + # Allfine + # @ref: http://www.myallfine.com/Products.asp + ######### + - regex: '; *((?:FINE|Fine)\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Allfine' + model_replacement: '$1' + + ######### + # Allview + # @ref: http://www.allview.ro/produse/droseries/lista-tablete-pc/ + ######### + - regex: '; *(ALLVIEW[ _]?|Allview[ _]?)((?:Speed|SPEED).*) Build/' + device_replacement: '$1$2' + brand_replacement: 'Allview' + model_replacement: '$2' + - regex: '; *(ALLVIEW[ _]?|Allview[ _]?)?(AX1_Shine|AX2_Frenzy) Build' + device_replacement: '$1$2' + brand_replacement: 'Allview' + model_replacement: '$2' + - regex: '; *(ALLVIEW[ _]?|Allview[ _]?)([^;/]*) Build' + device_replacement: '$1$2' + brand_replacement: 'Allview' + model_replacement: '$2' + + ######### + # Allwinner + # @ref: http://www.allwinner.com/ + # @models: A31 (13.3"),A20,A10, + ######### + - regex: '; *(A13-MID) Build' + device_replacement: '$1' + brand_replacement: 'Allwinner' + model_replacement: '$1' + - regex: '; *(Allwinner)[ _\-]?([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'Allwinner' + model_replacement: '$1' + + ######### + # Amaway + # @ref: http://www.amaway.cn/ + ######### + - regex: '; *(A651|A701B?|A702|A703|A705|A706|A707|A711|A712|A713|A717|A722|A785|A801|A802|A803|A901|A902|A1002|A1003|A1006|A1007|A9701|A9703|Q710|Q80) Build' + device_replacement: '$1' + brand_replacement: 'Amaway' + model_replacement: '$1' + + ######### + # Amoi + # @ref: http://www.amoi.com/en/prd/prd_index.jspx + ######### + - regex: '; *(?:AMOI|Amoi)[ _]([^;/]+) Build' + device_replacement: 'Amoi $1' + brand_replacement: 'Amoi' + model_replacement: '$1' + - regex: '^(?:AMOI|Amoi)[ _]([^;/]+) Linux' + device_replacement: 'Amoi $1' + brand_replacement: 'Amoi' + model_replacement: '$1' + + ######### + # Aoc + # @ref: http://latin.aoc.com/media_tablet + ######### + - regex: '; *(MW(?:0[789]|10)[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Aoc' + model_replacement: '$1' + + ######### + # Aoson + # @ref: http://www.luckystar.com.cn/en/mid.aspx?page=1 + # @ref: http://www.luckystar.com.cn/en/mobiletel.aspx?page=1 + # @note: brand owned by luckystar + ######### + - regex: '; *(G7|M1013|M1015G|M11[CG]?|M-?12[B]?|M15|M19[G]?|M30[ACQ]?|M31[GQ]|M32|M33[GQ]|M36|M37|M38|M701T|M710|M712B|M713|M715G|M716G|M71(?:G|GS|T)?|M72[T]?|M73[T]?|M75[GT]?|M77G|M79T|M7L|M7LN|M81|M810|M81T|M82|M92|M92KS|M92S|M717G|M721|M722G|M723|M725G|M739|M785|M791|M92SK|M93D) Build' + device_replacement: 'Aoson $1' + brand_replacement: 'Aoson' + model_replacement: '$1' + - regex: '; *Aoson ([^;/]+) Build' + regex_flag: 'i' + device_replacement: 'Aoson $1' + brand_replacement: 'Aoson' + model_replacement: '$1' + + ######### + # Apanda + # @ref: http://www.apanda.com.cn/ + ######### + - regex: '; *[Aa]panda[ _\-]([^;/]+) Build' + device_replacement: 'Apanda $1' + brand_replacement: 'Apanda' + model_replacement: '$1' + + ######### + # Archos + # @ref: http://www.archos.com/de/products/tablets.html + # @ref: http://www.archos.com/de/products/smartphones/index.html + ######### + - regex: '; *(?:ARCHOS|Archos) ?(GAMEPAD.*?)(?: Build|[;/\(\)\-])' + device_replacement: 'Archos $1' + brand_replacement: 'Archos' + model_replacement: '$1' + - regex: 'ARCHOS; GOGI; ([^;]+);' + device_replacement: 'Archos $1' + brand_replacement: 'Archos' + model_replacement: '$1' + - regex: '(?:ARCHOS|Archos)[ _]?(.*?)(?: Build|[;/\(\)\-]|$)' + device_replacement: 'Archos $1' + brand_replacement: 'Archos' + model_replacement: '$1' + - regex: '; *(AN(?:7|8|9|10|13)[A-Z0-9]{1,4}) Build' + device_replacement: 'Archos $1' + brand_replacement: 'Archos' + model_replacement: '$1' + - regex: '; *(A28|A32|A43|A70(?:BHT|CHT|HB|S|X)|A101(?:B|C|IT)|A7EB|A7EB-WK|101G9|80G9) Build' + device_replacement: 'Archos $1' + brand_replacement: 'Archos' + model_replacement: '$1' + + ######### + # A-rival + # @ref: http://www.a-rival.de/de/ + ######### + - regex: '; *(PAD-FMD[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Arival' + model_replacement: '$1' + - regex: '; *(BioniQ) ?([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'Arival' + model_replacement: '$1 $2' + + ######### + # Arnova + # @ref: http://arnovatech.com/ + ######### + - regex: '; *(AN\d[^;/]+|ARCHM\d+) Build' + device_replacement: 'Arnova $1' + brand_replacement: 'Arnova' + model_replacement: '$1' + - regex: '; *(?:ARNOVA|Arnova) ?([^;/]+) Build' + device_replacement: 'Arnova $1' + brand_replacement: 'Arnova' + model_replacement: '$1' + + ######### + # Assistant + # @ref: http://www.assistant.ua + ######### + - regex: '; *(?:ASSISTANT )?(AP)-?([1789]\d{2}[A-Z]{0,2}|80104) Build' + device_replacement: 'Assistant $1-$2' + brand_replacement: 'Assistant' + model_replacement: '$1-$2' + + ######### + # Asus + # @ref: http://www.asus.com/uk/Tablets_Mobile/ + ######### + - regex: '; *(ME17\d[^;/]*|ME3\d{2}[^;/]+|K00[A-Z]|Nexus 10|Nexus 7(?: 2013)?|PadFone[^;/]*|Transformer[^;/]*|TF\d{3}[^;/]*|eeepc) Build' + device_replacement: 'Asus $1' + brand_replacement: 'Asus' + model_replacement: '$1' + - regex: '; *ASUS[ _]*([^;/]+) Build' + device_replacement: 'Asus $1' + brand_replacement: 'Asus' + model_replacement: '$1' + + ######### + # Garmin-Asus + ######### + - regex: '; *Garmin-Asus ([^;/]+) Build' + device_replacement: 'Garmin-Asus $1' + brand_replacement: 'Garmin-Asus' + model_replacement: '$1' + - regex: '; *(Garminfone) Build' + device_replacement: 'Garmin $1' + brand_replacement: 'Garmin-Asus' + model_replacement: '$1' + + ######### + # Attab + # @ref: http://www.theattab.com/ + ######### + - regex: '; (@TAB-[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Attab' + model_replacement: '$1' + + ######### + # Audiosonic + # @ref: ?? + # @note: Take care with Docomo T-01 Toshiba + ######### + - regex: '; *(T-(?:07|[^0]\d)[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Audiosonic' + model_replacement: '$1' + + ######### + # Axioo + # @ref: http://www.axiooworld.com/ww/index.php + ######### + - regex: '; *(?:Axioo[ _\-]([^;/]+)|(picopad)[ _\-]([^;/]+)) Build' + regex_flag: 'i' + device_replacement: 'Axioo $1$2 $3' + brand_replacement: 'Axioo' + model_replacement: '$1$2 $3' + + ######### + # Azend + # @ref: http://azendcorp.com/index.php/products/portable-electronics + ######### + - regex: '; *(V(?:100|700|800)[^;/]*) Build' + device_replacement: '$1' + brand_replacement: 'Azend' + model_replacement: '$1' + + ######### + # Bak + # @ref: http://www.bakinternational.com/produtos.php?cat=80 + ######### + - regex: '; *(IBAK\-[^;/]*) Build' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'Bak' + model_replacement: '$1' + + ######### + # Bedove + # @ref: http://www.bedove.com/product.html + # @models: HY6501|HY5001|X12|X21|I5 + ######### + - regex: '; *(HY5001|HY6501|X12|X21|I5) Build' + device_replacement: 'Bedove $1' + brand_replacement: 'Bedove' + model_replacement: '$1' + + ######### + # Benss + # @ref: http://www.benss.net/ + ######### + - regex: '; *(JC-[^;/]*) Build' + device_replacement: 'Benss $1' + brand_replacement: 'Benss' + model_replacement: '$1' + + ######### + # Blackberry + # @ref: http://uk.blackberry.com/ + # @note: Android Apps seams to be used here + ######### + - regex: '; *(BB) ([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'Blackberry' + model_replacement: '$2' + + ######### + # Blackbird + # @ref: http://iblackbird.co.kr + ######### + - regex: '; *(BlackBird)[ _](I8.*) Build' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + - regex: '; *(BlackBird)[ _](.*) Build' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + + ######### + # Blaupunkt + # @ref: http://www.blaupunkt.com + ######### + # Endeavour + - regex: '; *([0-9]+BP[EM][^;/]*|Endeavour[^;/]+) Build' + device_replacement: 'Blaupunkt $1' + brand_replacement: 'Blaupunkt' + model_replacement: '$1' + + ######### + # Blu + # @ref: http://bluproducts.com + ######### + - regex: '; *((?:BLU|Blu)[ _\-])([^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'Blu' + model_replacement: '$2' + # BMOBILE = operator branded device + - regex: '; *(?:BMOBILE )?(Blu|BLU|DASH [^;/]+|VIVO 4\.3|TANK 4\.5) Build' + device_replacement: '$1' + brand_replacement: 'Blu' + model_replacement: '$1' + + ######### + # Blusens + # @ref: http://www.blusens.com/es/?sg=1&sv=al&roc=1 + ######### + # tablet + - regex: '; *(TOUCH\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Blusens' + model_replacement: '$1' + + ######### + # Bmobile + # @ref: http://bmobile.eu.com/?categoria=smartphones-2 + # @note: Might collide with Maxx as AX is used also there. + ######### + # smartphone + - regex: '; *(AX5\d+) Build' + device_replacement: '$1' + brand_replacement: 'Bmobile' + model_replacement: '$1' + + ######### + # bq + # @ref: http://bqreaders.com + ######### + - regex: '; *([Bb]q) ([^;/]+);? Build' + device_replacement: '$1 $2' + brand_replacement: 'bq' + model_replacement: '$2' + - regex: '; *(Maxwell [^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'bq' + model_replacement: '$1' + + ######### + # Braun Phototechnik + # @ref: http://www.braun-phototechnik.de/en/products/list/~pcat.250/Tablet-PC.html + ######### + - regex: '; *((?:B-Tab|B-TAB) ?\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Braun' + model_replacement: '$1' + + ######### + # Broncho + # @ref: http://www.broncho.cn/ + ######### + - regex: '; *(Broncho) ([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + + ######### + # Captiva + # @ref: http://www.captiva-power.de + ######### + - regex: '; *CAPTIVA ([^;/]+) Build' + device_replacement: 'Captiva $1' + brand_replacement: 'Captiva' + model_replacement: '$1' + + ######### + # Casio + # @ref: http://www.casiogzone.com/ + ######### + - regex: '; *(C771|CAL21|IS11CA) Build' + device_replacement: '$1' + brand_replacement: 'Casio' + model_replacement: '$1' + + ######### + # Cat + # @ref: http://www.cat-sound.com + ######### + - regex: '; *(?:Cat|CAT) ([^;/]+) Build' + device_replacement: 'Cat $1' + brand_replacement: 'Cat' + model_replacement: '$1' + - regex: '; *(?:Cat)(Nova.*) Build' + device_replacement: 'Cat $1' + brand_replacement: 'Cat' + model_replacement: '$1' + - regex: '; *(INM8002KP|ADM8000KP_[AB]) Build' + device_replacement: '$1' + brand_replacement: 'Cat' + model_replacement: 'Tablet PHOENIX 8.1J0' + + ######### + # Celkon + # @ref: http://www.celkonmobiles.com/?_a=products + # @models: A10, A19Q, A101, A105, A107, A107\+, A112, A118, A119, A119Q, A15, A19, A20, A200, A220, A225, A22 Race, A27, A58, A59, A60, A62, A63, A64, A66, A67, A69, A75, A77, A79, A8\+, A83, A85, A86, A87, A89 Ultima, A9\+, A90, A900, A95, A97i, A98, AR 40, AR 45, AR 50, ML5 + ######### + - regex: '; *(?:[Cc]elkon[ _\*]|CELKON[ _\*])([^;/\)]+) ?(?:Build|;|\))' + device_replacement: '$1' + brand_replacement: 'Celkon' + model_replacement: '$1' + - regex: 'Build/(?:[Cc]elkon)+_?([^;/_\)]+)' + device_replacement: '$1' + brand_replacement: 'Celkon' + model_replacement: '$1' + - regex: '; *(CT)-?(\d+) Build' + device_replacement: '$1$2' + brand_replacement: 'Celkon' + model_replacement: '$1$2' + # smartphones + - regex: '; *(A19|A19Q|A105|A107[^;/\)]*) ?(?:Build|;|\))' + device_replacement: '$1' + brand_replacement: 'Celkon' + model_replacement: '$1' + + ######### + # ChangJia + # @ref: http://www.cjshowroom.com/eproducts.aspx?classcode=004001001 + # @brief: China manufacturer makes tablets for different small brands + # (eg. http://www.zeepad.net/index.html) + ######### + - regex: '; *(TPC[0-9]{4,5}) Build' + device_replacement: '$1' + brand_replacement: 'ChangJia' + model_replacement: '$1' + + ######### + # Cloudfone + # @ref: http://www.cloudfonemobile.com/ + ######### + - regex: '; *(Cloudfone)[ _](Excite)([^ ][^;/]+) Build' + device_replacement: '$1 $2 $3' + brand_replacement: 'Cloudfone' + model_replacement: '$1 $2 $3' + - regex: '; *(Excite|ICE)[ _](\d+[^;/]+) Build' + device_replacement: 'Cloudfone $1 $2' + brand_replacement: 'Cloudfone' + model_replacement: 'Cloudfone $1 $2' + - regex: '; *(Cloudfone|CloudPad)[ _]([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'Cloudfone' + model_replacement: '$1 $2' + + ######### + # Cmx + # @ref: http://cmx.at/de/ + ######### + - regex: '; *((?:Aquila|Clanga|Rapax)[^;/]+) Build' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'Cmx' + model_replacement: '$1' + + ######### + # CobyKyros + # @ref: http://cobykyros.com + # @note: Be careful with MID\d{3} from MpMan or Manta + ######### + - regex: '; *(?:CFW-|Kyros )?(MID[0-9]{4}(?:[ABC]|SR|TV)?)(\(3G\)-4G| GB 8K| 3G| 8K| GB)? *(?:Build|[;\)])' + device_replacement: 'CobyKyros $1$2' + brand_replacement: 'CobyKyros' + model_replacement: '$1$2' + + ######### + # Coolpad + # @ref: ?? + ######### + - regex: '; *([^;/]*)Coolpad[ _]([^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'Coolpad' + model_replacement: '$1$2' + + ######### + # Cube + # @ref: http://www.cube-tablet.com/buy-products.html + ######### + - regex: '; *(CUBE[ _])?([KU][0-9]+ ?GT.*|A5300) Build' + regex_flag: 'i' + device_replacement: '$1$2' + brand_replacement: 'Cube' + model_replacement: '$2' + + ######### + # Cubot + # @ref: http://www.cubotmall.com/ + ######### + - regex: '; *CUBOT ([^;/]+) Build' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'Cubot' + model_replacement: '$1' + - regex: '; *(BOBBY) Build' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'Cubot' + model_replacement: '$1' + + ######### + # Danew + # @ref: http://www.danew.com/produits-tablette.php + ######### + - regex: '; *(Dslide [^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Danew' + model_replacement: '$1' + + ######### + # Dell + # @ref: http://www.dell.com + # @ref: http://www.softbank.jp/mobile/support/product/101dl/ + # @ref: http://www.softbank.jp/mobile/support/product/001dl/ + # @ref: http://developer.emnet.ne.jp/android.html + # @ref: http://www.dell.com/in/p/mobile-xcd28/pd + # @ref: http://www.dell.com/in/p/mobile-xcd35/pd + ######### + - regex: '; *(XCD)[ _]?(28|35) Build' + device_replacement: 'Dell $1$2' + brand_replacement: 'Dell' + model_replacement: '$1$2' + - regex: '; *(001DL) Build' + device_replacement: 'Dell $1' + brand_replacement: 'Dell' + model_replacement: 'Streak' + - regex: '; *(?:Dell|DELL) (Streak) Build' + device_replacement: 'Dell $1' + brand_replacement: 'Dell' + model_replacement: 'Streak' + - regex: '; *(101DL|GS01|Streak Pro[^;/]*) Build' + device_replacement: 'Dell $1' + brand_replacement: 'Dell' + model_replacement: 'Streak Pro' + - regex: '; *([Ss]treak ?7) Build' + device_replacement: 'Dell $1' + brand_replacement: 'Dell' + model_replacement: 'Streak 7' + - regex: '; *(Mini-3iX) Build' + device_replacement: 'Dell $1' + brand_replacement: 'Dell' + model_replacement: '$1' + - regex: '; *(?:Dell|DELL)[ _](Aero|Venue|Thunder|Mini.*|Streak[ _]Pro) Build' + device_replacement: 'Dell $1' + brand_replacement: 'Dell' + model_replacement: '$1' + - regex: '; *Dell[ _]([^;/]+) Build' + device_replacement: 'Dell $1' + brand_replacement: 'Dell' + model_replacement: '$1' + - regex: '; *Dell ([^;/]+) Build' + device_replacement: 'Dell $1' + brand_replacement: 'Dell' + model_replacement: '$1' + + ######### + # Denver + # @ref: http://www.denver-electronics.com/tablets1/ + ######### + - regex: '; *(TA[CD]-\d+[^;/]*) Build' + device_replacement: '$1' + brand_replacement: 'Denver' + model_replacement: '$1' + + ######### + # Dex + # @ref: http://dex.ua/ + ######### + - regex: '; *(iP[789]\d{2}(?:-3G)?|IP10\d{2}(?:-8GB)?) Build' + device_replacement: '$1' + brand_replacement: 'Dex' + model_replacement: '$1' + + ######### + # DNS AirTab + # @ref: http://www.dns-shop.ru/ + ######### + - regex: '; *(AirTab)[ _\-]([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'DNS' + model_replacement: '$1 $2' + + ######### + # Docomo (Operator Branded Device) + # @ref: http://www.ipentec.com/document/document.aspx?page=android-useragent + ######### + - regex: '; *(F\-\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Fujitsu' + model_replacement: '$1' + - regex: '; *(HT-03A) Build' + device_replacement: '$1' + brand_replacement: 'HTC' + model_replacement: 'Magic' + - regex: '; *(HT\-\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'HTC' + model_replacement: '$1' + - regex: '; *(L\-\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'LG' + model_replacement: '$1' + - regex: '; *(N\-\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Nec' + model_replacement: '$1' + - regex: '; *(P\-\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Panasonic' + model_replacement: '$1' + - regex: '; *(SC\-\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Samsung' + model_replacement: '$1' + - regex: '; *(SH\-\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Sharp' + model_replacement: '$1' + - regex: '; *(SO\-\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'SonyEricsson' + model_replacement: '$1' + - regex: '; *(T\-0[12][^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Toshiba' + model_replacement: '$1' + + ######### + # DOOV + # @ref: http://www.doov.com.cn/ + ######### + - regex: '; *(DOOV)[ _]([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'DOOV' + model_replacement: '$2' + + ######### + # Enot + # @ref: http://www.enot.ua/ + ######### + - regex: '; *(Enot|ENOT)[ -]?([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'Enot' + model_replacement: '$2' + + ######### + # Evercoss + # @ref: http://evercoss.com/android/ + ######### + - regex: '; *[^;/]+ Build/(?:CROSS|Cross)+[ _\-]([^\)]+)' + device_replacement: 'CROSS $1' + brand_replacement: 'Evercoss' + model_replacement: 'Cross $1' + - regex: '; *(CROSS|Cross)[ _\-]([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'Evercoss' + model_replacement: 'Cross $2' + + ######### + # Explay + # @ref: http://explay.ru/ + ######### + - regex: '; *Explay[_ ](.+?)(?:[\)]| Build)' + device_replacement: '$1' + brand_replacement: 'Explay' + model_replacement: '$1' + + ######### + # Fly + # @ref: http://www.fly-phone.com/ + ######### + - regex: '; *(IQ.*) Build' + device_replacement: '$1' + brand_replacement: 'Fly' + model_replacement: '$1' + - regex: '; *(Fly|FLY)[ _](IQ[^;]+|F[34]\d+[^;]*);? Build' + device_replacement: '$1 $2' + brand_replacement: 'Fly' + model_replacement: '$2' + + ######### + # Fujitsu + # @ref: http://www.fujitsu.com/global/ + ######### + - regex: '; *(M532|Q572|FJL21) Build/' + device_replacement: '$1' + brand_replacement: 'Fujitsu' + model_replacement: '$1' + + ######### + # Galapad + # @ref: http://www.galapad.net/product.html + ######### + - regex: '; *(G1) Build' + device_replacement: '$1' + brand_replacement: 'Galapad' + model_replacement: '$1' + + ######### + # Geeksphone + # @ref: http://www.geeksphone.com/ + ######### + - regex: '; *(Geeksphone) ([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + + ######### + # Gfive + # @ref: http://www.gfivemobile.com/en + ######### + #- regex: '; *(G\'?FIVE) ([^;/]+) Build' # there is a problem with python yaml parser here + - regex: '; *(G[^F]?FIVE) ([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'Gfive' + model_replacement: '$2' + + ######### + # Gionee + # @ref: http://www.gionee.com/ + ######### + - regex: '; *(Gionee)[ _\-]([^;/]+)(?:/[^;/]+)? Build' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'Gionee' + model_replacement: '$2' + - regex: '; *(GN\d+[A-Z]?|INFINITY_PASSION|Ctrl_V1) Build' + device_replacement: 'Gionee $1' + brand_replacement: 'Gionee' + model_replacement: '$1' + - regex: '; *(E3) Build/JOP40D' + device_replacement: 'Gionee $1' + brand_replacement: 'Gionee' + model_replacement: '$1' + + ######### + # GoClever + # @ref: http://www.goclever.com + ######### + - regex: '; *((?:FONE|QUANTUM|INSIGNIA) \d+[^;/]*|PLAYTAB) Build' + device_replacement: 'GoClever $1' + brand_replacement: 'GoClever' + model_replacement: '$1' + - regex: '; *GOCLEVER ([^;/]+) Build' + device_replacement: 'GoClever $1' + brand_replacement: 'GoClever' + model_replacement: '$1' + + ######### + # Google + # @ref: http://www.google.de/glass/start/ + ######### + - regex: '; *(Glass \d+) Build' + device_replacement: '$1' + brand_replacement: 'Google' + model_replacement: '$1' + + ######### + # Gigabyte + # @ref: http://gsmart.gigabytecm.com/en/ + ######### + - regex: '; *(GSmart)[ -]([^/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'Gigabyte' + model_replacement: '$1 $2' + + ######### + # Freescale development boards + # @ref: http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=IMX53QSB + ######### + - regex: '; *(imx5[13]_[^/]+) Build' + device_replacement: 'Freescale $1' + brand_replacement: 'Freescale' + model_replacement: '$1' + + ######### + # Haier + # @ref: http://www.haier.com/ + # @ref: http://www.haier.com/de/produkte/tablet/ + ######### + - regex: '; *Haier[ _\-]([^/]+) Build' + device_replacement: 'Haier $1' + brand_replacement: 'Haier' + model_replacement: '$1' + - regex: '; *(PAD1016) Build' + device_replacement: 'Haipad $1' + brand_replacement: 'Haipad' + model_replacement: '$1' + + ######### + # Haipad + # @ref: http://www.haipad.net/ + # @models: V7P|M7SM7S|M9XM9X|M7XM7X|M9|M8|M7-M|M1002|M7|M701 + ######### + - regex: '; *(M701|M7|M8|M9) Build' + device_replacement: 'Haipad $1' + brand_replacement: 'Haipad' + model_replacement: '$1' + + ######### + # Hannspree + # @ref: http://www.hannspree.eu/ + # @models: SN10T1|SN10T2|SN70T31B|SN70T32W + ######### + - regex: '; *(SN\d+T[^;\)/]*)(?: Build|[;\)])' + device_replacement: 'Hannspree $1' + brand_replacement: 'Hannspree' + model_replacement: '$1' + + ######### + # HCLme + # @ref: http://www.hclmetablet.com/india/ + ######### + - regex: 'Build/HCL ME Tablet ([^;\)]+)[\);]' + device_replacement: 'HCLme $1' + brand_replacement: 'HCLme' + model_replacement: '$1' + - regex: '; *([^;\/]+) Build/HCL' + device_replacement: 'HCLme $1' + brand_replacement: 'HCLme' + model_replacement: '$1' + + ######### + # Hena + # @ref: http://www.henadigital.com/en/product/index.asp?id=6 + ######### + - regex: '; *(MID-?\d{4}C[EM]) Build' + device_replacement: 'Hena $1' + brand_replacement: 'Hena' + model_replacement: '$1' + + ######### + # Hisense + # @ref: http://www.hisense.com/ + ######### + - regex: '; *(EG\d{2,}|HS-[^;/]+|MIRA[^;/]+) Build' + device_replacement: 'Hisense $1' + brand_replacement: 'Hisense' + model_replacement: '$1' + - regex: '; *(andromax[^;/]+) Build' + regex_flag: 'i' + device_replacement: 'Hisense $1' + brand_replacement: 'Hisense' + model_replacement: '$1' + + ######### + # hitech + # @ref: http://www.hitech-mobiles.com/ + ######### + - regex: '; *(?:AMAZE[ _](S\d+)|(S\d+)[ _]AMAZE) Build' + device_replacement: 'AMAZE $1$2' + brand_replacement: 'hitech' + model_replacement: 'AMAZE $1$2' + + ######### + # HP + # @ref: http://www.hp.com/ + ######### + - regex: '; *(PlayBook) Build' + device_replacement: 'HP $1' + brand_replacement: 'HP' + model_replacement: '$1' + - regex: '; *HP ([^/]+) Build' + device_replacement: 'HP $1' + brand_replacement: 'HP' + model_replacement: '$1' + - regex: '; *([^/]+_tenderloin) Build' + device_replacement: 'HP TouchPad' + brand_replacement: 'HP' + model_replacement: 'TouchPad' + + ######### + # Huawei + # @ref: http://www.huaweidevice.com + # @note: Needs to be before HTC due to Desire HD Build on U8815 + ######### + - regex: '; *(HUAWEI |Huawei-)?([UY][^;/]+) Build/(?:Huawei|HUAWEI)([UY][^\);]+)\)' + device_replacement: '$1$2' + brand_replacement: 'Huawei' + model_replacement: '$2' + - regex: '; *([^;/]+) Build[/ ]Huawei(MT1-U06|[A-Z]+\d+[^\);]+)[^\);]*\)' + device_replacement: '$1' + brand_replacement: 'Huawei' + model_replacement: '$2' + - regex: '; *(S7|M860) Build' + device_replacement: '$1' + brand_replacement: 'Huawei' + model_replacement: '$1' + - regex: '; *((?:HUAWEI|Huawei)[ \-]?)(MediaPad) Build' + device_replacement: '$1$2' + brand_replacement: 'Huawei' + model_replacement: '$2' + - regex: '; *((?:HUAWEI[ _]?|Huawei[ _])?Ascend[ _])([^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'Huawei' + model_replacement: '$2' + - regex: '; *((?:HUAWEI|Huawei)[ _\-]?)((?:G700-|MT-)[^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'Huawei' + model_replacement: '$2' + - regex: '; *((?:HUAWEI|Huawei)[ _\-]?)([^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'Huawei' + model_replacement: '$2' + - regex: '; *(MediaPad[^;]+|SpringBoard) Build/Huawei' + device_replacement: '$1' + brand_replacement: 'Huawei' + model_replacement: '$1' + - regex: '; *([^;]+) Build/Huawei' + device_replacement: '$1' + brand_replacement: 'Huawei' + model_replacement: '$1' + - regex: '; *([Uu])([89]\d{3}) Build' + device_replacement: '$1$2' + brand_replacement: 'Huawei' + model_replacement: 'U$2' + - regex: '; *(?:Ideos |IDEOS )(S7) Build' + device_replacement: 'Huawei Ideos$1' + brand_replacement: 'Huawei' + model_replacement: 'Ideos$1' + - regex: '; *(?:Ideos |IDEOS )([^;/]+\s*|\s*)Build' + device_replacement: 'Huawei Ideos$1' + brand_replacement: 'Huawei' + model_replacement: 'Ideos$1' + - regex: '; *(Orange Daytona|Pulse|Pulse Mini|Vodafone 858|C8500|C8600|C8650|C8660|Nexus 6P) Build' + device_replacement: 'Huawei $1' + brand_replacement: 'Huawei' + model_replacement: '$1' + + ######### + # HTC + # @ref: http://www.htc.com/www/products/ + # @ref: http://en.wikipedia.org/wiki/List_of_HTC_phones + ######### + + - regex: '; *HTC[ _]([^;]+); Windows Phone' + device_replacement: 'HTC $1' + brand_replacement: 'HTC' + model_replacement: '$1' + + # Android HTC with Version Number matcher + # ; HTC_0P3Z11/1.12.161.3 Build + # ;HTC_A3335 V2.38.841.1 Build + - regex: '; *(?:HTC[ _/])+([^ _/]+)(?:[/\\]1\.0 | V|/| +)\d+\.\d[\d\.]*(?: *Build|\))' + device_replacement: 'HTC $1' + brand_replacement: 'HTC' + model_replacement: '$1' + - regex: '; *(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+))?(?:[/\\]1\.0 | V|/| +)\d+\.\d[\d\.]*(?: *Build|\))' + device_replacement: 'HTC $1 $2' + brand_replacement: 'HTC' + model_replacement: '$1 $2' + - regex: '; *(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+))?)?(?:[/\\]1\.0 | V|/| +)\d+\.\d[\d\.]*(?: *Build|\))' + device_replacement: 'HTC $1 $2 $3' + brand_replacement: 'HTC' + model_replacement: '$1 $2 $3' + - regex: '; *(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+))?)?)?(?:[/\\]1\.0 | V|/| +)\d+\.\d[\d\.]*(?: *Build|\))' + device_replacement: 'HTC $1 $2 $3 $4' + brand_replacement: 'HTC' + model_replacement: '$1 $2 $3 $4' + + # Android HTC without Version Number matcher + - regex: '; *(?:(?:HTC|htc)(?:_blocked)*[ _/])+([^ _/;]+)(?: *Build|[;\)]| - )' + device_replacement: 'HTC $1' + brand_replacement: 'HTC' + model_replacement: '$1' + - regex: '; *(?:(?:HTC|htc)(?:_blocked)*[ _/])+([^ _/]+)(?:[ _/]([^ _/;\)]+))?(?: *Build|[;\)]| - )' + device_replacement: 'HTC $1 $2' + brand_replacement: 'HTC' + model_replacement: '$1 $2' + - regex: '; *(?:(?:HTC|htc)(?:_blocked)*[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/;\)]+))?)?(?: *Build|[;\)]| - )' + device_replacement: 'HTC $1 $2 $3' + brand_replacement: 'HTC' + model_replacement: '$1 $2 $3' + - regex: '; *(?:(?:HTC|htc)(?:_blocked)*[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ /;]+))?)?)?(?: *Build|[;\)]| - )' + device_replacement: 'HTC $1 $2 $3 $4' + brand_replacement: 'HTC' + model_replacement: '$1 $2 $3 $4' + + # HTC Streaming Player + - regex: 'HTC Streaming Player [^\/]*/[^\/]*/ htc_([^/]+) /' + device_replacement: 'HTC $1' + brand_replacement: 'HTC' + model_replacement: '$1' + # general matcher for anything else + - regex: '(?:[;,] *|^)(?:htccn_chs-)?HTC[ _-]?([^;]+?)(?: *Build|clay|Android|-?Mozilla| Opera| Profile| UNTRUSTED|[;/\(\)]|$)' + regex_flag: 'i' + device_replacement: 'HTC $1' + brand_replacement: 'HTC' + model_replacement: '$1' + # Android matchers without HTC + - regex: '; *(A6277|ADR6200|ADR6300|ADR6350|ADR6400[A-Z]*|ADR6425[A-Z]*|APX515CKT|ARIA|Desire[^_ ]*|Dream|EndeavorU|Eris|Evo|Flyer|HD2|Hero|HERO200|Hero CDMA|HTL21|Incredible|Inspire[A-Z0-9]*|Legend|Liberty|Nexus ?(?:One|HD2)|One|One S C2|One[ _]?(?:S|V|X\+?)\w*|PC36100|PG06100|PG86100|S31HT|Sensation|Wildfire)(?: Build|[/;\(\)])' + regex_flag: 'i' + device_replacement: 'HTC $1' + brand_replacement: 'HTC' + model_replacement: '$1' + - regex: '; *(ADR6200|ADR6400L|ADR6425LVW|Amaze|DesireS?|EndeavorU|Eris|EVO|Evo\d[A-Z]+|HD2|IncredibleS?|Inspire[A-Z0-9]*|Inspire[A-Z0-9]*|Sensation[A-Z0-9]*|Wildfire)[ _-](.+?)(?:[/;\)]|Build|MIUI|1\.0)' + regex_flag: 'i' + device_replacement: 'HTC $1 $2' + brand_replacement: 'HTC' + model_replacement: '$1 $2' + + ######### + # Hyundai + # @ref: http://www.hyundaitechnologies.com + ######### + - regex: '; *HYUNDAI (T\d[^/]*) Build' + device_replacement: 'Hyundai $1' + brand_replacement: 'Hyundai' + model_replacement: '$1' + - regex: '; *HYUNDAI ([^;/]+) Build' + device_replacement: 'Hyundai $1' + brand_replacement: 'Hyundai' + model_replacement: '$1' + # X900? http://www.amazon.com/Hyundai-X900-Retina-Android-Bluetooth/dp/B00AO07H3O + - regex: '; *(X700|Hold X|MB-6900) Build' + device_replacement: 'Hyundai $1' + brand_replacement: 'Hyundai' + model_replacement: '$1' + + ######### + # iBall + # @ref: http://www.iball.co.in/Category/Mobiles/22 + ######### + - regex: '; *(?:iBall[ _\-])?(Andi)[ _]?(\d[^;/]*) Build' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'iBall' + model_replacement: '$1 $2' + - regex: '; *(IBall)(?:[ _]([^;/]+)|) Build' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'iBall' + model_replacement: '$2' + + ######### + # IconBIT + # @ref: http://www.iconbit.com/catalog/tablets/ + ######### + - regex: '; *(NT-\d+[^ ;/]*|Net[Tt]AB [^;/]+|Mercury [A-Z]+|iconBIT)(?: S/N:[^;/]+)? Build' + device_replacement: '$1' + brand_replacement: 'IconBIT' + model_replacement: '$1' + + ######### + # IMO + # @ref: http://www.ponselimo.com/ + ######### + - regex: '; *(IMO)[ _]([^;/]+) Build' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'IMO' + model_replacement: '$2' + + ######### + # i-mobile + # @ref: http://www.i-mobilephone.com/ + ######### + - regex: '; *i-?mobile[ _]([^/]+) Build/' + regex_flag: 'i' + device_replacement: 'i-mobile $1' + brand_replacement: 'imobile' + model_replacement: '$1' + - regex: '; *(i-(?:style|note)[^/]*) Build/' + regex_flag: 'i' + device_replacement: 'i-mobile $1' + brand_replacement: 'imobile' + model_replacement: '$1' + + ######### + # Impression + # @ref: http://impression.ua/planshetnye-kompyutery + ######### + - regex: '; *(ImPAD) ?(\d+(?:.)*) Build' + device_replacement: '$1 $2' + brand_replacement: 'Impression' + model_replacement: '$1 $2' + + ######### + # Infinix + # @ref: http://www.infinixmobility.com/index.html + ######### + - regex: '; *(Infinix)[ _]([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'Infinix' + model_replacement: '$2' + + ######### + # Informer + # @ref: ?? + ######### + - regex: '; *(Informer)[ \-]([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'Informer' + model_replacement: '$2' + + ######### + # Intenso + # @ref: http://www.intenso.de + # @models: 7":TAB 714,TAB 724;8":TAB 814,TAB 824;10":TAB 1004 + ######### + - regex: '; *(TAB) ?([78][12]4) Build' + device_replacement: 'Intenso $1' + brand_replacement: 'Intenso' + model_replacement: '$1 $2' + + ######### + # Intex + # @ref: http://intexmobile.in/index.aspx + # @note: Zync also offers a "Cloud Z5" device + ######### + # smartphones + - regex: '; *(?:Intex[ _])?(AQUA|Aqua)([ _\.\-])([^;/]+) *(?:Build|;)' + device_replacement: '$1$2$3' + brand_replacement: 'Intex' + model_replacement: '$1 $3' + # matches "INTEX CLOUD X1" + - regex: '; *(?:INTEX|Intex)(?:[_ ]([^\ _;/]+))(?:[_ ]([^\ _;/]+))? *(?:Build|;)' + device_replacement: '$1 $2' + brand_replacement: 'Intex' + model_replacement: '$1 $2' + # tablets + - regex: '; *([iI]Buddy)[ _]?(Connect)(?:_|\?_| )?([^;/]*) *(?:Build|;)' + device_replacement: '$1 $2 $3' + brand_replacement: 'Intex' + model_replacement: 'iBuddy $2 $3' + - regex: '; *(I-Buddy)[ _]([^;/]+) *(?:Build|;)' + device_replacement: '$1 $2' + brand_replacement: 'Intex' + model_replacement: 'iBuddy $2' + + ######### + # iOCEAN + # @ref: http://www.iocean.cc/ + ######### + - regex: '; *(iOCEAN) ([^/]+) Build' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'iOCEAN' + model_replacement: '$2' + + ######### + # i.onik + # @ref: http://www.i-onik.de/ + ######### + - regex: '; *(TP\d+(?:\.\d+)?\-\d[^;/]+) Build' + device_replacement: 'ionik $1' + brand_replacement: 'ionik' + model_replacement: '$1' + + ######### + # IRU.ru + # @ref: http://www.iru.ru/catalog/soho/planetable/ + ######### + - regex: '; *(M702pro) Build' + device_replacement: '$1' + brand_replacement: 'Iru' + model_replacement: '$1' + + ######### + # Ivio + # @ref: http://www.ivio.com/mobile.php + # @models: DG80,DG20,DE38,DE88,MD70 + ######### + - regex: '; *(DE88Plus|MD70) Build' + device_replacement: '$1' + brand_replacement: 'Ivio' + model_replacement: '$1' + - regex: '; *IVIO[_\-]([^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Ivio' + model_replacement: '$1' + + ######### + # Jaytech + # @ref: http://www.jay-tech.de/jaytech/servlet/frontend/ + ######### + - regex: '; *(TPC-\d+|JAY-TECH) Build' + device_replacement: '$1' + brand_replacement: 'Jaytech' + model_replacement: '$1' + + ######### + # Jiayu + # @ref: http://www.ejiayu.com/en/Product.html + ######### + - regex: '; *(JY-[^;/]+|G[234]S?) Build' + device_replacement: '$1' + brand_replacement: 'Jiayu' + model_replacement: '$1' + + ######### + # JXD + # @ref: http://www.jxd.hk/ + ######### + - regex: '; *(JXD)[ _\-]([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'JXD' + model_replacement: '$2' + + ######### + # Karbonn + # @ref: http://www.karbonnmobiles.com/products_tablet.php + ######### + - regex: '; *Karbonn[ _]?([^;/]+) *(?:Build|;)' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'Karbonn' + model_replacement: '$1' + - regex: '; *([^;]+) Build/Karbonn' + device_replacement: '$1' + brand_replacement: 'Karbonn' + model_replacement: '$1' + - regex: '; *(A11|A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2|Titanium S\d) +Build' + device_replacement: '$1' + brand_replacement: 'Karbonn' + model_replacement: '$1' + + ######### + # KDDI (Operator Branded Device) + # @ref: http://www.ipentec.com/document/document.aspx?page=android-useragent + ######### + - regex: '; *(IS01|IS03|IS05|IS\d{2}SH) Build' + device_replacement: '$1' + brand_replacement: 'Sharp' + model_replacement: '$1' + - regex: '; *(IS04) Build' + device_replacement: '$1' + brand_replacement: 'Regza' + model_replacement: '$1' + - regex: '; *(IS06|IS\d{2}PT) Build' + device_replacement: '$1' + brand_replacement: 'Pantech' + model_replacement: '$1' + - regex: '; *(IS11S) Build' + device_replacement: '$1' + brand_replacement: 'SonyEricsson' + model_replacement: 'Xperia Acro' + - regex: '; *(IS11CA) Build' + device_replacement: '$1' + brand_replacement: 'Casio' + model_replacement: 'GzOne $1' + - regex: '; *(IS11LG) Build' + device_replacement: '$1' + brand_replacement: 'LG' + model_replacement: 'Optimus X' + - regex: '; *(IS11N) Build' + device_replacement: '$1' + brand_replacement: 'Medias' + model_replacement: '$1' + - regex: '; *(IS11PT) Build' + device_replacement: '$1' + brand_replacement: 'Pantech' + model_replacement: 'MIRACH' + - regex: '; *(IS12F) Build' + device_replacement: '$1' + brand_replacement: 'Fujitsu' + model_replacement: 'Arrows ES' + # @ref: https://ja.wikipedia.org/wiki/IS12M + - regex: '; *(IS12M) Build' + device_replacement: '$1' + brand_replacement: 'Motorola' + model_replacement: 'XT909' + - regex: '; *(IS12S) Build' + device_replacement: '$1' + brand_replacement: 'SonyEricsson' + model_replacement: 'Xperia Acro HD' + - regex: '; *(ISW11F) Build' + device_replacement: '$1' + brand_replacement: 'Fujitsu' + model_replacement: 'Arrowz Z' + - regex: '; *(ISW11HT) Build' + device_replacement: '$1' + brand_replacement: 'HTC' + model_replacement: 'EVO' + - regex: '; *(ISW11K) Build' + device_replacement: '$1' + brand_replacement: 'Kyocera' + model_replacement: 'DIGNO' + - regex: '; *(ISW11M) Build' + device_replacement: '$1' + brand_replacement: 'Motorola' + model_replacement: 'Photon' + - regex: '; *(ISW11SC) Build' + device_replacement: '$1' + brand_replacement: 'Samsung' + model_replacement: 'GALAXY S II WiMAX' + - regex: '; *(ISW12HT) Build' + device_replacement: '$1' + brand_replacement: 'HTC' + model_replacement: 'EVO 3D' + - regex: '; *(ISW13HT) Build' + device_replacement: '$1' + brand_replacement: 'HTC' + model_replacement: 'J' + - regex: '; *(ISW?[0-9]{2}[A-Z]{0,2}) Build' + device_replacement: '$1' + brand_replacement: 'KDDI' + model_replacement: '$1' + - regex: '; *(INFOBAR [^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'KDDI' + model_replacement: '$1' + + ######### + # Kingcom + # @ref: http://www.e-kingcom.com + ######### + - regex: '; *(JOYPAD|Joypad)[ _]([^;/]+) Build/' + device_replacement: '$1 $2' + brand_replacement: 'Kingcom' + model_replacement: '$1 $2' + + ######### + # Kobo + # @ref: https://en.wikipedia.org/wiki/Kobo_Inc. + # @ref: http://www.kobo.com/devices#tablets + ######### + - regex: '; *(Vox|VOX|Arc|K080) Build/' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'Kobo' + model_replacement: '$1' + - regex: '\b(Kobo Touch)\b' + device_replacement: '$1' + brand_replacement: 'Kobo' + model_replacement: '$1' + + ######### + # K-Touch + # @ref: ?? + ######### + - regex: '; *(K-Touch)[ _]([^;/]+) Build' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'Ktouch' + model_replacement: '$2' + + ######### + # KT Tech + # @ref: http://www.kttech.co.kr + ######### + - regex: '; *((?:EV|KM)-S\d+[A-Z]?) Build' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'KTtech' + model_replacement: '$1' + + ######### + # Kyocera + # @ref: http://www.android.com/devices/?country=all&m=kyocera + ######### + - regex: '; *(Zio|Hydro|Torque|Event|EVENT|Echo|Milano|Rise|URBANO PROGRESSO|WX04K|WX06K|WX10K|KYL21|101K|C5[12]\d{2}) Build/' + device_replacement: '$1' + brand_replacement: 'Kyocera' + model_replacement: '$1' + + ######### + # Lava + # @ref: http://www.lavamobiles.com/ + ######### + - regex: '; *(?:LAVA[ _])?IRIS[ _\-]?([^/;\)]+) *(?:;|\)|Build)' + regex_flag: 'i' + device_replacement: 'Iris $1' + brand_replacement: 'Lava' + model_replacement: 'Iris $1' + - regex: '; *LAVA[ _]([^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Lava' + model_replacement: '$1' + + ######### + # Lemon + # @ref: http://www.lemonmobiles.com/products.php?type=1 + ######### + - regex: '; *(?:(Aspire A1)|(?:LEMON|Lemon)[ _]([^;/]+))_? Build' + device_replacement: 'Lemon $1$2' + brand_replacement: 'Lemon' + model_replacement: '$1$2' + + ######### + # Lenco + # @ref: http://www.lenco.com/c/tablets/ + ######### + - regex: '; *(TAB-1012) Build/' + device_replacement: 'Lenco $1' + brand_replacement: 'Lenco' + model_replacement: '$1' + - regex: '; Lenco ([^;/]+) Build/' + device_replacement: 'Lenco $1' + brand_replacement: 'Lenco' + model_replacement: '$1' + + ######### + # Lenovo + # @ref: http://support.lenovo.com/en_GB/downloads/default.page?# + ######### + - regex: '; *(A1_07|A2107A-H|S2005A-H|S1-37AH0) Build' + device_replacement: '$1' + brand_replacement: 'Lenovo' + model_replacement: '$1' + - regex: '; *(Idea[Tp]ab)[ _]([^;/]+);? Build' + device_replacement: 'Lenovo $1 $2' + brand_replacement: 'Lenovo' + model_replacement: '$1 $2' + - regex: '; *(Idea(?:Tab|pad)) ?([^;/]+) Build' + device_replacement: 'Lenovo $1 $2' + brand_replacement: 'Lenovo' + model_replacement: '$1 $2' + - regex: '; *(ThinkPad) ?(Tablet) Build/' + device_replacement: 'Lenovo $1 $2' + brand_replacement: 'Lenovo' + model_replacement: '$1 $2' + - regex: '; *(?:LNV-)?(?:=?[Ll]enovo[ _\-]?|LENOVO[ _])+(.+?)(?:Build|[;/\)])' + device_replacement: 'Lenovo $1' + brand_replacement: 'Lenovo' + model_replacement: '$1' + - regex: '[;,] (?:Vodafone )?(SmartTab) ?(II) ?(\d+) Build/' + device_replacement: 'Lenovo $1 $2 $3' + brand_replacement: 'Lenovo' + model_replacement: '$1 $2 $3' + - regex: '; *(?:Ideapad )?K1 Build/' + device_replacement: 'Lenovo Ideapad K1' + brand_replacement: 'Lenovo' + model_replacement: 'Ideapad K1' + - regex: '; *(3GC101|3GW10[01]|A390) Build/' + device_replacement: '$1' + brand_replacement: 'Lenovo' + model_replacement: '$1' + - regex: '\b(?:Lenovo|LENOVO)+[ _\-]?([^,;:/ ]+)' + device_replacement: 'Lenovo $1' + brand_replacement: 'Lenovo' + model_replacement: '$1' + + ######### + # Lexibook + # @ref: http://www.lexibook.com/fr + ######### + - regex: '; *(MFC\d+)[A-Z]{2}([^;,/]*),? Build' + device_replacement: '$1$2' + brand_replacement: 'Lexibook' + model_replacement: '$1$2' + + ######### + # LG + # @ref: http://www.lg.com/uk/mobile + ######### + - regex: '; *(E[34][0-9]{2}|LS[6-8][0-9]{2}|VS[6-9][0-9]+[^;/]+|Nexus 4|Nexus 5X?|GT540f?|Optimus (?:2X|G|4X HD)|OptimusX4HD) *(?:Build|;)' + device_replacement: '$1' + brand_replacement: 'LG' + model_replacement: '$1' + - regex: '[;:] *(L-\d+[A-Z]|LGL\d+[A-Z]?)(?:/V\d+)? *(?:Build|[;\)])' + device_replacement: '$1' + brand_replacement: 'LG' + model_replacement: '$1' + - regex: '; *(LG-)([A-Z]{1,2}\d{2,}[^,;/\)\(]*?)(?:Build| V\d+|[,;/\)\(]|$)' + device_replacement: '$1$2' + brand_replacement: 'LG' + model_replacement: '$2' + - regex: '; *(LG[ \-]|LG)([^;/]+)[;/]? Build' + device_replacement: '$1$2' + brand_replacement: 'LG' + model_replacement: '$2' + - regex: '^(LG)-([^;/]+)/ Mozilla/.*; Android' + device_replacement: '$1 $2' + brand_replacement: 'LG' + model_replacement: '$2' + + ######### + # Malata + # @ref: http://www.malata.com/en/products.aspx?classid=680 + ######### + - regex: '; *((?:SMB|smb)[^;/]+) Build/' + device_replacement: '$1' + brand_replacement: 'Malata' + model_replacement: '$1' + - regex: '; *(?:Malata|MALATA) ([^;/]+) Build/' + device_replacement: '$1' + brand_replacement: 'Malata' + model_replacement: '$1' + + ######### + # Manta + # @ref: http://www.manta.com.pl/en + ######### + - regex: '; *(MS[45][0-9]{3}|MID0[568][NS]?|MID[1-9]|MID[78]0[1-9]|MID970[1-9]|MID100[1-9]) Build/' + device_replacement: '$1' + brand_replacement: 'Manta' + model_replacement: '$1' + + ######### + # Match + # @ref: http://www.match.net.cn/products.asp + ######### + - regex: '; *(M1052|M806|M9000|M9100|M9701|MID100|MID120|MID125|MID130|MID135|MID140|MID701|MID710|MID713|MID727|MID728|MID731|MID732|MID733|MID735|MID736|MID737|MID760|MID800|MID810|MID820|MID830|MID833|MID835|MID860|MID900|MID930|MID933|MID960|MID980) Build/' + device_replacement: '$1' + brand_replacement: 'Match' + model_replacement: '$1' + + ######### + # Maxx + # @ref: http://www.maxxmobile.in/ + # @models: Maxx MSD7-Play, Maxx MX245+ Trance, Maxx AX8 Race, Maxx MSD7 3G- AX50, Maxx Genx Droid 7 - AX40, Maxx AX5 Duo, + # Maxx AX3 Duo, Maxx AX3, Maxx AX8 Note II (Note 2), Maxx AX8 Note I, Maxx AX8, Maxx AX5 Plus, Maxx MSD7 Smarty, + # Maxx AX9Z Race, + # Maxx MT150, Maxx MQ601, Maxx M2020, Maxx Sleek MX463neo, Maxx MX525, Maxx MX192-Tune, Maxx Genx Droid 7 AX353, + # @note: Need more User-Agents!!! + ######### + - regex: '; *(GenxDroid7|MSD7.*|AX\d.*|Tab 701|Tab 722) Build/' + device_replacement: 'Maxx $1' + brand_replacement: 'Maxx' + model_replacement: '$1' + + ######### + # Mediacom + # @ref: http://www.mediacomeurope.it/ + ######### + - regex: '; *(M-PP[^;/]+|PhonePad ?\d{2,}[^;/]+) Build' + device_replacement: 'Mediacom $1' + brand_replacement: 'Mediacom' + model_replacement: '$1' + - regex: '; *(M-MP[^;/]+|SmartPad ?\d{2,}[^;/]+) Build' + device_replacement: 'Mediacom $1' + brand_replacement: 'Mediacom' + model_replacement: '$1' + + ######### + # Medion + # @ref: http://www.medion.com/en/ + ######### + - regex: '; *(?:MD_)?LIFETAB[ _]([^;/]+) Build' + regex_flag: 'i' + device_replacement: 'Medion Lifetab $1' + brand_replacement: 'Medion' + model_replacement: 'Lifetab $1' + - regex: '; *MEDION ([^;/]+) Build' + device_replacement: 'Medion $1' + brand_replacement: 'Medion' + model_replacement: '$1' + + ######### + # Meizu + # @ref: http://www.meizu.com + ######### + - regex: '; *(M030|M031|M035|M040|M065|m9) Build' + device_replacement: 'Meizu $1' + brand_replacement: 'Meizu' + model_replacement: '$1' + - regex: '; *(?:meizu_|MEIZU )(.+?) *(?:Build|[;\)])' + device_replacement: 'Meizu $1' + brand_replacement: 'Meizu' + model_replacement: '$1' + + ######### + # Micromax + # @ref: http://www.micromaxinfo.com + ######### + - regex: '; *(?:Micromax[ _](A111|A240)|(A111|A240)) Build' + regex_flag: 'i' + device_replacement: 'Micromax $1$2' + brand_replacement: 'Micromax' + model_replacement: '$1$2' + - regex: '; *Micromax[ _](A\d{2,3}[^;/]*) Build' + regex_flag: 'i' + device_replacement: 'Micromax $1' + brand_replacement: 'Micromax' + model_replacement: '$1' + # be carefull here with Acer e.g. A500 + - regex: '; *(A\d{2}|A[12]\d{2}|A90S|A110Q) Build' + regex_flag: 'i' + device_replacement: 'Micromax $1' + brand_replacement: 'Micromax' + model_replacement: '$1' + - regex: '; *Micromax[ _](P\d{3}[^;/]*) Build' + regex_flag: 'i' + device_replacement: 'Micromax $1' + brand_replacement: 'Micromax' + model_replacement: '$1' + - regex: '; *(P\d{3}|P\d{3}\(Funbook\)) Build' + regex_flag: 'i' + device_replacement: 'Micromax $1' + brand_replacement: 'Micromax' + model_replacement: '$1' + + ######### + # Mito + # @ref: http://new.mitomobile.com/ + ######### + - regex: '; *(MITO)[ _\-]?([^;/]+) Build' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'Mito' + model_replacement: '$2' + + ######### + # Mobistel + # @ref: http://www.mobistel.com/ + ######### + - regex: '; *(Cynus)[ _](F5|T\d|.+?) *(?:Build|[;/\)])' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'Mobistel' + model_replacement: '$1 $2' + + ######### + # Modecom + # @ref: http://www.modecom.eu/tablets/portal/ + ######### + - regex: '; *(MODECOM )?(FreeTab) ?([^;/]+) Build' + regex_flag: 'i' + device_replacement: '$1$2 $3' + brand_replacement: 'Modecom' + model_replacement: '$2 $3' + - regex: '; *(MODECOM )([^;/]+) Build' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'Modecom' + model_replacement: '$2' + + ######### + # Motorola + # @ref: http://www.motorola.com/us/shop-all-mobile-phones/ + ######### + - regex: '; *(MZ\d{3}\+?|MZ\d{3} 4G|Xoom|XOOM[^;/]*) Build' + device_replacement: 'Motorola $1' + brand_replacement: 'Motorola' + model_replacement: '$1' + - regex: '; *(Milestone )(XT[^;/]*) Build' + device_replacement: 'Motorola $1$2' + brand_replacement: 'Motorola' + model_replacement: '$2' + - regex: '; *(Motoroi ?x|Droid X|DROIDX) Build' + regex_flag: 'i' + device_replacement: 'Motorola $1' + brand_replacement: 'Motorola' + model_replacement: 'DROID X' + - regex: '; *(Droid[^;/]*|DROID[^;/]*|Milestone[^;/]*|Photon|Triumph|Devour|Titanium) Build' + device_replacement: 'Motorola $1' + brand_replacement: 'Motorola' + model_replacement: '$1' + - regex: '; *(A555|A85[34][^;/]*|A95[356]|ME[58]\d{2}\+?|ME600|ME632|ME722|MB\d{3}\+?|MT680|MT710|MT870|MT887|MT917|WX435|WX453|WX44[25]|XT\d{3,4}[A-Z\+]*|CL[iI]Q|CL[iI]Q XT) Build' + device_replacement: '$1' + brand_replacement: 'Motorola' + model_replacement: '$1' + - regex: '; *(Motorola MOT-|Motorola[ _\-]|MOT\-?)([^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'Motorola' + model_replacement: '$2' + - regex: '; *(Moto[_ ]?|MOT\-)([^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'Motorola' + model_replacement: '$2' + + ######### + # MpMan + # @ref: http://www.mpmaneurope.com + ######### + - regex: '; *((?:MP[DQ]C|MPG\d{1,4}|MP\d{3,4}|MID(?:(?:10[234]|114|43|7[247]|8[24]|7)C|8[01]1))[^;/]*) Build' + device_replacement: '$1' + brand_replacement: 'Mpman' + model_replacement: '$1' + + ######### + # MSI + # @ref: http://www.msi.com/product/windpad/ + ######### + - regex: '; *(?:MSI[ _])?(Primo\d+|Enjoy[ _\-][^;/]+) Build' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'Msi' + model_replacement: '$1' + + ######### + # Multilaser + # http://www.multilaser.com.br/listagem_produtos.php?cat=5 + ######### + - regex: '; *Multilaser[ _]([^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Multilaser' + model_replacement: '$1' + + ######### + # MyPhone + # @ref: http://myphone.com.ph/ + ######### + - regex: '; *(My)[_]?(Pad)[ _]([^;/]+) Build' + device_replacement: '$1$2 $3' + brand_replacement: 'MyPhone' + model_replacement: '$1$2 $3' + - regex: '; *(My)\|?(Phone)[ _]([^;/]+) Build' + device_replacement: '$1$2 $3' + brand_replacement: 'MyPhone' + model_replacement: '$3' + - regex: '; *(A\d+)[ _](Duo)? Build' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'MyPhone' + model_replacement: '$1 $2' + + ######### + # Mytab + # @ref: http://www.mytab.eu/en/category/mytab-products/ + ######### + - regex: '; *(myTab[^;/]*) Build' + device_replacement: '$1' + brand_replacement: 'Mytab' + model_replacement: '$1' + + ######### + # Nabi + # @ref: https://www.nabitablet.com + ######### + - regex: '; *(NABI2?-)([^;/]+) Build/' + device_replacement: '$1$2' + brand_replacement: 'Nabi' + model_replacement: '$2' + + ######### + # Nec Medias + # @ref: http://www.n-keitai.com/ + ######### + - regex: '; *(N-\d+[CDE]) Build/' + device_replacement: '$1' + brand_replacement: 'Nec' + model_replacement: '$1' + - regex: '; ?(NEC-)(.*) Build/' + device_replacement: '$1$2' + brand_replacement: 'Nec' + model_replacement: '$2' + - regex: '; *(LT-NA7) Build/' + device_replacement: '$1' + brand_replacement: 'Nec' + model_replacement: 'Lifetouch Note' + + ######### + # Nextbook + # @ref: http://nextbookusa.com + ######### + - regex: '; *(NXM\d+[A-z0-9_]*|Next\d[A-z0-9_ \-]*|NEXT\d[A-z0-9_ \-]*|Nextbook [A-z0-9_ ]*|DATAM803HC|M805)(?: Build|[\);])' + device_replacement: '$1' + brand_replacement: 'Nextbook' + model_replacement: '$1' + + ######### + # Nokia + # @ref: http://www.nokia.com + ######### + - regex: '; *(Nokia)([ _\-]*)([^;/]*) Build' + regex_flag: 'i' + device_replacement: '$1$2$3' + brand_replacement: 'Nokia' + model_replacement: '$3' + + ######### + # Nook + # @ref: + # TODO nook browser/1.0 + ######### + - regex: '; *(Nook ?|Barnes & Noble Nook |BN )([^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'Nook' + model_replacement: '$2' + - regex: '; *(NOOK )?(BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2) Build' + device_replacement: '$1$2' + brand_replacement: 'Nook' + model_replacement: '$2' + - regex: '; Build/(Nook)' + device_replacement: '$1' + brand_replacement: 'Nook' + model_replacement: 'Tablet' + + ######### + # Olivetti + # @ref: http://www.olivetti.de/EN/Page/t02/view_html?idp=348 + ######### + - regex: '; *(OP110|OliPad[^;/]+) Build' + device_replacement: 'Olivetti $1' + brand_replacement: 'Olivetti' + model_replacement: '$1' + + ######### + # Omega + # @ref: http://omega-technology.eu/en/produkty/346/tablets + # @note: MID tablets might get matched by CobyKyros first + # @models: (T107|MID(?:700[2-5]|7031|7108|7132|750[02]|8001|8500|9001|971[12]) + ######### + - regex: '; *OMEGA[ _\-](MID[^;/]+) Build' + device_replacement: 'Omega $1' + brand_replacement: 'Omega' + model_replacement: '$1' + - regex: '^(MID7500|MID\d+) Mozilla/5\.0 \(iPad;' + device_replacement: 'Omega $1' + brand_replacement: 'Omega' + model_replacement: '$1' + + ######### + # OpenPeak + # @ref: https://support.google.com/googleplay/answer/1727131?hl=en + ######### + - regex: '; *((?:CIUS|cius)[^;/]*) Build' + device_replacement: 'Openpeak $1' + brand_replacement: 'Openpeak' + model_replacement: '$1' + + ######### + # Oppo + # @ref: http://en.oppo.com/products/ + ######### + - regex: '; *(Find ?(?:5|7a)|R8[012]\d{1,2}|T703\d{0,1}|U70\d{1,2}T?|X90\d{1,2}) Build' + device_replacement: 'Oppo $1' + brand_replacement: 'Oppo' + model_replacement: '$1' + - regex: '; *OPPO ?([^;/]+) Build/' + device_replacement: 'Oppo $1' + brand_replacement: 'Oppo' + model_replacement: '$1' + + ######### + # Odys + # @ref: http://odys.de + ######### + - regex: '; *(?:Odys\-|ODYS\-|ODYS )([^;/]+) Build' + device_replacement: 'Odys $1' + brand_replacement: 'Odys' + model_replacement: '$1' + - regex: '; *(SELECT) ?(7) Build' + device_replacement: 'Odys $1 $2' + brand_replacement: 'Odys' + model_replacement: '$1 $2' + - regex: '; *(PEDI)_(PLUS)_(W) Build' + device_replacement: 'Odys $1 $2 $3' + brand_replacement: 'Odys' + model_replacement: '$1 $2 $3' + # Weltbild - Tablet PC 4 = Cat Phoenix = Odys Tablet PC 4? + - regex: '; *(AEON|BRAVIO|FUSION|FUSION2IN1|Genio|EOS10|IEOS[^;/]*|IRON|Loox|LOOX|LOOX Plus|Motion|NOON|NOON_PRO|NEXT|OPOS|PEDI[^;/]*|PRIME[^;/]*|STUDYTAB|TABLO|Tablet-PC-4|UNO_X8|XELIO[^;/]*|Xelio ?\d+ ?[Pp]ro|XENO10|XPRESS PRO) Build' + device_replacement: 'Odys $1' + brand_replacement: 'Odys' + model_replacement: '$1' + + ######### + # Orion + # @ref: http://www.orion.ua/en/products/computer-products/tablet-pcs.html + ######### + - regex: '; *(TP-\d+) Build/' + device_replacement: 'Orion $1' + brand_replacement: 'Orion' + model_replacement: '$1' + + ######### + # PackardBell + # @ref: http://www.packardbell.com/pb/en/AE/content/productgroup/tablets + ######### + - regex: '; *(G100W?) Build/' + device_replacement: 'PackardBell $1' + brand_replacement: 'PackardBell' + model_replacement: '$1' + + ######### + # Panasonic + # @ref: http://panasonic.jp/mobile/ + # @models: T11, T21, T31, P11, P51, Eluga Power, Eluga DL1 + # @models: (tab) Toughpad FZ-A1, Toughpad JT-B1 + ######### + - regex: '; *(Panasonic)[_ ]([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + # Toughpad + - regex: '; *(FZ-A1B|JT-B1) Build' + device_replacement: 'Panasonic $1' + brand_replacement: 'Panasonic' + model_replacement: '$1' + # Eluga Power + - regex: '; *(dL1|DL1) Build' + device_replacement: 'Panasonic $1' + brand_replacement: 'Panasonic' + model_replacement: '$1' + + ######### + # Pantech + # @href: http://www.pantech.co.kr/en/prod/prodList.do?gbrand=PANTECH + # @href: http://www.pantech.co.kr/en/prod/prodList.do?gbrand=VEGA + # @models: ADR8995, ADR910L, ADR930VW, C790, CDM8992, CDM8999, IS06, IS11PT, P2000, P2020, P2030, P4100, P5000, P6010, P6020, P6030, P7000, P7040, P8000, P8010, P9020, P9050, P9060, P9070, P9090, PT001, PT002, PT003, TXT8040, TXT8045, VEGA PTL21 + ######### + - regex: '; *(SKY[ _])?(IM\-[AT]\d{3}[^;/]+).* Build/' + device_replacement: 'Pantech $1$2' + brand_replacement: 'Pantech' + model_replacement: '$1$2' + - regex: '; *((?:ADR8995|ADR910L|ADR930L|ADR930VW|PTL21|P8000)(?: 4G)?) Build/' + device_replacement: '$1' + brand_replacement: 'Pantech' + model_replacement: '$1' + - regex: '; *Pantech([^;/]+).* Build/' + device_replacement: 'Pantech $1' + brand_replacement: 'Pantech' + model_replacement: '$1' + + ######### + # Papayre + # @ref: http://grammata.es/ + ######### + - regex: '; *(papyre)[ _\-]([^;/]+) Build/' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'Papyre' + model_replacement: '$2' + + ######### + # Pearl + # @ref: http://www.pearl.de/c-1540.shtml + ######### + - regex: '; *(?:Touchlet )?(X10\.[^;/]+) Build/' + device_replacement: 'Pearl $1' + brand_replacement: 'Pearl' + model_replacement: '$1' + + ######### + # Phicomm + # @ref: http://www.phicomm.com.cn/ + ######### + - regex: '; PHICOMM (i800) Build/' + device_replacement: 'Phicomm $1' + brand_replacement: 'Phicomm' + model_replacement: '$1' + - regex: '; PHICOMM ([^;/]+) Build/' + device_replacement: 'Phicomm $1' + brand_replacement: 'Phicomm' + model_replacement: '$1' + - regex: '; *(FWS\d{3}[^;/]+) Build/' + device_replacement: 'Phicomm $1' + brand_replacement: 'Phicomm' + model_replacement: '$1' + + ######### + # Philips + # @ref: http://www.support.philips.com/support/catalog/products.jsp?_dyncharset=UTF-8&country=&categoryid=MOBILE_PHONES_SMART_SU_CN_CARE&userLanguage=en&navCount=2&groupId=PC_PRODUCTS_AND_PHONES_GR_CN_CARE&catalogType=&navAction=push&userCountry=cn&title=Smartphones&cateId=MOBILE_PHONES_CA_CN_CARE + # @TODO: Philips Tablets User-Agents missing! + # @ref: http://www.support.philips.com/support/catalog/products.jsp?_dyncharset=UTF-8&country=&categoryid=ENTERTAINMENT_TABLETS_SU_CN_CARE&userLanguage=en&navCount=0&groupId=&catalogType=&navAction=push&userCountry=cn&title=Entertainment+Tablets&cateId=TABLETS_CA_CN_CARE + ######### + # @note: this a best guess according to available philips models. Need more User-Agents + - regex: '; *(D633|D822|D833|T539|T939|V726|W335|W336|W337|W3568|W536|W5510|W626|W632|W6350|W6360|W6500|W732|W736|W737|W7376|W820|W832|W8355|W8500|W8510|W930) Build' + device_replacement: '$1' + brand_replacement: 'Philips' + model_replacement: '$1' + - regex: '; *(?:Philips|PHILIPS)[ _]([^;/]+) Build' + device_replacement: 'Philips $1' + brand_replacement: 'Philips' + model_replacement: '$1' + + ######### + # Pipo + # @ref: http://www.pipo.cn/En/ + ######### + - regex: 'Android 4\..*; *(M[12356789]|U[12368]|S[123])\ ?(pro)? Build' + device_replacement: 'Pipo $1$2' + brand_replacement: 'Pipo' + model_replacement: '$1$2' + + ######### + # Ployer + # @ref: http://en.ployer.cn/ + ######### + - regex: '; *(MOMO[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Ployer' + model_replacement: '$1' + + ######### + # Polaroid/ Acho + # @ref: http://polaroidstore.com/store/start.asp?category_id=382&category_id2=0&order=title&filter1=&filter2=&filter3=&view=all + ######### + - regex: '; *(?:Polaroid[ _])?((?:MIDC\d{3,}|PMID\d{2,}|PTAB\d{3,})[^;/]*)(\/[^;/]*)? Build/' + device_replacement: '$1' + brand_replacement: 'Polaroid' + model_replacement: '$1' + - regex: '; *(?:Polaroid )(Tablet) Build/' + device_replacement: '$1' + brand_replacement: 'Polaroid' + model_replacement: '$1' + + ######### + # Pomp + # @ref: http://pompmobileshop.com/ + ######### + #~ TODO + - regex: '; *(POMP)[ _\-](.+?) *(?:Build|[;/\)])' + device_replacement: '$1 $2' + brand_replacement: 'Pomp' + model_replacement: '$2' + + ######### + # Positivo + # @ref: http://www.positivoinformatica.com.br/www/pessoal/tablet-ypy/ + ######### + - regex: '; *(TB07STA|TB10STA|TB07FTA|TB10FTA) Build/' + device_replacement: '$1' + brand_replacement: 'Positivo' + model_replacement: '$1' + - regex: '; *(?:Positivo )?((?:YPY|Ypy)[^;/]+) Build/' + device_replacement: '$1' + brand_replacement: 'Positivo' + model_replacement: '$1' + + ######### + # POV + # @ref: http://www.pointofview-online.com/default2.php + # @TODO: Smartphone Models MOB-3515, MOB-5045-B missing + ######### + - regex: '; *(MOB-[^;/]+) Build/' + device_replacement: '$1' + brand_replacement: 'POV' + model_replacement: '$1' + - regex: '; *POV[ _\-]([^;/]+) Build/' + device_replacement: 'POV $1' + brand_replacement: 'POV' + model_replacement: '$1' + - regex: '; *((?:TAB-PLAYTAB|TAB-PROTAB|PROTAB|PlayTabPro|Mobii[ _\-]|TAB-P)[^;/]*) Build/' + device_replacement: 'POV $1' + brand_replacement: 'POV' + model_replacement: '$1' + + ######### + # Prestigio + # @ref: http://www.prestigio.com/catalogue/MultiPhones + # @ref: http://www.prestigio.com/catalogue/MultiPads + ######### + - regex: '; *(?:Prestigio )?((?:PAP|PMP)\d[^;/]+) Build/' + device_replacement: 'Prestigio $1' + brand_replacement: 'Prestigio' + model_replacement: '$1' + + ######### + # Proscan + # @ref: http://www.proscanvideo.com/products-search.asp?itemClass=TABLET&itemnmbr= + ######### + - regex: '; *(PLT[0-9]{4}.*) Build/' + device_replacement: '$1' + brand_replacement: 'Proscan' + model_replacement: '$1' + + ######### + # QMobile + # @ref: http://www.qmobile.com.pk/ + ######### + - regex: '; *(A2|A5|A8|A900)_?(Classic)? Build' + device_replacement: '$1 $2' + brand_replacement: 'Qmobile' + model_replacement: '$1 $2' + - regex: '; *(Q[Mm]obile)_([^_]+)_([^_]+) Build' + device_replacement: 'Qmobile $2 $3' + brand_replacement: 'Qmobile' + model_replacement: '$2 $3' + - regex: '; *(Q\-?[Mm]obile)[_ ](A[^;/]+) Build' + device_replacement: 'Qmobile $2' + brand_replacement: 'Qmobile' + model_replacement: '$2' + + ######### + # Qmobilevn + # @ref: http://qmobile.vn/san-pham.html + ######### + - regex: '; *(Q\-Smart)[ _]([^;/]+) Build/' + device_replacement: '$1 $2' + brand_replacement: 'Qmobilevn' + model_replacement: '$2' + - regex: '; *(Q\-?[Mm]obile)[ _\-](S[^;/]+) Build/' + device_replacement: '$1 $2' + brand_replacement: 'Qmobilevn' + model_replacement: '$2' + + ######### + # Quanta + # @ref: ? + ######### + - regex: '; *(TA1013) Build' + device_replacement: '$1' + brand_replacement: 'Quanta' + model_replacement: '$1' + + ######### + # Rockchip + # @ref: http://www.rock-chips.com/a/cn/product/index.html + # @note: manufacturer sells chipsets - I assume that these UAs are dev-boards + ######### + - regex: '; *(RK\d+),? Build/' + device_replacement: '$1' + brand_replacement: 'Rockchip' + model_replacement: '$1' + - regex: ' Build/(RK\d+)' + device_replacement: '$1' + brand_replacement: 'Rockchip' + model_replacement: '$1' + + ######### + # Samsung Android Devices + # @ref: http://www.samsung.com/us/mobile/cell-phones/all-products + ######### + - regex: '; *(SAMSUNG |Samsung )?((?:Galaxy (?:Note II|S\d)|GT-I9082|GT-I9205|GT-N7\d{3}|SM-N9005)[^;/]*)\/?[^;/]* Build/' + device_replacement: 'Samsung $1$2' + brand_replacement: 'Samsung' + model_replacement: '$2' + - regex: '; *(Google )?(Nexus [Ss](?: 4G)?) Build/' + device_replacement: 'Samsung $1$2' + brand_replacement: 'Samsung' + model_replacement: '$2' + - regex: '; *(SAMSUNG |Samsung )([^\/]*)\/[^ ]* Build/' + device_replacement: 'Samsung $2' + brand_replacement: 'Samsung' + model_replacement: '$2' + - regex: '; *(Galaxy(?: Ace| Nexus| S ?II+|Nexus S| with MCR 1.2| Mini Plus 4G)?) Build/' + device_replacement: 'Samsung $1' + brand_replacement: 'Samsung' + model_replacement: '$1' + - regex: '; *(SAMSUNG[ _\-] *)+([^;/]+) Build' + device_replacement: 'Samsung $2' + brand_replacement: 'Samsung' + model_replacement: '$2' + - regex: '; *(SAMSUNG-)?(GT\-[BINPS]\d{4}[^\/]*)(\/[^ ]*) Build' + device_replacement: 'Samsung $1$2$3' + brand_replacement: 'Samsung' + model_replacement: '$2' + - regex: '(?:; *|^)((?:GT\-[BIiNPS]\d{4}|I9\d{2}0[A-Za-z\+]?\b)[^;/\)]*?)(?:Build|Linux|MIUI|[;/\)])' + device_replacement: 'Samsung $1' + brand_replacement: 'Samsung' + model_replacement: '$1' + - regex: '; (SAMSUNG-)([A-Za-z0-9\-]+).* Build/' + device_replacement: 'Samsung $1$2' + brand_replacement: 'Samsung' + model_replacement: '$2' + - regex: '; *((?:SCH|SGH|SHV|SHW|SPH|SC|SM)\-[A-Za-z0-9 ]+)(/?[^ ]*)? Build' + device_replacement: 'Samsung $1' + brand_replacement: 'Samsung' + model_replacement: '$1' + - regex: ' ((?:SCH)\-[A-Za-z0-9 ]+)(/?[^ ]*)? Build' + device_replacement: 'Samsung $1' + brand_replacement: 'Samsung' + model_replacement: '$1' + - regex: '; *(Behold ?(?:2|II)|YP\-G[^;/]+|EK-GC100|SCL21|I9300) Build' + device_replacement: 'Samsung $1' + brand_replacement: 'Samsung' + model_replacement: '$1' + + ######### + # Sharp + # @ref: http://www.sharp-phone.com/en/index.html + # @ref: http://www.android.com/devices/?country=all&m=sharp + ######### + - regex: '; *(SH\-?\d\d[^;/]+|SBM\d[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Sharp' + model_replacement: '$1' + - regex: '; *(SHARP[ -])([^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'Sharp' + model_replacement: '$2' + + ######### + # Simvalley + # @ref: http://www.simvalley-mobile.de/ + ######### + - regex: '; *(SPX[_\-]\d[^;/]*) Build/' + device_replacement: '$1' + brand_replacement: 'Simvalley' + model_replacement: '$1' + - regex: '; *(SX7\-PEARL\.GmbH) Build/' + device_replacement: '$1' + brand_replacement: 'Simvalley' + model_replacement: '$1' + - regex: '; *(SP[T]?\-\d{2}[^;/]*) Build/' + device_replacement: '$1' + brand_replacement: 'Simvalley' + model_replacement: '$1' + + ######### + # SK Telesys + # @ref: http://www.sk-w.com/phone/phone_list.jsp + # @ref: http://www.android.com/devices/?country=all&m=sk-telesys + ######### + - regex: '; *(SK\-.*) Build/' + device_replacement: '$1' + brand_replacement: 'SKtelesys' + model_replacement: '$1' + + ######### + # Skytex + # @ref: http://skytex.com/android + ######### + - regex: '; *(?:SKYTEX|SX)-([^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Skytex' + model_replacement: '$1' + - regex: '; *(IMAGINE [^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Skytex' + model_replacement: '$1' + + ######### + # SmartQ + # @ref: http://en.smartdevices.com.cn/Products/ + # @models: Z8, X7, U7H, U7, T30, T20, Ten3, V5-II, T7-3G, SmartQ5, K7, S7, Q8, T19, Ten2, Ten, R10, T7, R7, V5, V7, SmartQ7 + ######### + - regex: '; *(SmartQ) ?([^;/]+) Build/' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + + ######### + # Smartbitt + # @ref: http://www.smartbitt.com/ + # @missing: SBT Useragents + ######### + - regex: '; *(WF7C|WF10C|SBT[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Smartbitt' + model_replacement: '$1' + + ######### + # Softbank (Operator Branded Devices) + # @ref: http://www.ipentec.com/document/document.aspx?page=android-useragent + ######### + - regex: '; *(SBM(?:003SH|005SH|006SH|007SH|102SH)) Build' + device_replacement: '$1' + brand_replacement: 'Sharp' + model_replacement: '$1' + - regex: '; *(003P|101P|101P11C|102P) Build' + device_replacement: '$1' + brand_replacement: 'Panasonic' + model_replacement: '$1' + - regex: '; *(00\dZ) Build/' + device_replacement: '$1' + brand_replacement: 'ZTE' + model_replacement: '$1' + - regex: '; HTC(X06HT) Build' + device_replacement: '$1' + brand_replacement: 'HTC' + model_replacement: '$1' + - regex: '; *(001HT|X06HT) Build' + device_replacement: '$1' + brand_replacement: 'HTC' + model_replacement: '$1' + - regex: '; *(201M) Build' + device_replacement: '$1' + brand_replacement: 'Motorola' + model_replacement: 'XT902' + + ######### + # Trekstor + # @ref: http://www.trekstor.co.uk/surftabs-en.html + # @note: Must come before SonyEricsson + ######### + - regex: '; *(ST\d{4}.*)Build/ST' + device_replacement: 'Trekstor $1' + brand_replacement: 'Trekstor' + model_replacement: '$1' + - regex: '; *(ST\d{4}.*) Build/' + device_replacement: 'Trekstor $1' + brand_replacement: 'Trekstor' + model_replacement: '$1' + + ######### + # SonyEricsson + # @note: Must come before nokia since they also use symbian + # @ref: http://www.android.com/devices/?country=all&m=sony-ericssons + # @TODO: type! + ######### + # android matchers + - regex: '; *(Sony ?Ericsson ?)([^;/]+) Build' + device_replacement: '$1$2' + brand_replacement: 'SonyEricsson' + model_replacement: '$2' + - regex: '; *((?:SK|ST|E|X|LT|MK|MT|WT)\d{2}[a-z0-9]*(?:-o)?|R800i|U20i) Build' + device_replacement: '$1' + brand_replacement: 'SonyEricsson' + model_replacement: '$1' + # TODO X\d+ is wrong + - regex: '; *(Xperia (?:A8|Arc|Acro|Active|Live with Walkman|Mini|Neo|Play|Pro|Ray|X\d+)[^;/]*) Build' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'SonyEricsson' + model_replacement: '$1' + + ######### + # Sony + # @ref: http://www.sonymobile.co.jp/index.html + # @ref: http://www.sonymobile.com/global-en/products/phones/ + # @ref: http://www.sony.jp/tablet/ + ######### + - regex: '; Sony (Tablet[^;/]+) Build' + device_replacement: 'Sony $1' + brand_replacement: 'Sony' + model_replacement: '$1' + - regex: '; Sony ([^;/]+) Build' + device_replacement: 'Sony $1' + brand_replacement: 'Sony' + model_replacement: '$1' + - regex: '; *(Sony)([A-Za-z0-9\-]+) Build' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + - regex: '; *(Xperia [^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Sony' + model_replacement: '$1' + - regex: '; *(C(?:1[0-9]|2[0-9]|53|55|6[0-9])[0-9]{2}|D[25]\d{3}|D6[56]\d{2}) Build' + device_replacement: '$1' + brand_replacement: 'Sony' + model_replacement: '$1' + - regex: '; *(SGP\d{3}|SGPT\d{2}) Build' + device_replacement: '$1' + brand_replacement: 'Sony' + model_replacement: '$1' + - regex: '; *(NW-Z1000Series) Build' + device_replacement: '$1' + brand_replacement: 'Sony' + model_replacement: '$1' + + ########## + # Sony PlayStation + # @ref: http://playstation.com + # The Vita spoofs the Kindle + ########## + - regex: 'PLAYSTATION 3' + device_replacement: 'PlayStation 3' + brand_replacement: 'Sony' + model_replacement: 'PlayStation 3' + - regex: '(PlayStation (?:Portable|Vita|\d+))' + device_replacement: '$1' + brand_replacement: 'Sony' + model_replacement: '$1' + + ######### + # Spice + # @ref: http://www.spicemobilephones.co.in/ + ######### + - regex: '; *((?:CSL_Spice|Spice|SPICE|CSL)[ _\-]?)?([Mm][Ii])([ _\-])?(\d{3}[^;/]*) Build/' + device_replacement: '$1$2$3$4' + brand_replacement: 'Spice' + model_replacement: 'Mi$4' + + ######### + # Sprint (Operator Branded Devices) + # @ref: + ######### + - regex: '; *(Sprint )(.+?) *(?:Build|[;/])' + device_replacement: '$1$2' + brand_replacement: 'Sprint' + model_replacement: '$2' + - regex: '\b(Sprint)[: ]([^;,/ ]+)' + device_replacement: '$1$2' + brand_replacement: 'Sprint' + model_replacement: '$2' + + ######### + # Tagi + # @ref: ?? + ######### + - regex: '; *(TAGI[ ]?)(MID) ?([^;/]+) Build/' + device_replacement: '$1$2$3' + brand_replacement: 'Tagi' + model_replacement: '$2$3' + + ######### + # Tecmobile + # @ref: http://www.tecmobile.com/ + ######### + - regex: '; *(Oyster500|Opal 800) Build' + device_replacement: 'Tecmobile $1' + brand_replacement: 'Tecmobile' + model_replacement: '$1' + + ######### + # Tecno + # @ref: www.tecno-mobile.com/‎ + ######### + - regex: '; *(TECNO[ _])([^;/]+) Build/' + device_replacement: '$1$2' + brand_replacement: 'Tecno' + model_replacement: '$2' + + ######### + # Telechips, Techvision evaluation boards + # @ref: + ######### + - regex: '; *Android for (Telechips|Techvision) ([^ ]+) ' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + + ######### + # Telstra + # @ref: http://www.telstra.com.au/home-phone/thub-2/ + # @ref: https://support.google.com/googleplay/answer/1727131?hl=en + ######### + - regex: '; *(T-Hub2) Build/' + device_replacement: '$1' + brand_replacement: 'Telstra' + model_replacement: '$1' + + ######### + # Terra + # @ref: http://www.wortmann.de/ + ######### + - regex: '; *(PAD) ?(100[12]) Build/' + device_replacement: 'Terra $1$2' + brand_replacement: 'Terra' + model_replacement: '$1$2' + + ######### + # Texet + # @ref: http://www.texet.ru/tablet/ + ######### + - regex: '; *(T[BM]-\d{3}[^;/]+) Build/' + device_replacement: '$1' + brand_replacement: 'Texet' + model_replacement: '$1' + + ######### + # Thalia + # @ref: http://www.thalia.de/shop/tolino-shine-ereader/show/ + ######### + - regex: '; *(tolino [^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Thalia' + model_replacement: '$1' + - regex: '; *Build/.* (TOLINO_BROWSER)' + device_replacement: '$1' + brand_replacement: 'Thalia' + model_replacement: 'Tolino Shine' + + ######### + # Thl + # @ref: http://en.thl.com.cn/Mobile + # @ref: http://thlmobilestore.com + ######### + - regex: '; *(?:CJ[ -])?(ThL|THL)[ -]([^;/]+) Build/' + device_replacement: '$1 $2' + brand_replacement: 'Thl' + model_replacement: '$2' + - regex: '; *(T100|T200|T5|W100|W200|W8s) Build/' + device_replacement: '$1' + brand_replacement: 'Thl' + model_replacement: '$1' + + ######### + # T-Mobile (Operator Branded Devices) + ######### + # @ref: https://en.wikipedia.org/wiki/HTC_Hero + - regex: '; *(T-Mobile[ _]G2[ _]Touch) Build' + device_replacement: '$1' + brand_replacement: 'HTC' + model_replacement: 'Hero' + # @ref: https://en.wikipedia.org/wiki/HTC_Desire_Z + - regex: '; *(T-Mobile[ _]G2) Build' + device_replacement: '$1' + brand_replacement: 'HTC' + model_replacement: 'Desire Z' + - regex: '; *(T-Mobile myTouch Q) Build' + device_replacement: '$1' + brand_replacement: 'Huawei' + model_replacement: 'U8730' + - regex: '; *(T-Mobile myTouch) Build' + device_replacement: '$1' + brand_replacement: 'Huawei' + model_replacement: 'U8680' + - regex: '; *(T-Mobile_Espresso) Build' + device_replacement: '$1' + brand_replacement: 'HTC' + model_replacement: 'Espresso' + - regex: '; *(T-Mobile G1) Build' + device_replacement: '$1' + brand_replacement: 'HTC' + model_replacement: 'Dream' + - regex: '\b(T-Mobile ?)?(myTouch)[ _]?([34]G)[ _]?([^\/]*) (?:Mozilla|Build)' + device_replacement: '$1$2 $3 $4' + brand_replacement: 'HTC' + model_replacement: '$2 $3 $4' + - regex: '\b(T-Mobile)_([^_]+)_(.*) Build' + device_replacement: '$1 $2 $3' + brand_replacement: 'Tmobile' + model_replacement: '$2 $3' + - regex: '\b(T-Mobile)[_ ]?(.*?)Build' + device_replacement: '$1 $2' + brand_replacement: 'Tmobile' + model_replacement: '$2' + + ######### + # Tomtec + # @ref: http://www.tom-tec.eu/pages/tablets.php + ######### + - regex: ' (ATP[0-9]{4}) Build' + device_replacement: '$1' + brand_replacement: 'Tomtec' + model_replacement: '$1' + + ######### + # Tooky + # @ref: http://www.tookymobile.com/ + ######### + - regex: ' *(TOOKY)[ _\-]([^;/]+) ?(?:Build|;)' + regex_flag: 'i' + device_replacement: '$1 $2' + brand_replacement: 'Tooky' + model_replacement: '$2' + + ######### + # Toshiba + # @ref: http://www.toshiba.co.jp/ + # @missing: LT170, Thrive 7, TOSHIBA STB10 + ######### + - regex: '\b(TOSHIBA_AC_AND_AZ|TOSHIBA_FOLIO_AND_A|FOLIO_AND_A)' + device_replacement: '$1' + brand_replacement: 'Toshiba' + model_replacement: 'Folio 100' + - regex: '; *([Ff]olio ?100) Build/' + device_replacement: '$1' + brand_replacement: 'Toshiba' + model_replacement: 'Folio 100' + - regex: '; *(AT[0-9]{2,3}(?:\-A|LE\-A|PE\-A|SE|a)?|AT7-A|AT1S0|Hikari-iFrame/WDPF-[^;/]+|THRiVE|Thrive) Build/' + device_replacement: 'Toshiba $1' + brand_replacement: 'Toshiba' + model_replacement: '$1' + + ######### + # Touchmate + # @ref: http://touchmatepc.com/new/ + ######### + - regex: '; *(TM-MID\d+[^;/]+|TOUCHMATE|MID-750) Build' + device_replacement: '$1' + brand_replacement: 'Touchmate' + model_replacement: '$1' + # @todo: needs verification user-agents missing + - regex: '; *(TM-SM\d+[^;/]+) Build' + device_replacement: '$1' + brand_replacement: 'Touchmate' + model_replacement: '$1' + + ######### + # Treq + # @ref: http://www.treq.co.id/product + ######### + - regex: '; *(A10 [Bb]asic2?) Build/' + device_replacement: '$1' + brand_replacement: 'Treq' + model_replacement: '$1' + - regex: '; *(TREQ[ _\-])([^;/]+) Build' + regex_flag: 'i' + device_replacement: '$1$2' + brand_replacement: 'Treq' + model_replacement: '$2' + + ######### + # Umeox + # @ref: http://umeox.com/ + # @models: A936|A603|X-5|X-3 + ######### + # @todo: guessed markers + - regex: '; *(X-?5|X-?3) Build/' + device_replacement: '$1' + brand_replacement: 'Umeox' + model_replacement: '$1' + # @todo: guessed markers + - regex: '; *(A502\+?|A936|A603|X1|X2) Build/' + device_replacement: '$1' + brand_replacement: 'Umeox' + model_replacement: '$1' + + ######### + # Versus + # @ref: http://versusuk.com/support.html + ######### + - regex: '(TOUCH(?:TAB|PAD).+?) Build/' + regex_flag: 'i' + device_replacement: 'Versus $1' + brand_replacement: 'Versus' + model_replacement: '$1' + + ######### + # Vertu + # @ref: http://www.vertu.com/ + ######### + - regex: '(VERTU) ([^;/]+) Build/' + device_replacement: '$1 $2' + brand_replacement: 'Vertu' + model_replacement: '$2' + + ######### + # Videocon + # @ref: http://www.videoconmobiles.com + ######### + - regex: '; *(Videocon)[ _\-]([^;/]+) *(?:Build|;)' + device_replacement: '$1 $2' + brand_replacement: 'Videocon' + model_replacement: '$2' + - regex: ' (VT\d{2}[A-Za-z]*) Build' + device_replacement: '$1' + brand_replacement: 'Videocon' + model_replacement: '$1' + + ######### + # Viewsonic + # @ref: http://viewsonic.com + ######### + - regex: '; *((?:ViewPad|ViewPhone|VSD)[^;/]+) Build/' + device_replacement: '$1' + brand_replacement: 'Viewsonic' + model_replacement: '$1' + - regex: '; *(ViewSonic-)([^;/]+) Build/' + device_replacement: '$1$2' + brand_replacement: 'Viewsonic' + model_replacement: '$2' + - regex: '; *(GTablet.*) Build/' + device_replacement: '$1' + brand_replacement: 'Viewsonic' + model_replacement: '$1' + + ######### + # vivo + # @ref: http://vivo.cn/ + ######### + - regex: '; *([Vv]ivo)[ _]([^;/]+) Build' + device_replacement: '$1 $2' + brand_replacement: 'vivo' + model_replacement: '$2' + + ######### + # Vodafone (Operator Branded Devices) + # @ref: ?? + ######### + - regex: '(Vodafone) (.*) Build/' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + + ######### + # Walton + # @ref: http://www.waltonbd.com/ + ######### + - regex: '; *(?:Walton[ _\-])?(Primo[ _\-][^;/]+) Build' + regex_flag: 'i' + device_replacement: 'Walton $1' + brand_replacement: 'Walton' + model_replacement: '$1' + + ######### + # Wiko + # @ref: http://fr.wikomobile.com/collection.php?s=Smartphones + ######### + - regex: '; *(?:WIKO[ \-])?(CINK\+?|BARRY|BLOOM|DARKFULL|DARKMOON|DARKNIGHT|DARKSIDE|FIZZ|HIGHWAY|IGGY|OZZY|RAINBOW|STAIRWAY|SUBLIM|WAX|CINK [^;/]+) Build/' + regex_flag: 'i' + device_replacement: 'Wiko $1' + brand_replacement: 'Wiko' + model_replacement: '$1' + + ######### + # WellcoM + # @ref: ?? + ######### + - regex: '; *WellcoM-([^;/]+) Build' + device_replacement: 'Wellcom $1' + brand_replacement: 'Wellcom' + model_replacement: '$1' + + ########## + # WeTab + # @ref: http://wetab.mobi/ + ########## + - regex: '(?:(WeTab)-Browser|; (wetab) Build)' + device_replacement: '$1' + brand_replacement: 'WeTab' + model_replacement: 'WeTab' + + ######### + # Wolfgang + # @ref: http://wolfgangmobile.com/ + ######### + - regex: '; *(AT-AS[^;/]+) Build' + device_replacement: 'Wolfgang $1' + brand_replacement: 'Wolfgang' + model_replacement: '$1' + + ######### + # Woxter + # @ref: http://www.woxter.es/es-es/categories/index + ######### + - regex: '; *(?:Woxter|Wxt) ([^;/]+) Build' + device_replacement: 'Woxter $1' + brand_replacement: 'Woxter' + model_replacement: '$1' + + ######### + # Yarvik Zania + # @ref: http://yarvik.com + ######### + - regex: '; *(?:Xenta |Luna )?(TAB[234][0-9]{2}|TAB0[78]-\d{3}|TAB0?9-\d{3}|TAB1[03]-\d{3}|SMP\d{2}-\d{3}) Build/' + device_replacement: 'Yarvik $1' + brand_replacement: 'Yarvik' + model_replacement: '$1' + + ######### + # Yifang + # @note: Needs to be at the very last as manufacturer builds for other brands. + # @ref: http://www.yifangdigital.com/ + # @models: M1010, M1011, M1007, M1008, M1005, M899, M899LP, M909, M8000, + # M8001, M8002, M8003, M849, M815, M816, M819, M805, M878, M780LPW, + # M778, M7000, M7000AD, M7000NBD, M7001, M7002, M7002KBD, M777, M767, + # M789, M799, M769, M757, M755, M753, M752, M739, M729, M723, M712, M727 + ######### + - regex: '; *([A-Z]{2,4})(M\d{3,}[A-Z]{2})([^;\)\/]*)(?: Build|[;\)])' + device_replacement: 'Yifang $1$2$3' + brand_replacement: 'Yifang' + model_replacement: '$2' + + ######### + # XiaoMi + # @ref: http://www.xiaomi.com/event/buyphone + ######### + - regex: '; *((MI|HM|MI-ONE|Redmi)[ -](NOTE |Note )?[^;/]*) (Build|MIUI)/' + device_replacement: 'XiaoMi $1' + brand_replacement: 'XiaoMi' + model_replacement: '$1' + + ######### + # Xolo + # @ref: http://www.xolo.in/ + ######### + - regex: '; *XOLO[ _]([^;/]*tab.*) Build' + regex_flag: 'i' + device_replacement: 'Xolo $1' + brand_replacement: 'Xolo' + model_replacement: '$1' + - regex: '; *XOLO[ _]([^;/]+) Build' + regex_flag: 'i' + device_replacement: 'Xolo $1' + brand_replacement: 'Xolo' + model_replacement: '$1' + - regex: '; *(q\d0{2,3}[a-z]?) Build' + regex_flag: 'i' + device_replacement: 'Xolo $1' + brand_replacement: 'Xolo' + model_replacement: '$1' + + ######### + # Xoro + # @ref: http://www.xoro.de/produkte/ + ######### + - regex: '; *(PAD ?[79]\d+[^;/]*|TelePAD\d+[^;/]) Build' + device_replacement: 'Xoro $1' + brand_replacement: 'Xoro' + model_replacement: '$1' + + ######### + # Zopo + # @ref: http://www.zopomobiles.com/products.html + ######### + - regex: '; *(?:(?:ZOPO|Zopo)[ _]([^;/]+)|(ZP ?(?:\d{2}[^;/]+|C2))|(C[2379])) Build' + device_replacement: '$1$2$3' + brand_replacement: 'Zopo' + model_replacement: '$1$2$3' + + ######### + # ZiiLabs + # @ref: http://www.ziilabs.com/products/platforms/androidreferencetablets.php + ######### + - regex: '; *(ZiiLABS) (Zii[^;/]*) Build' + device_replacement: '$1 $2' + brand_replacement: 'ZiiLabs' + model_replacement: '$2' + - regex: '; *(Zii)_([^;/]*) Build' + device_replacement: '$1 $2' + brand_replacement: 'ZiiLabs' + model_replacement: '$2' + + ######### + # ZTE + # @ref: http://www.ztedevices.com/ + ######### + - regex: '; *(ARIZONA|(?:ATLAS|Atlas) W|D930|Grand (?:[SX][^;]*|Era|Memo[^;]*)|JOE|(?:Kis|KIS)\b[^;]*|Libra|Light [^;]*|N8[056][01]|N850L|N8000|N9[15]\d{2}|N9810|NX501|Optik|(?:Vip )Racer[^;]*|RacerII|RACERII|San Francisco[^;]*|V9[AC]|V55|V881|Z[679][0-9]{2}[A-z]?) Build' + device_replacement: '$1' + brand_replacement: 'ZTE' + model_replacement: '$1' + - regex: '; *([A-Z]\d+)_USA_[^;]* Build' + device_replacement: '$1' + brand_replacement: 'ZTE' + model_replacement: '$1' + - regex: '; *(SmartTab\d+)[^;]* Build' + device_replacement: '$1' + brand_replacement: 'ZTE' + model_replacement: '$1' + - regex: '; *(?:Blade|BLADE|ZTE-BLADE)([^;/]*) Build' + device_replacement: 'ZTE Blade$1' + brand_replacement: 'ZTE' + model_replacement: 'Blade$1' + - regex: '; *(?:Skate|SKATE|ZTE-SKATE)([^;/]*) Build' + device_replacement: 'ZTE Skate$1' + brand_replacement: 'ZTE' + model_replacement: 'Skate$1' + - regex: '; *(Orange |Optimus )(Monte Carlo|San Francisco) Build' + device_replacement: '$1$2' + brand_replacement: 'ZTE' + model_replacement: '$1$2' + - regex: '; *(?:ZXY-ZTE_|ZTE\-U |ZTE[\- _]|ZTE-C[_ ])([^;/]+) Build' + device_replacement: 'ZTE $1' + brand_replacement: 'ZTE' + model_replacement: '$1' + # operator specific + - regex: '; (BASE) (lutea|Lutea 2|Tab[^;]*) Build' + device_replacement: '$1 $2' + brand_replacement: 'ZTE' + model_replacement: '$1 $2' + - regex: '; (Avea inTouch 2|soft stone|tmn smart a7|Movistar[ _]Link) Build' + regex_flag: 'i' + device_replacement: '$1' + brand_replacement: 'ZTE' + model_replacement: '$1' + - regex: '; *(vp9plus)\)' + device_replacement: '$1' + brand_replacement: 'ZTE' + model_replacement: '$1' + + ########## + # Zync + # @ref: http://www.zync.in/index.php/our-products/tablet-phablets + ########## + - regex: '; ?(Cloud[ _]Z5|z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900) Build/' + device_replacement: '$1' + brand_replacement: 'Zync' + model_replacement: '$1' + + ########## + # Kindle + # @note: Needs to be after Sony Playstation Vita as this UA contains Silk/3.2 + # @ref: https://developer.amazon.com/sdk/fire/specifications.html + # @ref: http://amazonsilk.wordpress.com/useful-bits/silk-user-agent/ + ########## + - regex: '; ?(KFOT|Kindle Fire) Build\b' + device_replacement: 'Kindle Fire' + brand_replacement: 'Amazon' + model_replacement: 'Kindle Fire' + - regex: '; ?(KFOTE|Amazon Kindle Fire2) Build\b' + device_replacement: 'Kindle Fire 2' + brand_replacement: 'Amazon' + model_replacement: 'Kindle Fire 2' + - regex: '; ?(KFTT) Build\b' + device_replacement: 'Kindle Fire HD' + brand_replacement: 'Amazon' + model_replacement: 'Kindle Fire HD 7"' + - regex: '; ?(KFJWI) Build\b' + device_replacement: 'Kindle Fire HD 8.9" WiFi' + brand_replacement: 'Amazon' + model_replacement: 'Kindle Fire HD 8.9" WiFi' + - regex: '; ?(KFJWA) Build\b' + device_replacement: 'Kindle Fire HD 8.9" 4G' + brand_replacement: 'Amazon' + model_replacement: 'Kindle Fire HD 8.9" 4G' + - regex: '; ?(KFSOWI) Build\b' + device_replacement: 'Kindle Fire HD 7" WiFi' + brand_replacement: 'Amazon' + model_replacement: 'Kindle Fire HD 7" WiFi' + - regex: '; ?(KFTHWI) Build\b' + device_replacement: 'Kindle Fire HDX 7" WiFi' + brand_replacement: 'Amazon' + model_replacement: 'Kindle Fire HDX 7" WiFi' + - regex: '; ?(KFTHWA) Build\b' + device_replacement: 'Kindle Fire HDX 7" 4G' + brand_replacement: 'Amazon' + model_replacement: 'Kindle Fire HDX 7" 4G' + - regex: '; ?(KFAPWI) Build\b' + device_replacement: 'Kindle Fire HDX 8.9" WiFi' + brand_replacement: 'Amazon' + model_replacement: 'Kindle Fire HDX 8.9" WiFi' + - regex: '; ?(KFAPWA) Build\b' + device_replacement: 'Kindle Fire HDX 8.9" 4G' + brand_replacement: 'Amazon' + model_replacement: 'Kindle Fire HDX 8.9" 4G' + - regex: '; ?Amazon ([^;/]+) Build\b' + device_replacement: '$1' + brand_replacement: 'Amazon' + model_replacement: '$1' + - regex: '; ?(Kindle) Build\b' + device_replacement: 'Kindle' + brand_replacement: 'Amazon' + model_replacement: 'Kindle' + - regex: '; ?(Silk)/(\d+)\.(\d+)(?:\.([0-9\-]+))? Build\b' + device_replacement: 'Kindle Fire' + brand_replacement: 'Amazon' + model_replacement: 'Kindle Fire$2' + - regex: ' (Kindle)/(\d+\.\d+)' + device_replacement: 'Kindle' + brand_replacement: 'Amazon' + model_replacement: '$1 $2' + - regex: ' (Silk|Kindle)/(\d+)\.' + device_replacement: 'Kindle' + brand_replacement: 'Amazon' + model_replacement: 'Kindle' + + ######### + # Devices from chinese manufacturer(s) + # @note: identified by x-wap-profile http://218.249.47.94/Xianghe/.* + ######### + - regex: '(sprd)\-([^/]+)/' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + # @ref: http://eshinechina.en.alibaba.com/ + - regex: '; *(H\d{2}00\+?) Build' + device_replacement: '$1' + brand_replacement: 'Hero' + model_replacement: '$1' + - regex: '; *(iphone|iPhone5) Build/' + device_replacement: 'Xianghe $1' + brand_replacement: 'Xianghe' + model_replacement: '$1' + - regex: '; *(e\d{4}[a-z]?_?v\d+|v89_[^;/]+)[^;/]+ Build/' + device_replacement: 'Xianghe $1' + brand_replacement: 'Xianghe' + model_replacement: '$1' + + ######### + # Cellular + # @ref: + # @note: Operator branded devices + ######### + - regex: '\bUSCC[_\-]?([^ ;/\)]+)' + device_replacement: '$1' + brand_replacement: 'Cellular' + model_replacement: '$1' + + ###################################################################### + # Windows Phone Parsers + ###################################################################### + + ######### + # Alcatel Windows Phones + ######### + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?)?(?:ALCATEL)[^;]*; *([^;,\)]+)' + device_replacement: 'Alcatel $1' + brand_replacement: 'Alcatel' + model_replacement: '$1' + + ######### + # Asus Windows Phones + ######### + #~ - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?)?(?:ASUS|Asus)[^;]*; *([^;,\)]+)' + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?)?(?:ASUS|Asus)[^;]*; *([^;,\)]+)' + device_replacement: 'Asus $1' + brand_replacement: 'Asus' + model_replacement: '$1' + + ######### + # Dell Windows Phones + ######### + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?)?(?:DELL|Dell)[^;]*; *([^;,\)]+)' + device_replacement: 'Dell $1' + brand_replacement: 'Dell' + model_replacement: '$1' + + ######### + # HTC Windows Phones + ######### + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?)?(?:HTC|Htc|HTC_blocked[^;]*)[^;]*; *(?:HTC)?([^;,\)]+)' + device_replacement: 'HTC $1' + brand_replacement: 'HTC' + model_replacement: '$1' + + ######### + # Huawei Windows Phones + ######### + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?)?(?:HUAWEI)[^;]*; *(?:HUAWEI )?([^;,\)]+)' + device_replacement: 'Huawei $1' + brand_replacement: 'Huawei' + model_replacement: '$1' + + ######### + # LG Windows Phones + ######### + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?)?(?:LG|Lg)[^;]*; *(?:LG[ \-])?([^;,\)]+)' + device_replacement: 'LG $1' + brand_replacement: 'LG' + model_replacement: '$1' + + ######### + # Noka Windows Phones + ######### + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?)?(?:NOKIA|Nokia)[^;]*; *(?:NOKIA ?|Nokia ?|LUMIA ?|[Ll]umia ?)*(\d{3,}[^;\)]*)' + device_replacement: 'Lumia $1' + brand_replacement: 'Nokia' + model_replacement: 'Lumia $1' + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?)?(?:NOKIA|Nokia)[^;]*; *(RM-\d{3,})' + device_replacement: 'Nokia $1' + brand_replacement: 'Nokia' + model_replacement: '$1' + - regex: '(?:Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)]|WPDesktop;) ?(?:ARM; ?Touch; ?|Touch; ?)?(?:NOKIA|Nokia)[^;]*; *(?:NOKIA ?|Nokia ?|LUMIA ?|[Ll]umia ?)*([^;\)]+)' + device_replacement: 'Nokia $1' + brand_replacement: 'Nokia' + model_replacement: '$1' + + ######### + # Microsoft Windows Phones + ######### + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?)?(?:Microsoft(?: Corporation)?)[^;]*; *([^;,\)]+)' + device_replacement: 'Microsoft $1' + brand_replacement: 'Microsoft' + model_replacement: '$1' + + ######### + # Samsung Windows Phones + ######### + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?)?(?:SAMSUNG)[^;]*; *(?:SAMSUNG )?([^;,\.\)]+)' + device_replacement: 'Samsung $1' + brand_replacement: 'Samsung' + model_replacement: '$1' + + ######### + # Toshiba Windows Phones + ######### + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?)?(?:TOSHIBA|FujitsuToshibaMobileCommun)[^;]*; *([^;,\)]+)' + device_replacement: 'Toshiba $1' + brand_replacement: 'Toshiba' + model_replacement: '$1' + + ######### + # Generic Windows Phones + ######### + - regex: 'Windows Phone [^;]+; .*?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?)?([^;]+); *([^;,\)]+)' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + + ###################################################################### + # Other Devices Parser + ###################################################################### + + ######### + # Samsung Bada Phones + ######### + - regex: '(?:^|; )SAMSUNG\-([A-Za-z0-9\-]+).* Bada/' + device_replacement: 'Samsung $1' + brand_replacement: 'Samsung' + model_replacement: '$1' + + ######### + # Firefox OS + ######### + - regex: '\(Mobile; ALCATEL ?(One|ONE) ?(Touch|TOUCH) ?([^;/]+)(?:/[^;]+)?; rv:[^\)]+\) Gecko/[^\/]+ Firefox/' + device_replacement: 'Alcatel $1 $2 $3' + brand_replacement: 'Alcatel' + model_replacement: 'One Touch $3' + - regex: '\(Mobile; (?:ZTE([^;]+)|(OpenC)); rv:[^\)]+\) Gecko/[^\/]+ Firefox/' + device_replacement: 'ZTE $1$2' + brand_replacement: 'ZTE' + model_replacement: '$1$2' + + ########## + # NOKIA + # @note: NokiaN8-00 comes before iphone. Sometimes spoofs iphone + ########## + - regex: 'Nokia(N[0-9]+)([A-z_\-][A-z0-9_\-]*)' + device_replacement: 'Nokia $1' + brand_replacement: 'Nokia' + model_replacement: '$1$2' + - regex: '(?:NOKIA|Nokia)(?:\-| *)(?:([A-Za-z0-9]+)\-[0-9a-f]{32}|([A-Za-z0-9\-]+)(?:UCBrowser)|([A-Za-z0-9\-]+))' + device_replacement: 'Nokia $1$2$3' + brand_replacement: 'Nokia' + model_replacement: '$1$2$3' + - regex: 'Lumia ([A-Za-z0-9\-]+)' + device_replacement: 'Lumia $1' + brand_replacement: 'Nokia' + model_replacement: 'Lumia $1' + # UCWEB Browser on Symbian + - regex: '\(Symbian; U; S60 V5; [A-z]{2}\-[A-z]{2}; (SonyEricsson|Samsung|Nokia|LG)([^;/]+)\)' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + # Nokia Symbian + - regex: '\(Symbian(?:/3)?; U; ([^;]+);' + device_replacement: 'Nokia $1' + brand_replacement: 'Nokia' + model_replacement: '$1' + + ########## + # BlackBerry + # @ref: http://www.useragentstring.com/pages/BlackBerry/ + ########## + - regex: 'BB10; ([A-Za-z0-9\- ]+)\)' + device_replacement: 'BlackBerry $1' + brand_replacement: 'BlackBerry' + model_replacement: '$1' + - regex: 'Play[Bb]ook.+RIM Tablet OS' + device_replacement: 'BlackBerry Playbook' + brand_replacement: 'BlackBerry' + model_replacement: 'Playbook' + - regex: 'Black[Bb]erry ([0-9]+);' + device_replacement: 'BlackBerry $1' + brand_replacement: 'BlackBerry' + model_replacement: '$1' + - regex: 'Black[Bb]erry([0-9]+)' + device_replacement: 'BlackBerry $1' + brand_replacement: 'BlackBerry' + model_replacement: '$1' + - regex: 'Black[Bb]erry;' + device_replacement: 'BlackBerry' + brand_replacement: 'BlackBerry' + + ########## + # PALM / HP + # @note: some palm devices must come before iphone. sometimes spoofs iphone in ua + ########## + - regex: '(Pre|Pixi)/\d+\.\d+' + device_replacement: 'Palm $1' + brand_replacement: 'Palm' + model_replacement: '$1' + - regex: 'Palm([0-9]+)' + device_replacement: 'Palm $1' + brand_replacement: 'Palm' + model_replacement: '$1' + - regex: 'Treo([A-Za-z0-9]+)' + device_replacement: 'Palm Treo $1' + brand_replacement: 'Palm' + model_replacement: 'Treo $1' + - regex: 'webOS.*(P160U(?:NA)?)/(\d+).(\d+)' + device_replacement: 'HP Veer' + brand_replacement: 'HP' + model_replacement: 'Veer' + - regex: '(Touch[Pp]ad)/\d+\.\d+' + device_replacement: 'HP TouchPad' + brand_replacement: 'HP' + model_replacement: 'TouchPad' + - regex: 'HPiPAQ([A-Za-z0-9]+)/\d+.\d+' + device_replacement: 'HP iPAQ $1' + brand_replacement: 'HP' + model_replacement: 'iPAQ $1' + - regex: 'PDA; (PalmOS)/sony/model ([a-z]+)/Revision' + device_replacement: '$1' + brand_replacement: 'Sony' + model_replacement: '$1 $2' + + ########## + # AppleTV + # No built in browser that I can tell + # Stack Overflow indicated iTunes-AppleTV/4.1 as a known UA for app available and I'm seeing it in live traffic + ########## + - regex: '(Apple\s?TV)' + device_replacement: 'AppleTV' + brand_replacement: 'Apple' + model_replacement: 'AppleTV' + + ######### + # Tesla Model S + ######### + - regex: '(QtCarBrowser)' + device_replacement: 'Tesla Model S' + brand_replacement: 'Tesla' + model_replacement: 'Model S' + + ########## + # iSTUFF + # @note: complete but probably catches spoofs + # ipad and ipod must be parsed before iphone + # cannot determine specific device type from ua string. (3g, 3gs, 4, etc) + ########## + # @note: on some ua the device can be identified e.g. iPhone5,1 + - regex: '((?:iPhone|iPad|iPod)\d+,\d+)' + device_replacement: '$1' + brand_replacement: 'Apple' + model_replacement: '$1' + # @note: iPad needs to be before iPhone + - regex: '(iPad)(?:;| Simulator;)' + device_replacement: '$1' + brand_replacement: 'Apple' + model_replacement: '$1' + - regex: '(iPod)(?:;| touch;| Simulator;)' + device_replacement: '$1' + brand_replacement: 'Apple' + model_replacement: '$1' + - regex: '(iPhone)(?:;| Simulator;)' + device_replacement: '$1' + brand_replacement: 'Apple' + model_replacement: '$1' + # @note: desktop applications show device info + - regex: 'CFNetwork/.* Darwin/\d.*\(((?:Mac|iMac|PowerMac|PowerBook)[^\d]*)(\d+)(?:,|%2C)(\d+)' + device_replacement: '$1$2,$3' + brand_replacement: 'Apple' + model_replacement: '$1$2,$3' + # @note: iOS applications do not show device info + - regex: 'CFNetwork/.* Darwin/\d' + device_replacement: 'iOS-Device' + brand_replacement: 'Apple' + model_replacement: 'iOS-Device' + + ########## + # Acer + ########## + - regex: 'acer_([A-Za-z0-9]+)_' + device_replacement: 'Acer $1' + brand_replacement: 'Acer' + model_replacement: '$1' + + ########## + # Alcatel + ########## + - regex: '(?:ALCATEL|Alcatel)-([A-Za-z0-9\-]+)' + device_replacement: 'Alcatel $1' + brand_replacement: 'Alcatel' + model_replacement: '$1' + + ########## + # Amoi + ########## + - regex: '(?:Amoi|AMOI)\-([A-Za-z0-9]+)' + device_replacement: 'Amoi $1' + brand_replacement: 'Amoi' + model_replacement: '$1' + + ########## + # Asus + ########## + - regex: '(?:; |\/|^)((?:Transformer (?:Pad|Prime) |Transformer |PadFone[ _]?)[A-Za-z0-9]*)' + device_replacement: 'Asus $1' + brand_replacement: 'Asus' + model_replacement: '$1' + - regex: '(?:asus.*?ASUS|Asus|ASUS|asus)[\- ;]*((?:Transformer (?:Pad|Prime) |Transformer |Padfone |Nexus[ _])?[A-Za-z0-9]+)' + device_replacement: 'Asus $1' + brand_replacement: 'Asus' + model_replacement: '$1' + + + ########## + # Bird + ########## + - regex: '\bBIRD[ \-\.]([A-Za-z0-9]+)' + device_replacement: 'Bird $1' + brand_replacement: 'Bird' + model_replacement: '$1' + + ########## + # Dell + ########## + - regex: '\bDell ([A-Za-z0-9]+)' + device_replacement: 'Dell $1' + brand_replacement: 'Dell' + model_replacement: '$1' + + ########## + # DoCoMo + ########## + - regex: 'DoCoMo/2\.0 ([A-Za-z0-9]+)' + device_replacement: 'DoCoMo $1' + brand_replacement: 'DoCoMo' + model_replacement: '$1' + - regex: '([A-Za-z0-9]+)_W;FOMA' + device_replacement: 'DoCoMo $1' + brand_replacement: 'DoCoMo' + model_replacement: '$1' + - regex: '([A-Za-z0-9]+);FOMA' + device_replacement: 'DoCoMo $1' + brand_replacement: 'DoCoMo' + model_replacement: '$1' + + ########## + # htc + ########## + - regex: '\b(?:HTC/|HTC/[a-z0-9]+/)?HTC[ _\-;]? *(.*?)(?:-?Mozilla|fingerPrint|[;/\(\)]|$)' + device_replacement: 'HTC $1' + brand_replacement: 'HTC' + model_replacement: '$1' + + ########## + # Huawei + ########## + - regex: 'Huawei([A-Za-z0-9]+)' + device_replacement: 'Huawei $1' + brand_replacement: 'Huawei' + model_replacement: '$1' + - regex: 'HUAWEI-([A-Za-z0-9]+)' + device_replacement: 'Huawei $1' + brand_replacement: 'Huawei' + model_replacement: '$1' + - regex: 'vodafone([A-Za-z0-9]+)' + device_replacement: 'Huawei Vodafone $1' + brand_replacement: 'Huawei' + model_replacement: 'Vodafone $1' + + ########## + # i-mate + ########## + - regex: 'i\-mate ([A-Za-z0-9]+)' + device_replacement: 'i-mate $1' + brand_replacement: 'i-mate' + model_replacement: '$1' + + ########## + # kyocera + ########## + - regex: 'Kyocera\-([A-Za-z0-9]+)' + device_replacement: 'Kyocera $1' + brand_replacement: 'Kyocera' + model_replacement: '$1' + - regex: 'KWC\-([A-Za-z0-9]+)' + device_replacement: 'Kyocera $1' + brand_replacement: 'Kyocera' + model_replacement: '$1' + + ########## + # lenovo + ########## + - regex: 'Lenovo[_\-]([A-Za-z0-9]+)' + device_replacement: 'Lenovo $1' + brand_replacement: 'Lenovo' + model_replacement: '$1' + + ########## + # HbbTV (European and Australian standard) + # written before the LG regexes, as LG is making HbbTV too + ########## + - regex: '(HbbTV)/[0-9]+\.[0-9]+\.[0-9]+ \([^;]*; *(LG)E *; *([^;]*) *;[^;]*;[^;]*;\)' + device_replacement: '$1' + brand_replacement: '$2' + model_replacement: '$3' + - regex: '(HbbTV)/1\.1\.1.*CE-HTML/1\.\d;(Vendor/)*(THOM[^;]*?)[;\s](?:.*SW-Version/.*)*(LF[^;]+);?' + device_replacement: '$1' + brand_replacement: 'Thomson' + model_replacement: '$4' + - regex: '(HbbTV)(?:/1\.1\.1)?(?: ?\(;;;;;\))?; *CE-HTML(?:/1\.\d)?; *([^ ]+) ([^;]+);' + device_replacement: '$1' + brand_replacement: '$2' + model_replacement: '$3' + - regex: '(HbbTV)/1\.1\.1 \(;;;;;\) Maple_2011' + device_replacement: '$1' + brand_replacement: 'Samsung' + - regex: '(HbbTV)/[0-9]+\.[0-9]+\.[0-9]+ \([^;]*; *(?:CUS:([^;]*)|([^;]+)) *; *([^;]*) *;.*;' + device_replacement: '$1' + brand_replacement: '$2$3' + model_replacement: '$4' + - regex: '(HbbTV)/[0-9]+\.[0-9]+\.[0-9]+' + device_replacement: '$1' + + ########## + # LGE NetCast TV + ########## + - regex: 'LGE; (?:Media\/)?([^;]*);[^;]*;[^;]*;?\); "?LG NetCast(\.TV|\.Media|)-\d+' + device_replacement: 'NetCast$2' + brand_replacement: 'LG' + model_replacement: '$1' + + ########## + # InettvBrowser + ########## + - regex: 'InettvBrowser/[0-9]+\.[0-9A-Z]+ \([^;]*;(Sony)([^;]*);[^;]*;[^\)]*\)' + device_replacement: 'Inettv' + brand_replacement: '$1' + model_replacement: '$2' + - regex: 'InettvBrowser/[0-9]+\.[0-9A-Z]+ \([^;]*;([^;]*);[^;]*;[^\)]*\)' + device_replacement: 'Inettv' + brand_replacement: 'Generic_Inettv' + model_replacement: '$1' + - regex: '(?:InettvBrowser|TSBNetTV|NETTV|HBBTV)' + device_replacement: 'Inettv' + brand_replacement: 'Generic_Inettv' + + ########## + # lg + ########## + # LG Symbian Phones + - regex: 'Series60/\d\.\d (LG)[\-]?([A-Za-z0-9 \-]+)' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + # other LG phones + - regex: '\b(?:LGE[ \-]LG\-(?:AX)?|LGE |LGE?-LG|LGE?[ \-]|LG[ /\-]|lg[\-])([A-Za-z0-9]+)\b' + device_replacement: 'LG $1' + brand_replacement: 'LG' + model_replacement: '$1' + - regex: '(?:^LG[\-]?|^LGE[\-/]?)([A-Za-z]+[0-9]+[A-Za-z]*)' + device_replacement: 'LG $1' + brand_replacement: 'LG' + model_replacement: '$1' + - regex: '^LG([0-9]+[A-Za-z]*)' + device_replacement: 'LG $1' + brand_replacement: 'LG' + model_replacement: '$1' + + ########## + # microsoft + ########## + - regex: '(KIN\.[^ ]+) (\d+)\.(\d+)' + device_replacement: 'Microsoft $1' + brand_replacement: 'Microsoft' + model_replacement: '$1' + - regex: '(?:MSIE|XBMC).*\b(Xbox)\b' + device_replacement: '$1' + brand_replacement: 'Microsoft' + model_replacement: '$1' + - regex: '; ARM; Trident/6\.0; Touch[\);]' + device_replacement: 'Microsoft Surface RT' + brand_replacement: 'Microsoft' + model_replacement: 'Surface RT' + + ########## + # motorola + ########## + - regex: 'Motorola\-([A-Za-z0-9]+)' + device_replacement: 'Motorola $1' + brand_replacement: 'Motorola' + model_replacement: '$1' + - regex: 'MOTO\-([A-Za-z0-9]+)' + device_replacement: 'Motorola $1' + brand_replacement: 'Motorola' + model_replacement: '$1' + - regex: 'MOT\-([A-z0-9][A-z0-9\-]*)' + device_replacement: 'Motorola $1' + brand_replacement: 'Motorola' + model_replacement: '$1' + + ########## + # nintendo + ########## + - regex: 'Nintendo WiiU' + device_replacement: 'Nintendo Wii U' + brand_replacement: 'Nintendo' + model_replacement: 'Wii U' + - regex: 'Nintendo (DS|3DS|DSi|Wii);' + device_replacement: 'Nintendo $1' + brand_replacement: 'Nintendo' + model_replacement: '$1' + + ########## + # pantech + ########## + - regex: '(?:Pantech|PANTECH)[ _-]?([A-Za-z0-9\-]+)' + device_replacement: 'Pantech $1' + brand_replacement: 'Pantech' + model_replacement: '$1' + + ########## + # philips + ########## + - regex: 'Philips([A-Za-z0-9]+)' + device_replacement: 'Philips $1' + brand_replacement: 'Philips' + model_replacement: '$1' + - regex: 'Philips ([A-Za-z0-9]+)' + device_replacement: 'Philips $1' + brand_replacement: 'Philips' + model_replacement: '$1' + + ########## + # Samsung + ########## + # Samsung Symbian Devices + - regex: 'SymbianOS/9\.\d.* Samsung[/\-]([A-Za-z0-9 \-]+)' + device_replacement: 'Samsung $1' + brand_replacement: 'Samsung' + model_replacement: '$1' + - regex: '(Samsung)(SGH)(i[0-9]+)' + device_replacement: '$1 $2$3' + brand_replacement: '$1' + model_replacement: '$2-$3' + - regex: 'SAMSUNG-ANDROID-MMS/([^;/]+)' + device_replacement: '$1' + brand_replacement: 'Samsung' + model_replacement: '$1' + # Other Samsung + #- regex: 'SAMSUNG(?:; |-)([A-Za-z0-9\-]+)' + - regex: 'SAMSUNG(?:; |[ -/])([A-Za-z0-9\-]+)' + regex_flag: 'i' + device_replacement: 'Samsung $1' + brand_replacement: 'Samsung' + model_replacement: '$1' + + ########## + # Sega + ########## + - regex: '(Dreamcast)' + device_replacement: 'Sega $1' + brand_replacement: 'Sega' + model_replacement: '$1' + + ########## + # Siemens mobile + ########## + - regex: '^SIE-([A-Za-z0-9]+)' + device_replacement: 'Siemens $1' + brand_replacement: 'Siemens' + model_replacement: '$1' + + ########## + # Softbank + ########## + - regex: 'Softbank/[12]\.0/([A-Za-z0-9]+)' + device_replacement: 'Softbank $1' + brand_replacement: 'Softbank' + model_replacement: '$1' + + ########## + # SonyEricsson + ########## + - regex: 'SonyEricsson ?([A-Za-z0-9\-]+)' + device_replacement: 'Ericsson $1' + brand_replacement: 'SonyEricsson' + model_replacement: '$1' + + ########## + # Sony + ########## + - regex: 'Android [^;]+; ([^ ]+) (Sony)/' + device_replacement: '$2 $1' + brand_replacement: '$2' + model_replacement: '$1' + - regex: '(Sony)(?:BDP\/|\/)?([^ /;\)]+)[ /;\)]' + device_replacement: '$1 $2' + brand_replacement: '$1' + model_replacement: '$2' + + ######### + # Puffin Browser Device detect + # A=Android, I=iOS, P=Phone, T=Tablet + # AT=Android+Tablet + ######### + - regex: 'Puffin/[\d\.]+IT' + device_replacement: 'iPad' + brand_replacement: 'Apple' + model_replacement: 'iPad' + - regex: 'Puffin/[\d\.]+IP' + device_replacement: 'iPhone' + brand_replacement: 'Apple' + model_replacement: 'iPhone' + - regex: 'Puffin/[\d\.]+AT' + device_replacement: 'Generic Tablet' + brand_replacement: 'Generic' + model_replacement: 'Tablet' + - regex: 'Puffin/[\d\.]+AP' + device_replacement: 'Generic Smartphone' + brand_replacement: 'Generic' + model_replacement: 'Smartphone' + + ######### + # Android General Device Matching (far from perfect) + ######### + - regex: 'Android[\- ][\d]+\.[\d]+; [A-Za-z]{2}\-[A-Za-z]{0,2}; WOWMobile (.+) Build' + brand_replacement: 'Generic_Android' + model_replacement: '$1' + - regex: 'Android[\- ][\d]+\.[\d]+\-update1; [A-Za-z]{2}\-[A-Za-z]{0,2} *; *(.+?) Build' + brand_replacement: 'Generic_Android' + model_replacement: '$1' + - regex: 'Android[\- ][\d]+(?:\.[\d]+){1,2}; *[A-Za-z]{2}[_\-][A-Za-z]{0,2}\-? *; *(.+?) Build' + brand_replacement: 'Generic_Android' + model_replacement: '$1' + - regex: 'Android[\- ][\d]+(?:\.[\d]+){1,2}; *[A-Za-z]{0,2}\- *; *(.+?) Build' + brand_replacement: 'Generic_Android' + model_replacement: '$1' + # No build info at all - "Build" follows locale immediately + - regex: 'Android[\- ][\d]+(?:\.[\d]+){1,2}; *[a-z]{0,2}[_\-]?[A-Za-z]{0,2};? Build' + device_replacement: 'Generic Smartphone' + brand_replacement: 'Generic' + model_replacement: 'Smartphone' + - regex: 'Android[\- ][\d]+(?:\.[\d]+){1,2}; *\-?[A-Za-z]{2}; *(.+?) Build' + brand_replacement: 'Generic_Android' + model_replacement: '$1' + - regex: 'Android[\- ][\d]+(?:\.[\d]+){1,2}(?:;.*)?; *(.+?) Build' + brand_replacement: 'Generic_Android' + model_replacement: '$1' + + ########## + # Google TV + ########## + - regex: '(GoogleTV)' + brand_replacement: 'Generic_Inettv' + model_replacement: '$1' + + ########## + # WebTV + ########## + - regex: '(WebTV)/\d+.\d+' + brand_replacement: 'Generic_Inettv' + model_replacement: '$1' + # Roku Digital-Video-Players https://www.roku.com/ + - regex: '^(Roku)/DVP-\d+\.\d+' + brand_replacement: 'Generic_Inettv' + model_replacement: '$1' + + ########## + # Generic Tablet + ########## + - regex: '(Android 3\.\d|Opera Tablet|Tablet; .+Firefox/|Android.*(?:Tab|Pad))' + regex_flag: 'i' + device_replacement: 'Generic Tablet' + brand_replacement: 'Generic' + model_replacement: 'Tablet' + + ########## + # Generic Smart Phone + ########## + - regex: '(Symbian|\bS60(Version|V\d)|\bS60\b|\((Series 60|Windows Mobile|Palm OS|Bada); Opera Mini|Windows CE|Opera Mobi|BREW|Brew|Mobile; .+Firefox/|iPhone OS|Android|MobileSafari|Windows *Phone|\(webOS/|PalmOS)' + device_replacement: 'Generic Smartphone' + brand_replacement: 'Generic' + model_replacement: 'Smartphone' + - regex: '(hiptop|avantgo|plucker|xiino|blazer|elaine)' + regex_flag: 'i' + device_replacement: 'Generic Smartphone' + brand_replacement: 'Generic' + model_replacement: 'Smartphone' + + ########## + # Spiders (this is hack...) + ########## + - regex: '(bot|zao|borg|DBot|oegp|silk|Xenu|zeal|^NING|CCBot|crawl|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|^Java/|^JNLP/|Daumoa|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|spider|msnbot|msrbot|vortex|^vortex|crawler|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|bingbot|openbot|gigabot|furlbot|polybot|seekbot|^voyager|archiver|Icarus6j|mogimogi|Netvibes|blitzbot|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|SeznamBot|ProoXiBot|wsr\-agent|Squrl Java|EtaoSpider|PaperLiBot|SputnikBot|A6\-Indexer|netresearch|searchsight|baiduspider|YisouSpider|ICC\-Crawler|http%20client|Python-urllib|dataparksearch|converacrawler|Screaming Frog|AppEngine-Google|YahooCacheSystem|fast\-webcrawler|Sogou Pic Spider|semanticdiscovery|Innovazion Crawler|facebookexternalhit|Google.*/\+/web/snippet|Google-HTTP-Java-Client|BlogBridge|IlTrovatore-Setaccio|InternetArchive|GomezAgent|WebThumbnail|heritrix|NewsGator|PagePeeker|Reaper|ZooShot|holmes)' + regex_flag: 'i' + device_replacement: 'Spider' + brand_replacement: 'Spider' + model_replacement: 'Desktop' + + ########## + # Generic Feature Phone + # take care to do case insensitive matching + ########## + - regex: '^(1207|3gso|4thp|501i|502i|503i|504i|505i|506i|6310|6590|770s|802s|a wa|acer|acs\-|airn|alav|asus|attw|au\-m|aur |aus |abac|acoo|aiko|alco|alca|amoi|anex|anny|anyw|aptu|arch|argo|bmobile|bell|bird|bw\-n|bw\-u|beck|benq|bilb|blac|c55/|cdm\-|chtm|capi|comp|cond|dall|dbte|dc\-s|dica|ds\-d|ds12|dait|devi|dmob|doco|dopo|dorado|el(?:38|39|48|49|50|55|58|68)|el[3456]\d{2}dual|erk0|esl8|ex300|ez40|ez60|ez70|ezos|ezze|elai|emul|eric|ezwa|fake|fly\-|fly_|g\-mo|g1 u|g560|gf\-5|grun|gene|go.w|good|grad|hcit|hd\-m|hd\-p|hd\-t|hei\-|hp i|hpip|hs\-c|htc |htc\-|htca|htcg)' + regex_flag: 'i' + device_replacement: 'Generic Feature Phone' + brand_replacement: 'Generic' + model_replacement: 'Feature Phone' + - regex: '^(htcp|htcs|htct|htc_|haie|hita|huaw|hutc|i\-20|i\-go|i\-ma|i\-mobile|i230|iac|iac\-|iac/|ig01|im1k|inno|iris|jata|kddi|kgt|kgt/|kpt |kwc\-|klon|lexi|lg g|lg\-a|lg\-b|lg\-c|lg\-d|lg\-f|lg\-g|lg\-k|lg\-l|lg\-m|lg\-o|lg\-p|lg\-s|lg\-t|lg\-u|lg\-w|lg/k|lg/l|lg/u|lg50|lg54|lge\-|lge/|leno|m1\-w|m3ga|m50/|maui|mc01|mc21|mcca|medi|meri|mio8|mioa|mo01|mo02|mode|modo|mot |mot\-|mt50|mtp1|mtv |mate|maxo|merc|mits|mobi|motv|mozz|n100|n101|n102|n202|n203|n300|n302|n500|n502|n505|n700|n701|n710|nec\-|nem\-|newg|neon)' + regex_flag: 'i' + device_replacement: 'Generic Feature Phone' + brand_replacement: 'Generic' + model_replacement: 'Feature Phone' + - regex: '^(netf|noki|nzph|o2 x|o2\-x|opwv|owg1|opti|oran|ot\-s|p800|pand|pg\-1|pg\-2|pg\-3|pg\-6|pg\-8|pg\-c|pg13|phil|pn\-2|pt\-g|palm|pana|pire|pock|pose|psio|qa\-a|qc\-2|qc\-3|qc\-5|qc\-7|qc07|qc12|qc21|qc32|qc60|qci\-|qwap|qtek|r380|r600|raks|rim9|rove|s55/|sage|sams|sc01|sch\-|scp\-|sdk/|se47|sec\-|sec0|sec1|semc|sgh\-|shar|sie\-|sk\-0|sl45|slid|smb3|smt5|sp01|sph\-|spv |spv\-|sy01|samm|sany|sava|scoo|send|siem|smar|smit|soft|sony|t\-mo|t218|t250|t600|t610|t618|tcl\-|tdg\-|telm|tim\-|ts70|tsm\-|tsm3|tsm5|tx\-9|tagt)' + regex_flag: 'i' + device_replacement: 'Generic Feature Phone' + brand_replacement: 'Generic' + model_replacement: 'Feature Phone' + - regex: '^(talk|teli|topl|tosh|up.b|upg1|utst|v400|v750|veri|vk\-v|vk40|vk50|vk52|vk53|vm40|vx98|virg|vertu|vite|voda|vulc|w3c |w3c\-|wapj|wapp|wapu|wapm|wig |wapi|wapr|wapv|wapy|wapa|waps|wapt|winc|winw|wonu|x700|xda2|xdag|yas\-|your|zte\-|zeto|aste|audi|avan|blaz|brew|brvw|bumb|ccwa|cell|cldc|cmd\-|dang|eml2|fetc|hipt|http|ibro|idea|ikom|ipaq|jbro|jemu|jigs|keji|kyoc|kyok|libw|m\-cr|midp|mmef|moto|mwbp|mywa|newt|nok6|o2im|pant|pdxg|play|pluc|port|prox|rozo|sama|seri|smal|symb|treo|upsi|vx52|vx53|vx60|vx61|vx70|vx80|vx81|vx83|vx85|wap\-|webc|whit|wmlb|xda\-|xda_)' + regex_flag: 'i' + device_replacement: 'Generic Feature Phone' + brand_replacement: 'Generic' + model_replacement: 'Feature Phone' + - regex: '^(Ice)$' + device_replacement: 'Generic Feature Phone' + brand_replacement: 'Generic' + model_replacement: 'Feature Phone' + - regex: '(wap[\-\ ]browser|maui|netfront|obigo|teleca|up\.browser|midp|Opera Mini)' + regex_flag: 'i' + device_replacement: 'Generic Feature Phone' + brand_replacement: 'Generic' + model_replacement: 'Feature Phone' \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/App_GlobalResources/EditorLocalization.designer.cs b/src/Presentation/SmartStore.Web/App_GlobalResources/EditorLocalization.designer.cs index 8c5b956c13..1002132cd1 100644 --- a/src/Presentation/SmartStore.Web/App_GlobalResources/EditorLocalization.designer.cs +++ b/src/Presentation/SmartStore.Web/App_GlobalResources/EditorLocalization.designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.18444 +// Laufzeitversion:4.0.30319.34209 // // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn // der Code erneut generiert wird. diff --git a/src/Presentation/SmartStore.Web/App_GlobalResources/GridLocalization.designer.cs b/src/Presentation/SmartStore.Web/App_GlobalResources/GridLocalization.designer.cs index 9153314c68..4ad683bdb0 100644 --- a/src/Presentation/SmartStore.Web/App_GlobalResources/GridLocalization.designer.cs +++ b/src/Presentation/SmartStore.Web/App_GlobalResources/GridLocalization.designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.18444 +// Laufzeitversion:4.0.30319.34209 // // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn // der Code erneut generiert wird. diff --git a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.Designer.cs b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.Designer.cs index 7aedfffcc9..1eda45cf26 100644 --- a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.Designer.cs +++ b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34209 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -61,7 +61,7 @@ internal MvcLocalization() { } /// - /// Looks up a localized string similar to The field {0} must be a date.. + /// Looks up a localized string similar to The field '{0}' must be a date.. /// internal static string FieldMustBeDate { get { @@ -70,7 +70,7 @@ internal static string FieldMustBeDate { } /// - /// Looks up a localized string similar to The field {0} must be a number.. + /// Looks up a localized string similar to The field '{0}' must be a number.. /// internal static string FieldMustBeNumeric { get { @@ -79,7 +79,7 @@ internal static string FieldMustBeNumeric { } /// - /// Looks up a localized string similar to The value '{0}' is not valid for {1}.. + /// Looks up a localized string similar to The value '{0}' is not valid for '{1}'.. /// internal static string PropertyValueInvalid { get { @@ -95,5 +95,14 @@ internal static string PropertyValueRequired { return ResourceManager.GetString("PropertyValueRequired", resourceCulture); } } + + /// + /// Looks up a localized string similar to The field '{0}' is required.. + /// + internal static string Required { + get { + return ResourceManager.GetString("Required", resourceCulture); + } + } } } diff --git a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.de.resx b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.de.resx index e1d0fa764f..fee5893b3b 100644 --- a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.de.resx +++ b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.de.resx @@ -118,15 +118,18 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Das Feld {0} muss ein Datum sein. + Das Feld '{0}' muss ein Datum sein. - Das Feld {0} muss numerisch sein. + Das Feld '{0}' muss numerisch sein. - Der Wert '{0}' für {1} ist ungültig. + Der Wert '{0}' für '{1}' ist ungültig. Ein Wert ist zwingend erforderlich. + + Das Feld '{0}' ist erforderlich. + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.resx b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.resx index da4a85fa32..0e272fccd2 100644 --- a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.resx +++ b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.resx @@ -118,15 +118,18 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - The field {0} must be a date. + The field '{0}' must be a date. - The field {0} must be a number. + The field '{0}' must be a number. - The value '{0}' is not valid for {1}. + The value '{0}' is not valid for '{1}'. A value is required. + + The field '{0}' is required. + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000019_5_virtual_gift_card_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000019_5_virtual_gift_card_75.jpeg deleted file mode 100644 index b46a43618f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000019_5_virtual_gift_card_75.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card.jpeg deleted file mode 100644 index 231fcaca34..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_125.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_125.jpeg deleted file mode 100644 index 23b629526d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_125.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_75.jpeg deleted file mode 100644 index b46a43618f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_75.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000021_50_physical_gift_card_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000021_50_physical_gift_card_75.jpeg deleted file mode 100644 index b46a43618f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000021_50_physical_gift_card_75.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000022_100_physical_gift_card_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000022_100_physical_gift_card_75.jpeg deleted file mode 100644 index b46a43618f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000022_100_physical_gift_card_75.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000024_acer_aspire_one_89_mini_notebook_case_black_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000024_acer_aspire_one_89_mini_notebook_case_black_75.jpeg deleted file mode 100644 index 4b9ef163cf..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000024_acer_aspire_one_89_mini_notebook_case_black_75.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000026_adidas_womens_supernova_csh_7_running_shoe_75.jpg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000026_adidas_womens_supernova_csh_7_running_shoe_75.jpg deleted file mode 100644 index 72b7a3b370..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000026_adidas_womens_supernova_csh_7_running_shoe_75.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000028_adobe_photoshop_elements_7_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000028_adobe_photoshop_elements_7_75.jpeg deleted file mode 100644 index 6482f9153e..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000028_adobe_photoshop_elements_7_75.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000029_apc_back_ups_rs_800va_ups_800_va_ups_battery_lead_acid_br800blk__75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000029_apc_back_ups_rs_800va_ups_800_va_ups_battery_lead_acid_br800blk__75.jpeg deleted file mode 100644 index efb2e46f2c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000029_apc_back_ups_rs_800va_ups_800_va_ups_battery_lead_acid_br800blk__75.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000034_black_white_diamond_heart_75.jpg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000034_black_white_diamond_heart_75.jpg deleted file mode 100644 index feac89b5fb..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000034_black_white_diamond_heart_75.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000035_blackberry_bold_9000_phone_black_att_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000035_blackberry_bold_9000_phone_black_att_75.jpeg deleted file mode 100644 index ae48a960b3..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000035_blackberry_bold_9000_phone_black_att_75.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer.jpeg deleted file mode 100644 index af6447c685..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer_125.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer_125.jpeg deleted file mode 100644 index 41ddd6848a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer_125.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000052_etnies_mens_digit_sneaker.jpg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000052_etnies_mens_digit_sneaker.jpg deleted file mode 100644 index 67198f7d85..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000052_etnies_mens_digit_sneaker.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000052_etnies_mens_digit_sneaker_125.jpg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000052_etnies_mens_digit_sneaker_125.jpg deleted file mode 100644 index 80885934b0..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000052_etnies_mens_digit_sneaker_125.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/Images/uploaded/placeholder.txt b/src/Presentation/SmartStore.Web/Content/Images/uploaded/placeholder.txt deleted file mode 100644 index 5f282702bb..0000000000 --- a/src/Presentation/SmartStore.Web/Content/Images/uploaded/placeholder.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/alerts.less b/src/Presentation/SmartStore.Web/Content/bootstrap/alerts.less index 996514d9b9..bc7b04be96 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/alerts.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/alerts.less @@ -22,7 +22,7 @@ color: rgba(0,0,0, .85); margin-bottom: 10px; } -.alert a { +.alert a:not(.btn) { text-decoration: underline; } diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less index 4100e82a92..67aeef972b 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less @@ -211,6 +211,9 @@ span.field-validation-error { .btn-below { margin-top: 5px !important; } +.btn-right { + margin-left: 10px !important; +} // FORMS (GENERAL) @@ -386,7 +389,8 @@ th label { vertical-align: text-top; } -.label-smnet { +.label-smnet, +.label-inline { margin-right: 5px; } .label-smnet-hide { @@ -417,51 +421,6 @@ th label { } -/*.ui-menu { - padding: 5px 0; - margin: 2px 0 0; // override default ul - background-color: @dropdownBackground; - border: 1px solid #ccc; // Fallback for IE7-8 - border: 1px solid @dropdownBorder; - *border-right-width: 2px; - *border-bottom-width: 2px; - border-top: none; - .border-radius(6px); - .box-shadow(0 5px 10px rgba(0,0,0,.2)); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; -} - -.ui-autocomplete { - border-color: @inputBorder; // goes nice with textboxes - .box-shadow(0 5px 10px rgba(0,0,0,.12)); - border-top: none !important; -} -.ui-autocomplete.ui-corner-all { - .border-radius(0 0 @inputBorderRadius @inputBorderRadius); -} - -.ui-autocomplete-loading { - background-image: url('images/throbber-progress-black-16.gif'); - background-repeat: no-repeat; - background-position: 98% center; -} - -.ui-autocomplete .ui-menu-item a { - .border-radius(0); - padding: 3px 16px; - font-family: @baseFontFamily; - font-weight: normal; - color: @dropdownLinkColor; - .small(); - - &:hover, - &.ui-state-hover { - color: @dropdownLinkColorHover !important; - background: @dropdownLinkBackgroundHover !important; - } -}*/ // MVC UI adaptation // ------------------------- @@ -513,4 +472,99 @@ th label { .note-editable { background-color: #fff; +} + + +// NICER MODAL FADE +// ------------------------------ + +.modal.fade { + top: 50%; + .transition(~'opacity 0.12s linear, transform 0.12s ease-out'); + .transform(~'translate(0, -40px) scale(0.9, 0.9)'); +} + +.modal.fade.in { + .transform(none); +} + +.modal-backdrop, +.modal-backdrop.fade.in { + opacity: 0.6; +} + +.modal-header .close { + font-size: 30px; + text-shadow: none; +} + +.modal-large { + width: 800px !important; + margin: -250px 0 0 -400px !important; +} + + +// XLARGE MODAL +// ------------------------------ + +.modal-xlarge { + width: 960px !important; + margin: -250px 0 0 -480px !important; +} + + +// FLEX MODAL +// ------------------------------ + +.modal-flex { + top: 3% !important; + bottom: 3% !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + + &.fade.in { + display: -webkit-box !important; + display: -webkit-flex !important; + display: -ms-flexbox !important; + display: flex !important; + } + + .modal-body { + max-height: none; + overflow-y: auto; + overflow-x: hidden; + .display-flex(); + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + -webkit-box-align: stretch; + -webkit-align-items: stretch; + -ms-flex-align: stretch; + align-items: stretch; + -webkit-align-content: stretch; + -ms-flex-line-pack: stretch; + align-content: stretch; + } + + .modal-flex-fill-area { + //overflow: hidden; + min-height: 100%; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-black-16.gif b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-black-16.gif deleted file mode 100644 index edcef28713..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-black-16.gif and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-black-32.gif b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-black-32.gif deleted file mode 100644 index 9fafab009a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-black-32.gif and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-white-16.gif b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-white-16.gif deleted file mode 100644 index 152084b58c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-white-16.gif and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-white-32-alt.gif b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-white-32-alt.gif deleted file mode 100644 index f201c9a0a0..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-white-32-alt.gif and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-white-32.gif b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-white-32.gif deleted file mode 100644 index d663892719..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/images/throbber-progress-white-32.gif and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/mixins.less b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/mixins.less index 298d86b427..f0e9eb81ea 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/mixins.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/mixins.less @@ -140,10 +140,8 @@ @bg: ~'top, @{c1} 0%, @{c2} @{stop1}, @{c3} @{stop}, @{c4} 100%'; background-color: mix(@c2, @c3, 50%); background-image: -webkit-linear-gradient(@bg); - background-image: -o-linear-gradient(@bg); background-image: linear-gradient(@bg); background-repeat: repeat-x; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@c1),argb(@c4))); } } @@ -152,13 +150,11 @@ // Drop shadow extras .box-shadow(@shadow1, @shadow2) { - -webkit-box-shadow: @shadow1, @shadow2; - box-shadow: @shadow1, @shadow2; + box-shadow: @shadow1, @shadow2; } .box-shadow(@shadow1, @shadow2, @shadow3) { - -webkit-box-shadow: @shadow1, @shadow2, @shadow3; - box-shadow: @shadow1, @shadow2, @shadow3; + box-shadow: @shadow1, @shadow2, @shadow3; } @@ -171,13 +167,11 @@ .transition(@transition1, @transition2) { -webkit-transition: @transition1, @transition2; - -o-transition: @transition1, @transition2; transition: @transition1, @transition2; } .transition(@transition1, @transition2, @transition3) { -webkit-transition: @transition1, @transition2, @transition3; - -o-transition: @transition1, @transition2, @transition3; transition: @transition1, @transition2, @transition3; } @@ -186,7 +180,6 @@ .animation(@animation) { -webkit-animation: @animation; - -o-animation: @animation; animation: @animation; } @@ -194,7 +187,27 @@ .transform(@expr) { -webkit-transform: @expr; - -moz-transform: @expr; - -ms-transform: @expr; transform: @expr; +} + + +/* Flexbox Extras +----------------------------------------*/ + +.display-flex() { + display: -webkit-flex; + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} + +.flex-center() { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/spinner.less b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/spinner.less new file mode 100644 index 0000000000..660161e472 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/spinner.less @@ -0,0 +1,116 @@ +/* +SmartStore Circular Spinner +--------------------------- + Version: 1, + Timestamp: 10.02.2016 + Author: Murat Cakir +*/ + +.spinner-container { + // for centering the spinner + position: relative; + display: none; + .flex-center(); +} + +.spinner { + position: relative; + text-align: center; + vertical-align: middle; + display: none; +} + +.spinner-container.active > .spinner, +.spinner.active { + background-color: transparent; + border: none; + display: inline-block; +} + +.spinner.spinner-boxed { + border-radius: 50%; + padding: 0.2em; + background-color: #fff !important; + box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), + 0px 2px 2px 0px rgba(0, 0, 0, 0.14), + 0px 1px 5px 0px rgba(0, 0, 0, 0.12); +} + +.spinner-container.active { + .display-flex(); +} + +.spinner svg { + -webkit-transform-origin: 50% 50% 0; + transform-origin: 50% 50% 0; + + -webkit-animation: spinner-rotate 1.333s linear infinite; + animation: spinner-rotate 1.333s linear infinite; + + html.ie & { + animation: spinner-rotate-ie 2.5s linear infinite; + } +} + +.spinner circle { + fill: transparent; + stroke: #ff9800; + stroke-linecap: round; + stroke-dasharray: 200.96; + stroke-dashoffset: 58px; + + html.ie & { + stroke-dashoffset: 80px; + } + + -webkit-animation: spinner-dash 1.333s linear infinite, spinner-colors 10.644s linear infinite; + animation: spinner-dash 1.333s linear infinite, spinner-colors 10.644s linear infinite; +} + +@keyframes spinner-dash { + 0% { stroke-dashoffset: 58px; } + 50% { stroke-dashoffset: 200.96px; } + 100% { stroke-dashoffset: 58px; } +} +@-webkit-keyframes spinner-dash { + 0% { stroke-dashoffset: 58px; } + 50% { stroke-dashoffset: 200.96px; } + 100% { stroke-dashoffset: 58px; } +} + +@keyframes spinner-rotate { + 50% { transform: rotate(600deg); } + 100% { transform: rotate(720deg); } +} +@-webkit-keyframes spinner-rotate { + 50% { transform: rotate(600deg); } + 100% { transform: rotate(720deg); } +} +@keyframes spinner-rotate-ie { + 50% { transform: rotate(360deg); } + 100% { transform: rotate(720deg); } +} + +@keyframes spinner-colors { + 0% { stroke: #3F51B5; } + 20% { stroke: #09b7bf; } + 40% { stroke: #90d36b; } + 60% { stroke: #F44336; } + 80% { stroke: #f90; } + 100% { stroke: #3F51B5; } +} +@-webkit-keyframes spinner-colors { + 0% { stroke: #3F51B5; } + 20% { stroke: #09b7bf; } + 40% { stroke: #90d36b; } + 60% { stroke: #F44336; } + 80% { stroke: #f90; } + 100% { stroke: #3F51B5; } +} + +.spinner.white circle { + stroke: #fff; + -webkit-animation-name: spinner-dash, spinner-rotate; + animation-name: spinner-dash, spinner-rotate; +} + diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/telerik/telerik.smartstore.less b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/telerik/telerik.smartstore.less index 70a6c0dfa6..a0c24195c8 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/telerik/telerik.smartstore.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/telerik/telerik.smartstore.less @@ -17,10 +17,6 @@ .reset-filter(); color: @grayDark; text-shadow: 0 1px 1px rgba(255,255,255, .75); - - .no-cssgradients & { - background: #e9e9e9 /*@btnBackgroundHighlight*/ url('gradient.png') repeat-x 0 center; - } } .t-menu-vertical, .t-editor, .t-tooltip, .t-tabstrip { background-position: 0 -48px; @@ -89,7 +85,7 @@ color: #3b3b3b; } .t-button, button.t-button.t-state-disabled:hover, a.t-button.t-state-disabled:hover, .t-state-disabled .t-button:hover { - background: #d2d5d9 url('gradient.png') repeat-x 0 center; + background: #d2d5d9; } .t-button:hover { border-color: #f3d64a; @@ -153,7 +149,7 @@ } .t-numerictextbox .t-input { - width: 252px; + width: 301px; padding: 6px 8px 6px 3px; } .t-numerictextbox.small .t-input { @@ -230,4 +226,9 @@ background-color: #f7f7f7; } +.t-grouping-header { + text-shadow: none; + padding: 0.6em; +} + diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/throbber.less b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/throbber.less index 31aa8cdfbd..f579532a64 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/throbber.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/throbber.less @@ -5,65 +5,57 @@ Version: 1, Timestamp: 18.08.2012 .throbber { display: none; - .opacity(0); - - & .throbber-overlay { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - background-color: #000; - .opacity(70); - z-index: 2147483640; + opacity: 0; + overflow: hidden; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 2147483640; + background-color: rgba(0,0,0, 0.7); + + &.white { + background-color: rgba(255,255,255, 0.85); } - - &.white .throbber-overlay { - background-color: #fff; + + body > & { + position: fixed; } - - & .throbber-content { + + .throbber-flex { + width: 100%; + height: 100%; position: absolute; - z-index: 2147483640; - color: #fff; - text-shadow: 1px 1px 0 #666; - background: url('images/throbber-progress-white-32.gif') 4px 6px no-repeat; + .display-flex(); + .flex-center(); + + > * { + text-align: center; + } + } + + .throbber-content { + display: block; + overflow: hidden; + line-height: 1.3; + + &:not(:empty) { + margin-bottom: 30px; + } } - + &.large .throbber-content { - min-height: 38px; - line-height: 32px; - max-width: 400px; - font-size: 24px; - font-family: 'Segoe UI Light', 'Segoe UI', Tahoma, 'Helvetica Neue', Helvetica, Arial, 'sans-serif'; + font-size: 32px; font-weight: 100; - padding-left: 44px; } + &.small .throbber-content { - .text-overflow(); - height: 16px; - font-size: @baseFontSize; - line-height: @baseLineHeight; - vertical-align: middle; - padding-left: 24px; - background: url('images/throbber-progress-white-32.gif') 0 6px no-repeat; + font-size: 16px; + font-weight: 600; } - &.white .throbber-content { - color: #000; - text-shadow: none; - } - - &.large .throbber-content { background-image: url('images/throbber-progress-white-32.gif'); } - &.small .throbber-content { background-image: url('images/throbber-progress-white-16.gif'); background-position: 0 0; } - &.white.large .throbber-content { background-image: url('images/throbber-progress-black-32.gif'); } - &.white.small .throbber-content { background-image: url('images/throbber-progress-black-16.gif'); background-position: 0 0; } - - &.global .throbber-overlay { - position: fixed; - width: 100%; - height: 100%; - } - &.global .throbber-content { - position: fixed; + + &:not(.white) .throbber-content { + color: #fff; } -} \ No newline at end of file +} diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/dropdowns.less b/src/Presentation/SmartStore.Web/Content/bootstrap/dropdowns.less index 2e744829c6..e30e708e3d 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/dropdowns.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/dropdowns.less @@ -59,7 +59,6 @@ .box-shadow(0 2px 6px rgba(0,0,0,.15)); -webkit-background-clip: padding-box; - -moz-background-clip: padding; background-clip: padding-box; .dropdown-menu { diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/forms.less b/src/Presentation/SmartStore.Web/Content/bootstrap/forms.less index 50e2ad9aed..917cc2e396 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/forms.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/forms.less @@ -124,7 +124,7 @@ input[type="color"], .uneditable-input { background-color: @inputBackground; border: 1px solid @inputBorder; - .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); + //.box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Focus state &:focus { @@ -195,7 +195,7 @@ input[type="checkbox"]:focus { color: @grayLight; background-color: darken(@inputBackground, 1%); border-color: @inputBorder; - .box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); + //.box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); cursor: not-allowed; } @@ -385,8 +385,8 @@ select:focus:required:invalid { border-color: #ee5f5b; &:focus { border-color: darken(#ee5f5b, 10%); - @shadow: 0 0 6px lighten(#ee5f5b, 20%); - .box-shadow(@shadow); + //@shadow: 0 0 6px lighten(#ee5f5b, 20%); + //.box-shadow(@shadow); } } diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/js/bootstrap-typeahead.js b/src/Presentation/SmartStore.Web/Content/bootstrap/js/bootstrap-typeahead.js index 442db502d1..664300a21b 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/js/bootstrap-typeahead.js +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/js/bootstrap-typeahead.js @@ -22,7 +22,6 @@ "use strict"; // jshint ;_; - /* TYPEAHEAD PUBLIC CLASS DEFINITION * ================================= */ @@ -37,6 +36,9 @@ this.source = this.options.source this.shown = false this.listen() + + // codehint: sm-add (throttled keypress) + this.timer = null; } Typeahead.prototype = { @@ -44,7 +46,8 @@ constructor: Typeahead , select: function () { - var val = this.$menu.find('.active').attr('data-value') + var val = this.$menu.find('.active').attr('data-value') + // codehint: deleted ('change' results in js error) /*this.$element .val(this.updater(val)) @@ -64,10 +67,12 @@ var pos = $.extend({}, this.$element.offset(), { height: this.$element[0].offsetHeight }) - + this.$menu.css({ top: pos.top + pos.height , left: pos.left + , boxSizing: "border-box" // codehint: added + , minWidth: this.$element.outerWidth(false) + "px" // codehint: added }) this.$menu.show() @@ -131,7 +136,13 @@ } , highlighter: function (item) { - var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&') + // codehint: sm-edit + // Original: + // var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&') + + // codehint: the above query transforms the whole query to a regex pattern, thus only highlighting 'exact' matches. + // But we also want to highlight 'far' words. So we create a pattern like "word1|word2|wordn..." (instead of "word1\ word2\ wordn...") + var query = _.map(_.str.words(this.query), function (val, i) { return val.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&') }).join("|"); return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) { return '' + match + '' }) @@ -145,8 +156,12 @@ i.find('a').html(that.highlighter(item)) return i[0] }) + + // codehint: edit + if (this.options.autoSelectFirstItem) { + items.first().addClass('active') + } - items.first().addClass('active') this.$menu.html(items) return this } @@ -205,11 +220,16 @@ switch(e.keyCode) { case 9: // tab - case 13: // enter case 27: // escape e.preventDefault() break + case 13: // enter + if (this.$menu.find('.active').length > 0) { + e.preventDefault(); + } + break + case 38: // up arrow e.preventDefault() this.prev() @@ -254,8 +274,12 @@ this.hide() break - default: - this.lookup() + default: + // codehint: sm-edit (throttled keypress) + // Original: this.lookup() + var self = this; + clearTimeout(self.timer); + self.timer = setTimeout(function () { self.lookup() }, self.options.keyUpDelay); } e.stopPropagation() @@ -316,6 +340,10 @@ , menu: '' , item: '
  • ' , minLength: 1 + // codehint: sm-add (throttled KeyPress) + , keyUpDelay: 0 + // codehint: sm-add + , autoSelectFirstItem: true } $.fn.typeahead.Constructor = Typeahead diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/media.less b/src/Presentation/SmartStore.Web/Content/bootstrap/media.less index 1decab71de..8b89ef39df 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/media.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/media.less @@ -38,7 +38,7 @@ // ------------------------- .media .pull-left { - margin-right: 10px; + margin-right: 25px; } .media .pull-right { margin-left: 10px; diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/mixins.less b/src/Presentation/SmartStore.Web/Content/bootstrap/mixins.less index 60e1b07a23..c1d9950469 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/mixins.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/mixins.less @@ -240,14 +240,12 @@ // Drop shadows .box-shadow(@shadow) { - -webkit-box-shadow: @shadow; - box-shadow: @shadow; + box-shadow: @shadow; } // Transitions .transition(@transition) { -webkit-transition: @transition; - -o-transition: @transition; transition: @transition; } .transition-delay(@transition-delay) { @@ -258,33 +256,23 @@ // Transformations .rotate(@degrees) { -webkit-transform: rotate(@degrees); - -ms-transform: rotate(@degrees); - -o-transform: rotate(@degrees); transform: rotate(@degrees); } .scale(@ratio) { -webkit-transform: scale(@ratio); - -ms-transform: scale(@ratio); - -o-transform: scale(@ratio); transform: scale(@ratio); } .translate(@x, @y) { -webkit-transform: translate(@x, @y); - -ms-transform: translate(@x, @y); - -o-transform: translate(@x, @y); transform: translate(@x, @y); } .skew(@x, @y) { -webkit-transform: skew(@x, @y); - -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twitter/bootstrap/issues/4885 - -o-transform: skew(@x, @y); transform: skew(@x, @y); -webkit-backface-visibility: hidden; // See https://github.com/twitter/bootstrap/issues/5319 } .translate3d(@x, @y, @z) { -webkit-transform: translate3d(@x, @y, @z); - -moz-transform: translate3d(@x, @y, @z); - -o-transform: translate3d(@x, @y, @z); transform: translate3d(@x, @y, @z); } @@ -294,32 +282,24 @@ // See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples .backface-visibility(@visibility){ -webkit-backface-visibility: @visibility; - -moz-backface-visibility: @visibility; backface-visibility: @visibility; } // Background clipping // Heads up: FF 3.6 and under need "padding" instead of "padding-box" .background-clip(@clip) { - -webkit-background-clip: @clip; - -moz-background-clip: @clip; - background-clip: @clip; + background-clip: @clip; } // Background sizing .background-size(@size) { - -webkit-background-size: @size; - -moz-background-size: @size; - -o-background-size: @size; - background-size: @size; + background-size: @size; } // Box sizing .box-sizing(@boxmodel) { - -webkit-box-sizing: @boxmodel; - -moz-box-sizing: @boxmodel; - box-sizing: @boxmodel; + box-sizing: @boxmodel; } // User select @@ -353,14 +333,12 @@ -webkit-hyphens: @mode; -moz-hyphens: @mode; -ms-hyphens: @mode; - -o-hyphens: @mode; hyphens: @mode; } // Opacity .opacity(@opacity) { - opacity: @opacity / 100; - filter: ~"alpha(opacity=@{opacity})"; + opacity: @opacity / 100; } @@ -392,33 +370,26 @@ .horizontal(@startColor: #555, @endColor: #333) { background-color: @endColor; background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ - background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 background-repeat: repeat-x; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down } .vertical(@startColor: #555, @endColor: #333) { background-color: mix(@startColor, @endColor, 60%); background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ - background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 background-repeat: repeat-x; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down } .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { background-color: @endColor; background-repeat: repeat-x; background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ - background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10 } .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { background-color: mix(@midColor, @endColor, 80%); background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); - background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); background-repeat: no-repeat; - filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback } .radial(@innerColor: #555, @outerColor: #333) { background-color: @outerColor; @@ -429,7 +400,6 @@ .striped(@color: #555, @angle: 45deg) { background-color: @color; background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); } } diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/popovers.less b/src/Presentation/SmartStore.Web/Content/bootstrap/popovers.less index a4c4bb0e07..70da281fdd 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/popovers.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/popovers.less @@ -13,7 +13,6 @@ padding: 1px; background-color: @popoverBackground; -webkit-background-clip: padding-box; - -moz-background-clip: padding; background-clip: padding-box; border: 1px solid #ccc; border: 1px solid rgba(0,0,0,.2); diff --git a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/config.js b/src/Presentation/SmartStore.Web/Content/editors/ckeditor/config.js index 4b4d54c9d5..6eebb477e4 100644 --- a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/config.js +++ b/src/Presentation/SmartStore.Web/Content/editors/ckeditor/config.js @@ -20,6 +20,7 @@ CKEDITOR.editorConfig = function( config ) { config.toolbarStartupExpanded = true; //config.skin = "moono"; config.skin = "smartstore"; + config.fillEmptyBlocks = false; // The toolbar groups arrangement, optimized for two toolbar rows. config.toolbarGroups = [ diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/asp_net/main.ashx b/src/Presentation/SmartStore.Web/Content/filemanager/asp_net/main.ashx deleted file mode 100644 index 72c5f5feb9..0000000000 --- a/src/Presentation/SmartStore.Web/Content/filemanager/asp_net/main.ashx +++ /dev/null @@ -1,719 +0,0 @@ -<%@ WebHandler Language="C#" Class="RoxyFilemanHandler" Debug="true" %> -/* - RoxyFileman - web based file manager. Ready to use with CKEditor, TinyMCE. - Can be easily integrated with any other WYSIWYG editor or CMS. - - Copyright (C) 2013, RoxyFileman.com - Lyubomir Arsov. All rights reserved. - For licensing, see LICENSE.txt or http://RoxyFileman.com/license - - 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 3 of the License. - - 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, see . - - Contact: Lyubomir Arsov, liubo (at) web-lobby.com -*/ -using System; -using System.Reflection; -using System.Drawing; -using System.Collections; -using System.IO; -using System.Collections.Generic; -using System.Web; -using System.Text.RegularExpressions; -using System.Drawing.Imaging; -using System.IO.Compression; - -public class RoxyFilemanHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState -{ - Dictionary _settings = null; - Dictionary _lang = null; - HttpResponse _r = null; - HttpContext _context = null; - string confFile = "../conf.json"; - public void ProcessRequest (HttpContext context) { - _context = context; - _r = context.Response; - string action = "DIRLIST"; - try{ - if (_context.Request["a"] != null) - action = (string)_context.Request["a"]; - - VerifyAction(action); - switch (action.ToUpper()) - { - case "DIRLIST": - ListDirTree(_context.Request["type"]); - break; - case "FILESLIST": - ListFiles(_context.Request["d"], _context.Request["type"]); - break; - case "COPYDIR": - CopyDir(_context.Request["d"], _context.Request["n"]); - break; - case "COPYFILE": - CopyFile(_context.Request["f"], _context.Request["n"]); - break; - case "CREATEDIR": - CreateDir(_context.Request["d"], _context.Request["n"]); - break; - case "DELETEDIR": - DeleteDir(_context.Request["d"]); - break; - case "DELETEFILE": - DeleteFile(_context.Request["f"]); - break; - case "DOWNLOAD": - DownloadFile(_context.Request["f"]); - break; - case "DOWNLOADDIR": - DownloadDir(_context.Request["d"]); - break; - case "MOVEDIR": - MoveDir(_context.Request["d"], _context.Request["n"]); - break; - case "MOVEFILE": - MoveFile(_context.Request["f"], _context.Request["n"]); - break; - case "RENAMEDIR": - RenameDir(_context.Request["d"], _context.Request["n"]); - break; - case "RENAMEFILE": - RenameFile(_context.Request["f"], _context.Request["n"]); - break; - case "GENERATETHUMB": - int w = 140, h = 0; - int.TryParse(_context.Request["width"].Replace("px", ""), out w); - int.TryParse(_context.Request["height"].Replace("px", ""), out h); - ShowThumbnail(_context.Request["f"], w, h); - break; - case "UPLOAD": - Upload(_context.Request["d"]); - break; - default: - _r.Write(GetErrorRes("This action is not implemented.")); - break; - } - - } - catch(Exception ex){ - if (action == "UPLOAD"){ - _r.Write(""); - } - else{ - _r.Write(GetErrorRes(ex.Message)); - } - } - - } - private string FixPath(string path) - { - if (!path.StartsWith("~")){ - if (!path.StartsWith("/")) - path = "/" + path; - path = "~" + path; - } - return _context.Server.MapPath(path); - } - private string GetLangFile(){ - return "../lang/en.json"; - } - protected string LangRes(string name) - { - string ret = name; - if (_lang == null) - _lang = ParseJSON(GetLangFile()); - if (_lang.ContainsKey(name)) - ret = _lang[name]; - - return ret; - } - protected string GetFileType(string ext){ - string ret = "file"; - ext = ext.ToLower(); - if(ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif") - ret = "image"; - else if(ext == ".swf" || ext == ".flv") - ret = "flash"; - return ret; - } - protected bool CanHandleFile(string filename) - { - bool ret = false; - FileInfo file = new FileInfo(filename); - string ext = file.Extension.Replace(".", "").ToLower(); - string setting = GetSetting("FORBIDDEN_UPLOADS").Trim().ToLower(); - if (setting != "") - { - ArrayList tmp = new ArrayList(); - tmp.AddRange(Regex.Split(setting, "\\s+")); - if (!tmp.Contains(ext)) - ret = true; - } - setting = GetSetting("ALLOWED_UPLOADS").Trim().ToLower(); - if (setting != "") - { - ArrayList tmp = new ArrayList(); - tmp.AddRange(Regex.Split(setting, "\\s+")); - if (!tmp.Contains(ext)) - ret = false; - } - - return ret; - } - protected Dictionary ParseJSON(string file){ - Dictionary ret = new Dictionary(); - string json = ""; - try{ - json = File.ReadAllText(_context.Server.MapPath(file), System.Text.Encoding.UTF8); - } - catch {} - - json = json.Trim(); - if(json != ""){ - if (json.StartsWith("{")) - json = json.Substring(1, json.Length - 2); - json = json.Trim(); - json = json.Substring(1, json.Length - 2); - string[] lines = Regex.Split(json, "\"\\s*,\\s*\""); - foreach(string line in lines){ - string[] tmp = Regex.Split(line, "\"\\s*:\\s*\""); - try{ - if (tmp[0] != "" && !ret.ContainsKey(tmp[0])) - { - ret.Add(tmp[0], tmp[1]); - } - } - catch {} - } - } - return ret; - } - protected string GetFilesRoot(){ - string ret = GetSetting("FILES_ROOT"); - if (GetSetting("SESSION_PATH_KEY") != "" && _context.Session[GetSetting("SESSION_PATH_KEY")] != null) - ret = (string)_context.Session[GetSetting("SESSION_PATH_KEY")]; - - if(ret == "") - ret = _context.Server.MapPath("../Uploads"); - else - ret = FixPath(ret); - return ret; - } - protected void LoadConf(){ - if(_settings == null) - _settings = ParseJSON(confFile); - } - protected string GetSetting(string name){ - string ret = ""; - LoadConf(); - if(_settings.ContainsKey(name)) - ret = _settings[name]; - - return ret; - } - protected void CheckPath(string path) - { - if (FixPath(path).IndexOf(GetFilesRoot()) != 0) - { - throw new Exception("Access to " + path + " is denied"); - } - } - protected void VerifyAction(string action) - { - string setting = GetSetting(action); - if (setting.IndexOf("?") > -1) - setting = setting.Substring(0, setting.IndexOf("?")); - if (!setting.StartsWith("/")) - setting = "/" + setting; - setting = ".." + setting; - - if (_context.Server.MapPath(setting) != _context.Server.MapPath(_context.Request.Url.LocalPath)) - throw new Exception(LangRes("E_ActionDisabled")); - } - protected string GetResultStr(string type, string msg) - { - return "{\"res\":\"" + type + "\",\"msg\":\"" + msg.Replace("\"","\\\"") + "\"}"; - } - protected string GetSuccessRes(string msg) - { - return GetResultStr("ok", msg); - } - protected string GetSuccessRes() - { - return GetSuccessRes(""); - } - protected string GetErrorRes(string msg) - { - return GetResultStr("error", msg); - } - private void _copyDir(string path, string dest){ - if(!Directory.Exists(dest)) - Directory.CreateDirectory(dest); - foreach(string f in Directory.GetFiles(path)){ - FileInfo file = new FileInfo(f); - if (!File.Exists(Path.Combine(dest, file.Name))){ - File.Copy(f, Path.Combine(dest, file.Name)); - } - } - foreach (string d in Directory.GetDirectories(path)) - { - DirectoryInfo dir = new DirectoryInfo(d); - _copyDir(d, Path.Combine(dest, dir.Name)); - } - } - protected void CopyDir(string path, string newPath) - { - CheckPath(path); - CheckPath(newPath); - DirectoryInfo dir = new DirectoryInfo(FixPath(path)); - DirectoryInfo newDir = new DirectoryInfo(FixPath(newPath + "/" + dir.Name)); - - if (!dir.Exists) - { - throw new Exception(LangRes("E_CopyDirInvalidPath")); - } - else if (newDir.Exists) - { - throw new Exception(LangRes("E_DirAlreadyExists")); - } - else{ - _copyDir(dir.FullName, newDir.FullName); - } - _r.Write(GetSuccessRes()); - } - protected string MakeUniqueFilename(string dir, string filename){ - string ret = filename; - int i = 0; - while (File.Exists(Path.Combine(dir, ret))) - { - i++; - ret = Path.GetFileNameWithoutExtension(filename) + " - Copy " + i.ToString() + Path.GetExtension(filename); - } - return ret; - } - protected void CopyFile(string path, string newPath) - { - CheckPath(path); - FileInfo file = new FileInfo(FixPath(path)); - newPath = FixPath(newPath); - if (!file.Exists) - throw new Exception(LangRes("E_CopyFileInvalisPath")); - else{ - string newName = MakeUniqueFilename(newPath, file.Name); - try{ - File.Copy(file.FullName, Path.Combine(newPath, newName)); - _r.Write(GetSuccessRes()); - } - catch { - throw new Exception(LangRes("E_CopyFile")); - } - } - } - protected void CreateDir(string path, string name) - { - CheckPath(path); - path = FixPath(path); - if(!Directory.Exists(path)) - throw new Exception(LangRes("E_CreateDirInvalidPath")); - else{ - try - { - path = Path.Combine(path, name); - if(!Directory.Exists(path)) - Directory.CreateDirectory(path); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_CreateDirFailed")); - } - } - } - protected void DeleteDir(string path) - { - CheckPath(path); - path = FixPath(path); - if (!Directory.Exists(path)) - throw new Exception(LangRes("E_DeleteDirInvalidPath")); - else if (path == GetFilesRoot()) - throw new Exception(LangRes("E_CannotDeleteRoot")); - else if(Directory.GetDirectories(path).Length > 0 || Directory.GetFiles(path).Length > 0) - throw new Exception(LangRes("E_DeleteNonEmpty")); - else - { - try - { - Directory.Delete(path); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_CannotDeleteDir")); - } - } - } - protected void DeleteFile(string path) - { - CheckPath(path); - path = FixPath(path); - if (!File.Exists(path)) - throw new Exception(LangRes("E_DeleteFileInvalidPath")); - else - { - try - { - File.Delete(path); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_DeletеFile")); - } - } - } - private List GetFiles(string path, string type){ - List ret = new List(); - if(type == "#") - type = ""; - string[] files = Directory.GetFiles(path); - foreach(string f in files){ - if ((GetFileType(new FileInfo(f).Extension) == type) || (type == "")) - ret.Add(f); - } - return ret; - } - private ArrayList ListDirs(string path){ - string[] dirs = Directory.GetDirectories(path); - ArrayList ret = new ArrayList(); - foreach(string dir in dirs){ - ret.Add(dir); - ret.AddRange(ListDirs(dir)); - } - return ret; - } - protected void ListDirTree(string type) - { - DirectoryInfo d = new DirectoryInfo(GetFilesRoot()); - if(!d.Exists) - throw new Exception("Invalid files root directory. Check your configuration."); - - ArrayList dirs = ListDirs(d.FullName); - dirs.Insert(0, d.FullName); - - string localPath = _context.Server.MapPath("~/"); - _r.Write("["); - for(int i = 0; i files = GetFiles(fullPath, type); - _r.Write("["); - for(int i = 0; i < files.Count; i++){ - FileInfo f = new FileInfo(files[i]); - int w = 0, h = 0; - if (GetFileType(f.Extension) == "image"){ - try{ - FileStream fs = new FileStream(f.FullName, FileMode.Open); - Image img = Image.FromStream(fs); - w = img.Width; - h = img.Height; - fs.Close(); - fs.Dispose(); - img.Dispose(); - } - catch(Exception ex) - { - throw ex; - } - } - _r.Write("{"); - _r.Write("\"p\":\""+path + "/" + f.Name+"\""); - _r.Write(",\"t\":\"" + Math.Ceiling(LinuxTimestamp(f.LastWriteTime)).ToString() + "\""); - _r.Write(",\"s\":\""+f.Length.ToString()+"\""); - _r.Write(",\"w\":\""+w.ToString()+"\""); - _r.Write(",\"h\":\""+h.ToString()+"\""); - _r.Write("}"); - if (i < files.Count - 1) - _r.Write(","); - } - _r.Write("]"); - } - public void DownloadDir(string path) - { - path = FixPath(path); - if(!Directory.Exists(path)) - throw new Exception(LangRes("E_CreateArchive")); - string dirName = new FileInfo(path).Name; - string tmpZip = _context.Server.MapPath("../tmp/" + dirName + ".zip"); - if(File.Exists(tmpZip)) - File.Delete(tmpZip); - ZipFile.CreateFromDirectory(path, tmpZip,CompressionLevel.Fastest, true); - _r.Clear(); - _r.Headers.Add("Content-Disposition", "attachment; filename=\"" + dirName + ".zip\""); - _r.ContentType = "application/force-download"; - _r.TransmitFile(tmpZip); - _r.Flush(); - File.Delete(tmpZip); - _r.End(); - } - protected void DownloadFile(string path) - { - CheckPath(path); - FileInfo file = new FileInfo(FixPath(path)); - if(file.Exists){ - _r.Clear(); - _r.Headers.Add("Content-Disposition", "attachment; filename=\"" + file.Name + "\""); - _r.ContentType = "application/force-download"; - _r.TransmitFile(file.FullName); - _r.Flush(); - _r.End(); - } - } - protected void MoveDir(string path, string newPath) - { - CheckPath(path); - CheckPath(newPath); - DirectoryInfo source = new DirectoryInfo(FixPath(path)); - DirectoryInfo dest = new DirectoryInfo(FixPath(Path.Combine(newPath, source.Name))); - if(dest.FullName.IndexOf(source.FullName) == 0) - throw new Exception(LangRes("E_CannotMoveDirToChild")); - else if (!source.Exists) - throw new Exception(LangRes("E_MoveDirInvalisPath")); - else if (dest.Exists) - throw new Exception(LangRes("E_DirAlreadyExists")); - else{ - try{ - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch { - throw new Exception(LangRes("E_MoveDir") + " \"" + path + "\""); - } - } - - } - protected void MoveFile(string path, string newPath) - { - CheckPath(path); - CheckPath(newPath); - FileInfo source = new FileInfo(FixPath(path)); - FileInfo dest = new FileInfo(FixPath(newPath)); - if (!source.Exists) - throw new Exception(LangRes("E_MoveFileInvalisPath")); - else if (dest.Exists) - throw new Exception(LangRes("E_MoveFileAlreadyExists")); - else - { - try - { - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_MoveFile") + " \"" + path + "\""); - } - } - } - protected void RenameDir(string path, string name) - { - CheckPath(path); - DirectoryInfo source = new DirectoryInfo(FixPath(path)); - DirectoryInfo dest = new DirectoryInfo(Path.Combine(source.Parent.FullName, name)); - if(source.FullName == GetFilesRoot()) - throw new Exception(LangRes("E_CannotRenameRoot")); - else if (!source.Exists) - throw new Exception(LangRes("E_RenameDirInvalidPath")); - else if (dest.Exists) - throw new Exception(LangRes("E_DirAlreadyExists")); - else - { - try - { - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_RenameDir") + " \"" + path + "\""); - } - } - } - protected void RenameFile(string path, string name) - { - CheckPath(path); - FileInfo source = new FileInfo(FixPath(path)); - FileInfo dest = new FileInfo(Path.Combine(source.Directory.FullName, name)); - if (!source.Exists) - throw new Exception(LangRes("E_RenameFileInvalidPath")); - else if (!CanHandleFile(name)) - throw new Exception(LangRes("E_FileExtensionForbidden")); - else - { - try - { - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch (Exception ex) - { - throw new Exception(ex.Message + "; " + LangRes("E_RenameFile") + " \"" + path + "\""); - } - } - } - public bool ThumbnailCallback() - { - return false; - } - - protected void ShowThumbnail(string path, int width, int height) - { - CheckPath(path); - FileStream fs = new FileStream(FixPath(path), FileMode.Open); - Bitmap img = new Bitmap(Bitmap.FromStream(fs)); - fs.Close(); - fs.Dispose(); - int cropWidth = img.Width, cropHeight = img.Height; - int cropX = 0, cropY = 0; - - double imgRatio = (double)img.Width / (double)img.Height; - - if(height == 0) - height = Convert.ToInt32(Math.Floor((double)width / imgRatio)); - - if (width > img.Width) - width = img.Width; - if (height > img.Height) - height = img.Height; - - double cropRatio = (double)width / (double)height; - cropWidth = Convert.ToInt32(Math.Floor((double)img.Height * cropRatio)); - cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); - if (cropWidth > img.Width) - { - cropWidth = img.Width; - cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); - } - if (cropHeight > img.Height) - { - cropHeight = img.Height; - cropWidth = Convert.ToInt32(Math.Floor((double)cropHeight * cropRatio)); - } - if(cropWidth < img.Width){ - cropX = Convert.ToInt32(Math.Floor((double)(img.Width - cropWidth) / 2)); - } - if(cropHeight < img.Height){ - cropY = Convert.ToInt32(Math.Floor((double)(img.Height - cropHeight) / 2)); - } - - Rectangle area = new Rectangle(cropX, cropY, cropWidth, cropHeight); - Bitmap cropImg = img.Clone(area, System.Drawing.Imaging.PixelFormat.DontCare); - img.Dispose(); - Image.GetThumbnailImageAbort imgCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback); - - _r.AddHeader("Content-Type", "image/png"); - cropImg.GetThumbnailImage(width, height, imgCallback, IntPtr.Zero).Save(_r.OutputStream, ImageFormat.Png); - _r.OutputStream.Close(); - cropImg.Dispose(); - } - private ImageFormat GetImageFormat(string filename){ - ImageFormat ret = ImageFormat.Jpeg; - switch(new FileInfo(filename).Extension.ToLower()){ - case ".png": ret = ImageFormat.Png; break; - case ".gif": ret = ImageFormat.Gif; break; - } - return ret; - } - protected void ImageResize(string path, string dest, int width, int height) - { - FileStream fs = new FileStream(path, FileMode.Open); - Image img = Image.FromStream(fs); - fs.Close(); - fs.Dispose(); - float ratio = (float)img.Width / (float)img.Height; - if ((img.Width <= width && img.Height <= height) || (width == 0 && height == 0)) - return; - - int newWidth = width; - int newHeight = Convert.ToInt16(Math.Floor((float)newWidth / ratio)); - if ((height > 0 && newHeight > height) || (width == 0)) - { - newHeight = height; - newWidth = Convert.ToInt16(Math.Floor((float)newHeight * ratio)); - } - Bitmap newImg = new Bitmap(newWidth, newHeight); - Graphics g = Graphics.FromImage((Image)newImg); - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - g.DrawImage(img, 0, 0, newWidth, newHeight); - img.Dispose(); - g.Dispose(); - if(dest != ""){ - newImg.Save(dest, GetImageFormat(dest)); - } - newImg.Dispose(); - } - - protected void Upload(string path) - { - CheckPath(path); - path = FixPath(path); - string res = GetSuccessRes(); - try{ - for(int i = 0; i < HttpContext.Current.Request.Files.Count; i++){ - if (CanHandleFile(HttpContext.Current.Request.Files[i].FileName)) - { - string filename = MakeUniqueFilename(path, HttpContext.Current.Request.Files[i].FileName); - string dest = Path.Combine(path, filename); - HttpContext.Current.Request.Files[i].SaveAs(dest); - if(GetFileType(new FileInfo(filename).Extension) == "image"){ - int w = 0; - int h = 0; - int.TryParse(GetSetting("MAX_IMAGE_WIDTH"), out w); - int.TryParse(GetSetting("MAX_IMAGE_HEIGHT"), out h); - ImageResize(dest, dest, w, h); - } - } - else - res = GetSuccessRes(LangRes("E_UploadNotAll")); - } - } - catch(Exception ex){ - res = GetErrorRes(ex.Message); - } - _r.Write(""); - } - - public bool IsReusable { - get { - return false; - } - } - -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/conf.json b/src/Presentation/SmartStore.Web/Content/filemanager/conf.json deleted file mode 100644 index 3df29bf505..0000000000 --- a/src/Presentation/SmartStore.Web/Content/filemanager/conf.json +++ /dev/null @@ -1,34 +0,0 @@ -{ -"FILES_ROOT": "~/Media/Uploaded", -"SESSION_PATH_KEY": "", -"THUMBS_VIEW_WIDTH": "140", -"THUMBS_VIEW_HEIGHT": "120", -"PREVIEW_THUMB_WIDTH":"300", -"PREVIEW_THUMB_HEIGHT":"200", -"MAX_IMAGE_WIDTH": "0", -"MAX_IMAGE_HEIGHT": "0", -"INTEGRATION": "ckeditor", -"DIRLIST": "asp_net/main.ashx?a=DIRLIST", -"CREATEDIR": "asp_net/main.ashx?a=CREATEDIR", -"DELETEDIR": "asp_net/main.ashx?a=DELETEDIR", -"MOVEDIR": "asp_net/main.ashx?a=MOVEDIR", -"COPYDIR": "asp_net/main.ashx?a=COPYDIR", -"RENAMEDIR": "asp_net/main.ashx?a=RENAMEDIR", -"FILESLIST": "asp_net/main.ashx?a=FILESLIST", -"UPLOAD": "asp_net/main.ashx?a=UPLOAD", -"DOWNLOAD": "asp_net/main.ashx?a=DOWNLOAD", -"DOWNLOADDIR": "asp_net/main.ashx?a=DOWNLOADDIR", -"DOWNLOADDIR": "asp_net/main.ashx?a=DOWNLOADDIR", -"DELETEFILE": "asp_net/main.ashx?a=DELETEFILE", -"MOVEFILE": "asp_net/main.ashx?a=MOVEFILE", -"COPYFILE": "asp_net/main.ashx?a=COPYFILE", -"RENAMEFILE": "asp_net/main.ashx?a=RENAMEFILE", -"GENERATETHUMB": "asp_net/main.ashx?a=GENERATETHUMB", -"DEFAULTVIEW": "list", -"FORBIDDEN_UPLOADS": "zip js jsp jsb mhtml mht xhtml xht php phtml php3 php4 php5 phps shtml jhtml pl sh py cgi exe scr dll msi vbs bat com pif cmd vxd cpl htpasswd htaccess", -"ALLOWED_UPLOADS": "", -"FILEPERMISSIONS": "0644", -"DIRPERMISSIONS": "0755", -"LANG": "auto", -"DATEFORMAT": "dd.MM.yyyy HH:mm" -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/font-awesome.css b/src/Presentation/SmartStore.Web/Content/font-awesome.css index 5bea9bf0d5..ba49171638 100644 --- a/src/Presentation/SmartStore.Web/Content/font-awesome.css +++ b/src/Presentation/SmartStore.Web/Content/font-awesome.css @@ -1,13 +1,13 @@ /*! - * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ /* FONT PATH * -------------------------- */ @font-face { font-family: 'FontAwesome'; - src: url('fonts/fontawesome-webfont.eot?v=4.2.0'); - src: url('fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'), url('fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'), url('fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'), url('fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg'); + src: url('fonts/fontawesome-webfont.eot?v=4.5.0'); + src: url('fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'), url('fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'), url('fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'), url('fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'), url('fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg'); font-weight: normal; font-style: normal; } @@ -64,6 +64,19 @@ border: solid 0.08em #eeeeee; border-radius: .1em; } +.fa-pull-left { + float: left; +} +.fa-pull-right { + float: right; +} +.fa.fa-pull-left { + margin-right: .3em; +} +.fa.fa-pull-right { + margin-left: .3em; +} +/* Deprecated as of 4.4.0 */ .pull-right { float: right; } @@ -80,6 +93,10 @@ -webkit-animation: fa-spin 2s infinite linear; animation: fa-spin 2s infinite linear; } +.fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); +} @-webkit-keyframes fa-spin { 0% { -webkit-transform: rotate(0deg); @@ -610,6 +627,7 @@ .fa-twitter:before { content: "\f099"; } +.fa-facebook-f:before, .fa-facebook:before { content: "\f09a"; } @@ -622,6 +640,7 @@ .fa-credit-card:before { content: "\f09d"; } +.fa-feed:before, .fa-rss:before { content: "\f09e"; } @@ -1259,7 +1278,8 @@ .fa-male:before { content: "\f183"; } -.fa-gittip:before { +.fa-gittip:before, +.fa-gratipay:before { content: "\f184"; } .fa-sun-o:before { @@ -1502,6 +1522,8 @@ .fa-git:before { content: "\f1d3"; } +.fa-y-combinator-square:before, +.fa-yc-square:before, .fa-hacker-news:before { content: "\f1d4"; } @@ -1670,3 +1692,395 @@ .fa-meanpath:before { content: "\f20c"; } +.fa-buysellads:before { + content: "\f20d"; +} +.fa-connectdevelop:before { + content: "\f20e"; +} +.fa-dashcube:before { + content: "\f210"; +} +.fa-forumbee:before { + content: "\f211"; +} +.fa-leanpub:before { + content: "\f212"; +} +.fa-sellsy:before { + content: "\f213"; +} +.fa-shirtsinbulk:before { + content: "\f214"; +} +.fa-simplybuilt:before { + content: "\f215"; +} +.fa-skyatlas:before { + content: "\f216"; +} +.fa-cart-plus:before { + content: "\f217"; +} +.fa-cart-arrow-down:before { + content: "\f218"; +} +.fa-diamond:before { + content: "\f219"; +} +.fa-ship:before { + content: "\f21a"; +} +.fa-user-secret:before { + content: "\f21b"; +} +.fa-motorcycle:before { + content: "\f21c"; +} +.fa-street-view:before { + content: "\f21d"; +} +.fa-heartbeat:before { + content: "\f21e"; +} +.fa-venus:before { + content: "\f221"; +} +.fa-mars:before { + content: "\f222"; +} +.fa-mercury:before { + content: "\f223"; +} +.fa-intersex:before, +.fa-transgender:before { + content: "\f224"; +} +.fa-transgender-alt:before { + content: "\f225"; +} +.fa-venus-double:before { + content: "\f226"; +} +.fa-mars-double:before { + content: "\f227"; +} +.fa-venus-mars:before { + content: "\f228"; +} +.fa-mars-stroke:before { + content: "\f229"; +} +.fa-mars-stroke-v:before { + content: "\f22a"; +} +.fa-mars-stroke-h:before { + content: "\f22b"; +} +.fa-neuter:before { + content: "\f22c"; +} +.fa-genderless:before { + content: "\f22d"; +} +.fa-facebook-official:before { + content: "\f230"; +} +.fa-pinterest-p:before { + content: "\f231"; +} +.fa-whatsapp:before { + content: "\f232"; +} +.fa-server:before { + content: "\f233"; +} +.fa-user-plus:before { + content: "\f234"; +} +.fa-user-times:before { + content: "\f235"; +} +.fa-hotel:before, +.fa-bed:before { + content: "\f236"; +} +.fa-viacoin:before { + content: "\f237"; +} +.fa-train:before { + content: "\f238"; +} +.fa-subway:before { + content: "\f239"; +} +.fa-medium:before { + content: "\f23a"; +} +.fa-yc:before, +.fa-y-combinator:before { + content: "\f23b"; +} +.fa-optin-monster:before { + content: "\f23c"; +} +.fa-opencart:before { + content: "\f23d"; +} +.fa-expeditedssl:before { + content: "\f23e"; +} +.fa-battery-4:before, +.fa-battery-full:before { + content: "\f240"; +} +.fa-battery-3:before, +.fa-battery-three-quarters:before { + content: "\f241"; +} +.fa-battery-2:before, +.fa-battery-half:before { + content: "\f242"; +} +.fa-battery-1:before, +.fa-battery-quarter:before { + content: "\f243"; +} +.fa-battery-0:before, +.fa-battery-empty:before { + content: "\f244"; +} +.fa-mouse-pointer:before { + content: "\f245"; +} +.fa-i-cursor:before { + content: "\f246"; +} +.fa-object-group:before { + content: "\f247"; +} +.fa-object-ungroup:before { + content: "\f248"; +} +.fa-sticky-note:before { + content: "\f249"; +} +.fa-sticky-note-o:before { + content: "\f24a"; +} +.fa-cc-jcb:before { + content: "\f24b"; +} +.fa-cc-diners-club:before { + content: "\f24c"; +} +.fa-clone:before { + content: "\f24d"; +} +.fa-balance-scale:before { + content: "\f24e"; +} +.fa-hourglass-o:before { + content: "\f250"; +} +.fa-hourglass-1:before, +.fa-hourglass-start:before { + content: "\f251"; +} +.fa-hourglass-2:before, +.fa-hourglass-half:before { + content: "\f252"; +} +.fa-hourglass-3:before, +.fa-hourglass-end:before { + content: "\f253"; +} +.fa-hourglass:before { + content: "\f254"; +} +.fa-hand-grab-o:before, +.fa-hand-rock-o:before { + content: "\f255"; +} +.fa-hand-stop-o:before, +.fa-hand-paper-o:before { + content: "\f256"; +} +.fa-hand-scissors-o:before { + content: "\f257"; +} +.fa-hand-lizard-o:before { + content: "\f258"; +} +.fa-hand-spock-o:before { + content: "\f259"; +} +.fa-hand-pointer-o:before { + content: "\f25a"; +} +.fa-hand-peace-o:before { + content: "\f25b"; +} +.fa-trademark:before { + content: "\f25c"; +} +.fa-registered:before { + content: "\f25d"; +} +.fa-creative-commons:before { + content: "\f25e"; +} +.fa-gg:before { + content: "\f260"; +} +.fa-gg-circle:before { + content: "\f261"; +} +.fa-tripadvisor:before { + content: "\f262"; +} +.fa-odnoklassniki:before { + content: "\f263"; +} +.fa-odnoklassniki-square:before { + content: "\f264"; +} +.fa-get-pocket:before { + content: "\f265"; +} +.fa-wikipedia-w:before { + content: "\f266"; +} +.fa-safari:before { + content: "\f267"; +} +.fa-chrome:before { + content: "\f268"; +} +.fa-firefox:before { + content: "\f269"; +} +.fa-opera:before { + content: "\f26a"; +} +.fa-internet-explorer:before { + content: "\f26b"; +} +.fa-tv:before, +.fa-television:before { + content: "\f26c"; +} +.fa-contao:before { + content: "\f26d"; +} +.fa-500px:before { + content: "\f26e"; +} +.fa-amazon:before { + content: "\f270"; +} +.fa-calendar-plus-o:before { + content: "\f271"; +} +.fa-calendar-minus-o:before { + content: "\f272"; +} +.fa-calendar-times-o:before { + content: "\f273"; +} +.fa-calendar-check-o:before { + content: "\f274"; +} +.fa-industry:before { + content: "\f275"; +} +.fa-map-pin:before { + content: "\f276"; +} +.fa-map-signs:before { + content: "\f277"; +} +.fa-map-o:before { + content: "\f278"; +} +.fa-map:before { + content: "\f279"; +} +.fa-commenting:before { + content: "\f27a"; +} +.fa-commenting-o:before { + content: "\f27b"; +} +.fa-houzz:before { + content: "\f27c"; +} +.fa-vimeo:before { + content: "\f27d"; +} +.fa-black-tie:before { + content: "\f27e"; +} +.fa-fonticons:before { + content: "\f280"; +} +.fa-reddit-alien:before { + content: "\f281"; +} +.fa-edge:before { + content: "\f282"; +} +.fa-credit-card-alt:before { + content: "\f283"; +} +.fa-codiepie:before { + content: "\f284"; +} +.fa-modx:before { + content: "\f285"; +} +.fa-fort-awesome:before { + content: "\f286"; +} +.fa-usb:before { + content: "\f287"; +} +.fa-product-hunt:before { + content: "\f288"; +} +.fa-mixcloud:before { + content: "\f289"; +} +.fa-scribd:before { + content: "\f28a"; +} +.fa-pause-circle:before { + content: "\f28b"; +} +.fa-pause-circle-o:before { + content: "\f28c"; +} +.fa-stop-circle:before { + content: "\f28d"; +} +.fa-stop-circle-o:before { + content: "\f28e"; +} +.fa-shopping-bag:before { + content: "\f290"; +} +.fa-shopping-basket:before { + content: "\f291"; +} +.fa-hashtag:before { + content: "\f292"; +} +.fa-bluetooth:before { + content: "\f293"; +} +.fa-bluetooth-b:before { + content: "\f294"; +} +.fa-percent:before { + content: "\f295"; +} diff --git a/src/Presentation/SmartStore.Web/Content/font-awesome.min.css b/src/Presentation/SmartStore.Web/Content/font-awesome.min.css index 08e68ec0c0..acce2ef232 100644 --- a/src/Presentation/SmartStore.Web/Content/font-awesome.min.css +++ b/src/Presentation/SmartStore.Web/Content/font-awesome.min.css @@ -1,4 +1,4 @@ /*! - * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('fonts/fontawesome-webfont.eot?v=4.2.0');src:url('fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'),url('fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'),url('fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'),url('fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"} \ No newline at end of file + */@font-face{font-family:'FontAwesome';src:url('fonts/fontawesome-webfont.eot?v=4.5.0');src:url('fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"} diff --git a/src/Presentation/SmartStore.Web/Content/fonts/FontAwesome.otf b/src/Presentation/SmartStore.Web/Content/fonts/FontAwesome.otf index 81c9ad949b..3ed7f8b48a 100644 Binary files a/src/Presentation/SmartStore.Web/Content/fonts/FontAwesome.otf and b/src/Presentation/SmartStore.Web/Content/fonts/FontAwesome.otf differ diff --git a/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.eot b/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.eot deleted file mode 100644 index e179d5fe77..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.eot and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.svg b/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.svg deleted file mode 100644 index 95f295b61e..0000000000 --- a/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.svg +++ /dev/null @@ -1,391 +0,0 @@ - - - - -This is a custom SVG font generated by IcoMoon. -9 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.ttf b/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.ttf deleted file mode 100644 index 9817e5cf4e..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.ttf and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.woff b/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.woff deleted file mode 100644 index 3a60828b77..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/fonts/Fontastic.woff and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/fonts/fontastic.less b/src/Presentation/SmartStore.Web/Content/fonts/fontastic.less deleted file mode 100644 index eaa4cfc2da..0000000000 --- a/src/Presentation/SmartStore.Web/Content/fonts/fontastic.less +++ /dev/null @@ -1,202 +0,0 @@ -@font-face { - font-family: 'Fontastic'; - src:url('Fontastic.eot'); - src:url('Fontastic.eot?#iefix') format('embedded-opentype'), - url('Fontastic.woff') format('woff'), - url('Fontastic.ttf') format('truetype'), - url('Fontastic.svg#Fontastic') format('svg'); - font-weight: normal; - font-style: normal; -} - -/* Use the following CSS code if you want to use data attributes for inserting your icons */ -[data-icon]:before { - font-family: 'Fontastic'; - content: attr(data-icon); - speak: none; - font-weight: normal; - -webkit-font-smoothing: auto; -} - -/* Use the following CSS code if you want to have a class per icon */ -[class^="sm-icon-"]:before, [class*=" sm-icon-"]:before { - font-family: 'Fontastic'; - font-style: normal; - speak: none; - font-weight: normal; - -webkit-font-smoothing: antialiased; -} -.sm-icon-database:before { - content: "\21"; -} -.sm-icon-user:before { - content: "\22"; -} -.sm-icon-cog:before { - content: "\23"; -} -.sm-icon-stats-up:before { - content: "\24"; -} -.sm-icon-gift:before { - content: "\25"; -} -.sm-icon-cube:before { - content: "\26"; -} -.sm-icon-help:before { - content: "\27"; -} -.sm-icon-loop:before { - content: "\28"; -} -.sm-icon-tag:before { - content: "\29"; -} -.sm-icon-tab:before { - content: "\2a"; -} -.sm-icon-gift-2:before { - content: "\2b"; -} -.sm-icon-home:before { - content: "\2c"; -} -.sm-icon-home-2:before { - content: "\2d"; -} -.sm-icon-earth:before { - content: "\2e"; -} -.sm-icon-database-2:before { - content: "\2f"; -} -.sm-icon-cog-2:before { - content: "\30"; -} -.sm-icon-loop-alt3:before { - content: "\31"; -} -.sm-icon-loop-alt4:before { - content: "\32"; -} -.sm-icon-loop-alt2:before { - content: "\33"; -} -.sm-icon-layers:before { - content: "\34"; -} -.sm-icon-user-2:before { - content: "\35"; -} -.sm-icon-tag-stroke:before { - content: "\36"; -} -.sm-icon-tag-fill:before { - content: "\37"; -} -.sm-icon-home-3:before { - content: "\38"; -} -.sm-icon-user-3:before { - content: "\39"; -} -.sm-icon-user-4:before { - content: "\3a"; -} -.sm-icon-users:before { - content: "\3b"; -} -.sm-icon-archive:before { - content: "\3c"; -} -.sm-icon-cog-3:before { - content: "\3d"; -} -.sm-icon-refresh:before { - content: "\3e"; -} -.sm-icon-tag-2:before { - content: "\3f"; -} -.sm-icon-tag-3:before { - content: "\40"; -} -.sm-icon-cog-4:before { - content: "\41"; -} -.sm-icon-folder:before { - content: "\42"; -} -.sm-icon-chart:before { - content: "\43"; -} -.sm-icon-cube-2:before { - content: "\44"; -} -.sm-icon-megaphone:before { - content: "\45"; -} -.sm-icon-box:before { - content: "\46"; -} -.sm-icon-layout:before { - content: "\47"; -} -.sm-icon-layout-2:before { - content: "\48"; -} -.sm-icon-layout-3:before { - content: "\49"; -} -.sm-icon-layout-4:before { - content: "\4a"; -} -.sm-icon-stats:before { - content: "\4b"; -} -.sm-icon-discout:before { - content: "\4c"; -} -.sm-icon-retweet:before { - content: "\4d"; -} -.sm-icon-tags:before { - content: "\4e"; -} -.sm-icon-users-2:before { - content: "\4f"; -} -.sm-icon-user-5:before { - content: "\50"; -} -.sm-icon-price:before { - content: "\51"; -} -.sm-icon-retweet-2:before { - content: "\52"; -} -.sm-icon-download:before { - content: "\53"; -} -.sm-icon-upload:before { - content: "\54"; -} -.sm-icon-home-4:before { - content: "\55"; -} -.sm-icon-dots-three:before { - content: "\56"; -} -.sm-icon-users-3:before { - content: "\e029"; -} -.sm-icon-user-6:before { - content: "\e028"; -} -.sm-icon-cog-5:before { - content: "\e000"; -} -.sm-icon-user-7:before { - content: "\e001"; -} diff --git a/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.eot b/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.eot index 84677bc0c5..9b6afaedc0 100644 Binary files a/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.eot and b/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.eot differ diff --git a/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.svg b/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.svg index d907b25ae6..d05688e9e2 100644 --- a/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.svg +++ b/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.svg @@ -147,14 +147,14 @@ - + - + @@ -219,8 +219,8 @@ - - + + @@ -275,7 +275,7 @@ - + @@ -362,7 +362,7 @@ - + @@ -399,7 +399,7 @@ - + @@ -410,9 +410,9 @@ - - - + + + @@ -438,7 +438,7 @@ - + @@ -454,12 +454,12 @@ - + - + @@ -483,13 +483,13 @@ - + - + @@ -513,8 +513,143 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.ttf b/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.ttf index 96a3639cdd..26dea7951a 100644 Binary files a/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.ttf and b/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.ttf differ diff --git a/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.woff b/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.woff index 628b6a52a8..dc35ce3c2c 100644 Binary files a/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.woff and b/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.woff differ diff --git a/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.woff2 b/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000000..500e517253 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/fonts/fontawesome-webfont.woff2 differ diff --git a/src/Presentation/SmartStore.Web/Content/fonts/raw/IcoMoon Session.json b/src/Presentation/SmartStore.Web/Content/fonts/raw/IcoMoon Session.json deleted file mode 100644 index b0e4ef45ab..0000000000 --- a/src/Presentation/SmartStore.Web/Content/fonts/raw/IcoMoon Session.json +++ /dev/null @@ -1 +0,0 @@ -{"share":"6", "iconsVersion":"1.4", "icomoon":"{\"selected\":[{\"idx\":\"591\",\"unicode\":\"21\"},{\"idx\":\"577\",\"unicode\":\"22\"},{\"idx\":\"563\",\"unicode\":\"23\"},{\"idx\":\"560\",\"unicode\":\"24\"},{\"idx\":\"559\",\"unicode\":\"25\"},{\"idx\":\"541\",\"unicode\":\"26\"},{\"idx\":\"497\",\"unicode\":\"27\"},{\"idx\":\"488\",\"unicode\":\"28\"},{\"idx\":\"270\",\"unicode\":\"29\"},{\"idx\":\"259\",\"unicode\":\"2b\"},{\"idx\":\"642\",\"unicode\":\"2d\"},{\"idx\":\"285\",\"unicode\":\"2e\"},{\"idx\":\"109\",\"unicode\":\"33\"},{\"idx\":\"36\",\"unicode\":\"34\"},{\"idx\":\"19\",\"unicode\":\"36\"},{\"idx\":\"816\",\"unicode\":\"3c\"},{\"idx\":\"803\",\"unicode\":\"3d\"},{\"idx\":\"788\",\"unicode\":\"3e\"},{\"idx\":\"1149\",\"unicode\":\"3f\"},{\"idx\":\"1148\",\"unicode\":\"40\"},{\"idx\":\"1065\",\"unicode\":\"43\"},{\"idx\":\"983\",\"unicode\":\"44\"},{\"idx\":\"991\",\"unicode\":\"45\"},{\"idx\":\"981\",\"unicode\":\"46\"},{\"idx\":\"962\",\"unicode\":\"47\"},{\"idx\":\"961\",\"unicode\":\"48\"},{\"idx\":\"959\",\"unicode\":\"49\"},{\"idx\":\"958\",\"unicode\":\"4a\"},{\"idx\":\"933\",\"unicode\":\"4b\"},{\"idx\":\"924\",\"unicode\":\"4c\"},{\"idx\":\"914\",\"unicode\":\"4d\"},{\"idx\":\"830\",\"unicode\":\"4e\"},{\"idx\":\"1393\",\"unicode\":\"4f\"},{\"idx\":\"1394\",\"unicode\":\"50\"},{\"idx\":\"1368\",\"unicode\":\"51\"},{\"idx\":\"1331\",\"unicode\":\"52\"},{\"idx\":\"1316\",\"unicode\":\"53\"},{\"idx\":\"1317\",\"unicode\":\"54\"},{\"idx\":\"1253\",\"unicode\":\"55\"},{\"idx\":\"1248\",\"unicode\":\"56\"},{\"idx\":\"1453\",\"unicode\":\"57\"},{\"idx\":\"1392\",\"unicode\":\"2a\"},{\"idx\":\"1367\",\"unicode\":\"2c\"},{\"idx\":\"1674\",\"unicode\":\"2f\"},{\"idx\":\"1673\",\"unicode\":\"30\"},{\"idx\":\"1362\",\"unicode\":\"31\"},{\"idx\":\"616\",\"unicode\":\"32\"},{\"idx\":\"721\",\"unicode\":\"35\"},{\"idx\":\"318\",\"unicode\":\"37\"},{\"idx\":\"255\",\"unicode\":\"38\"}],\"customIcons\":[{\"metadata\":{\"id\":\"iconic\",\"name\":\"Iconic\",\"link\":\"http://somerandomdude.com/work/iconic/\",\"author\":\"P.J. Onori\",\"authorLink\":\"http://somerandomdude.com\",\"license\":\"CC BY-SA 3.0\",\"licenseLink\":\"http://creativecommons.org/licenses/by-sa/3.0/us/\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"meteocons\",\"name\":\"Meteocons\",\"link\":\"http://www.alessioatzeni.com/meteocons/\",\"author\":\"Alessio Atzeni\",\"authorLink\":\"http://www.alessioatzeni.com/\",\"license\":\"Arbitrary\",\"licenseLink\":\"http://www.alessioatzeni.com/meteocons/#about\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"broccolidry\",\"name\":\"Broccolidry\",\"link\":\"http://dribbble.com/shots/587469-Free-16px-Broccolidryiconsaniconsetitisfullof-icons\",\"author\":\"Visual Idiot\",\"authorLink\":\"http://idiot.vc/\",\"license\":\"Aribitrary\",\"licenseLink\":\"http://licence.visualidiot.com/\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"icomoon\",\"name\":\"IcoMoon - Free\",\"link\":\"http://keyamoon.com/icomoon/\",\"author\":\"Keyamoon\",\"authorLink\":\"http://keyamoon.com/\",\"license\":\"CC BY-SA 3.0\",\"licenseLink\":\"http://creativecommons.org/licenses/by-sa/3.0/\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"iconminia\",\"name\":\"Icon Minia\",\"link\":\"http://dribbble.com/shots/598215-Icon-Minia-139-Vector-Icons\",\"author\":\"Egemen Kapusuz\",\"authorLink\":\"https://twitter.com/#!/egemem\",\"license\":\"GPL V3\",\"licenseLink\":\"http://www.gnu.org/copyleft/gpl.html\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"ecoico\",\"name\":\"Eco Ico\",\"link\":\"http://dribbble.com/shots/665585-Eco-Ico\",\"author\":\"Matthew Skiles\",\"authorLink\":\"http://www.dvq.co.nz/\",\"license\":\"CC0\",\"licenseLink\":\"http://creativecommons.org/publicdomain/zero/1.0/\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"brankic1979\",\"name\":\"Brankic1979\",\"link\":\"http://brankic1979.com/icons/\",\"author\":\"Keyamoon\",\"authorLink\":\"http://brankic1979.com\",\"license\":\"Custom\",\"licenseLink\":\"http://brankic1979.com/icons/\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"cuticons\",\"name\":\"Cuticons\",\"link\":\"http://dribbble.com/shots/631056-Cuticons-You-wanted-free-icons-right\",\"author\":\"Vaibhav Bhat\",\"authorLink\":\"https://twitter.com/vabhat\",\"license\":\"CC0\",\"licenseLink\":\"http://creativecommons.org/publicdomain/zero/1.0/\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"entypo\",\"name\":\"Entypo\",\"link\":\"http://www.entypo.com/\",\"author\":\"Daniel Bruce\",\"authorLink\":\"mailto:daniel@precinct.net\",\"license\":\"CC BY-SA 3.0\",\"licenseLink\":\"http://creativecommons.org/licenses/by-sa/3.0/\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"typicons\",\"name\":\"Typicons\",\"link\":\"http://typicons.com/\",\"author\":\"Stephen Hutchings\",\"license\":\"CC BY-SA 3.0\",\"licenseLink\":\"http://creativecommons.org/licenses/by-sa/3.0/\",\"defaultunicode\":true},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"silkcons\",\"name\":\"Silkcons\",\"link\":\"http://dribbble.com/shots/632219-Silkcons-You-can-t-do-with-just-one-icon-set\",\"author\":\"Vaibhav Bhat\",\"authorLink\":\"https://twitter.com/vabhat\",\"license\":\"CC0\",\"licenseLink\":\"http://creativecommons.org/publicdomain/zero/1.0/\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"wpzoom\",\"name\":\"WPZOOM Developer Icon Set\",\"link\":\"http://www.wpzoom.com/wpzoom/new-freebie-wpzoom-developer-icon-set-154-free-icons/\",\"author\":\"David Ferreira\",\"authorLink\":\"http://cargocollective.com/davidferreira\",\"license\":\"CC BY-SA 3.0\",\"licenseLink\":\"http://creativecommons.org/licenses/by-sa/3.0/\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]},{\"metadata\":{\"id\":\"loops105\",\"name\":\"105 Loops\",\"link\":\"http://dribbble.com/shots/707117-105-Loops-with-PSD\",\"author\":\"Pranav\",\"authorLink\":\"http://dribbble.com/pranav\",\"license\":\"Custom\",\"licenseLink\":\"http://dribbble.com/shots/707117-105-Loops-with-PSD\",\"defaultunicode\":false},\"svgs\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"]}],\"IDs\":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,19,20,21,22,23,24,25,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869],\"user\":{\"email\":\"keyamoon@gmail.com\",\"newsletter\":true,\"secret\":\"29769e48fe4bab8807b024a41d770900e18015c1af12cf4ad63d2d19009e6a90aead0d2885d7a32787d7336b442a44662cdfddfa2a2b0c74445becb1f50a8998\",\"uid\":4}}","inputCache":"{\"baseline\":\"0\",\"emSize\":\"512\",\"prev_size\":\"32\",\"hdr-imported\":\"checked\",\"iconAlignment\":\"0\",\"showGrid\":\"checked\",\"fi_name\":\"Fontastic\",\"fi_id\":\"\",\"fi_link\":\"\",\"fi_author\":\"\",\"fi_authorLink\":\"\",\"fi_license\":\"\",\"fi_licenseLink\":\"\",\"include_metadata\":false,\"base64\":false,\"img-height\":\"24\",\"img-color\":\"000000\",\"include_png\":\"checked\",\"fi_class\":\"sm-icon-\",\"showCloudLinks\":false,\"hdr-iconic\":\"checked\",\"hdr-meteocons\":\"checked\",\"hdr-broccolidry\":\"checked\",\"hdr-icomoon\":\"checked\",\"hdr-iconminia\":\"checked\",\"hdr-ecoico\":\"checked\",\"hdr-brankic1979\":\"checked\",\"hdr-cuticons\":\"checked\",\"hdr-entypo\":\"checked\",\"hdr-typicons\":\"checked\",\"hdr-silkcons\":\"checked\",\"hdr-wpzoom\":\"checked\",\"hdr-loops105\":\"checked\",\"sprites-cols\":\"16\",\"include_sprites\":false}"} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.eot b/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.eot deleted file mode 100644 index e179d5fe77..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.eot and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.svg b/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.svg deleted file mode 100644 index 95f295b61e..0000000000 --- a/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.svg +++ /dev/null @@ -1,391 +0,0 @@ - - - - -This is a custom SVG font generated by IcoMoon. -9 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.ttf b/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.ttf deleted file mode 100644 index 9817e5cf4e..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.ttf and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.woff b/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.woff deleted file mode 100644 index 3a60828b77..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/fonts/raw/fonts/Fontastic.woff and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/fonts/raw/index.html b/src/Presentation/SmartStore.Web/Content/fonts/raw/index.html deleted file mode 100644 index e628421a3b..0000000000 --- a/src/Presentation/SmartStore.Web/Content/fonts/raw/index.html +++ /dev/null @@ -1,508 +0,0 @@ - - - -Your Font/Glyphs - - - - - -
    -
    -
    -

    Your font contains the following glyphs

    -

    The generated SVG font can be imported back to IcoMoon for modification.

    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    -

    CSS Class Names

    -
    - - -  sm-icon-database - - - -  sm-icon-user - - - -  sm-icon-cog - - - -  sm-icon-stats-up - - - -  sm-icon-gift - - - -  sm-icon-cube - - - -  sm-icon-help - - - -  sm-icon-loop - - - -  sm-icon-tag - - - -  sm-icon-gift-2 - - - -  sm-icon-home - - - -  sm-icon-earth - - - -  sm-icon-loop-alt2 - - - -  sm-icon-layers - - - -  sm-icon-tag-stroke - - - -  sm-icon-archive - - - -  sm-icon-cog-2 - - - -  sm-icon-refresh - - - -  sm-icon-tag-2 - - - -  sm-icon-tag-3 - - - -  sm-icon-chart - - - -  sm-icon-cube-2 - - - -  sm-icon-megaphone - - - -  sm-icon-box - - - -  sm-icon-layout - - - -  sm-icon-layout-2 - - - -  sm-icon-layout-3 - - - -  sm-icon-layout-4 - - - -  sm-icon-stats - - - -  sm-icon-discout - - - -  sm-icon-retweet - - - -  sm-icon-tags - - - -  sm-icon-users - - - -  sm-icon-user-2 - - - -  sm-icon-price - - - -  sm-icon-retweet-2 - - - -  sm-icon-download - - - -  sm-icon-upload - - - -  sm-icon-home-2 - - - -  sm-icon-dots-three - - - -  sm-icon-users-2 - - - -  sm-icon-contact - - - -  sm-icon-camera - - - -  sm-icon-cart - - - -  sm-icon-bag - - - -  sm-icon-shopping - - - -  sm-icon-cart-2 - - - -  sm-icon-cart-3 - - - -  sm-icon-cart-4 - - - -  sm-icon-basket - -
    - -
    - - - \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/fonts/raw/license.txt b/src/Presentation/SmartStore.Web/Content/fonts/raw/license.txt deleted file mode 100644 index f963b349f2..0000000000 --- a/src/Presentation/SmartStore.Web/Content/fonts/raw/license.txt +++ /dev/null @@ -1,34 +0,0 @@ -Icon Set: WPZOOM Developer Icon Set -- http://www.wpzoom.com/wpzoom/new-freebie-wpzoom-developer-icon-set-154-free-icons/ -License: CC BY-SA 3.0 -- http://creativecommons.org/licenses/by-sa/3.0/ - - -Icon Set: Typicons -- http://typicons.com/ -License: CC BY-SA 3.0 -- http://creativecommons.org/licenses/by-sa/3.0/ - - -Icon Set: Entypo -- http://www.entypo.com/ -License: CC BY-SA 3.0 -- http://creativecommons.org/licenses/by-sa/3.0/ - - -Icon Set: Brankic1979 -- http://brankic1979.com/icons/ -License: Custom -- http://brankic1979.com/icons/ - - -Icon Set: Eco Ico -- http://dribbble.com/shots/665585-Eco-Ico -License: CC0 -- http://creativecommons.org/publicdomain/zero/1.0/ - - -Icon Set: Icon Minia -- http://dribbble.com/shots/598215-Icon-Minia-139-Vector-Icons -License: GPL V3 -- http://www.gnu.org/copyleft/gpl.html - - -Icon Set: IcoMoon - Free -- http://keyamoon.com/icomoon/ -License: CC BY-SA 3.0 -- http://creativecommons.org/licenses/by-sa/3.0/ - - -Icon Set: Broccolidry -- http://dribbble.com/shots/587469-Free-16px-Broccolidryiconsaniconsetitisfullof-icons -License: Aribitrary -- http://licence.visualidiot.com/ - - -Icon Set: Iconic -- http://somerandomdude.com/work/iconic/ -License: CC BY-SA 3.0 -- http://creativecommons.org/licenses/by-sa/3.0/us/ \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/fonts/raw/lte-ie7.js b/src/Presentation/SmartStore.Web/Content/fonts/raw/lte-ie7.js deleted file mode 100644 index b109d62b28..0000000000 --- a/src/Presentation/SmartStore.Web/Content/fonts/raw/lte-ie7.js +++ /dev/null @@ -1,74 +0,0 @@ -/* Use this script if you need to support IE 7 and IE 6. */ - -window.onload = function() { - function addIcon(el, entity) { - var html = el.innerHTML; - el.innerHTML = '' + entity + '' + html; - } - var icons = { - 'sm-icon-database' : '!', - 'sm-icon-user' : '"', - 'sm-icon-cog' : '#', - 'sm-icon-stats-up' : '$', - 'sm-icon-gift' : '%', - 'sm-icon-cube' : '&', - 'sm-icon-help' : ''', - 'sm-icon-loop' : '(', - 'sm-icon-tag' : ')', - 'sm-icon-gift-2' : '+', - 'sm-icon-home' : '-', - 'sm-icon-earth' : '.', - 'sm-icon-loop-alt2' : '3', - 'sm-icon-layers' : '4', - 'sm-icon-tag-stroke' : '6', - 'sm-icon-archive' : '<', - 'sm-icon-cog-2' : '=', - 'sm-icon-refresh' : '>', - 'sm-icon-tag-2' : '?', - 'sm-icon-tag-3' : '@', - 'sm-icon-chart' : 'C', - 'sm-icon-cube-2' : 'D', - 'sm-icon-megaphone' : 'E', - 'sm-icon-box' : 'F', - 'sm-icon-layout' : 'G', - 'sm-icon-layout-2' : 'H', - 'sm-icon-layout-3' : 'I', - 'sm-icon-layout-4' : 'J', - 'sm-icon-stats' : 'K', - 'sm-icon-discout' : 'L', - 'sm-icon-retweet' : 'M', - 'sm-icon-tags' : 'N', - 'sm-icon-users' : 'O', - 'sm-icon-user-2' : 'P', - 'sm-icon-price' : 'Q', - 'sm-icon-retweet-2' : 'R', - 'sm-icon-download' : 'S', - 'sm-icon-upload' : 'T', - 'sm-icon-home-2' : 'U', - 'sm-icon-dots-three' : 'V', - 'sm-icon-users-2' : 'W', - 'sm-icon-contact' : '*', - 'sm-icon-camera' : ',', - 'sm-icon-cart' : '/', - 'sm-icon-bag' : '0', - 'sm-icon-shopping' : '1', - 'sm-icon-cart-2' : '2', - 'sm-icon-cart-3' : '5', - 'sm-icon-cart-4' : '7', - 'sm-icon-basket' : '8' - }, - els = document.getElementsByTagName('*'), - i, attr, html, c, el; - for (i = 0; i < els.length; i += 1) { - el = els[i]; - attr = el.getAttribute('data-icon'); - if (attr) { - addIcon(el, attr); - } - c = el.className; - c = c.match(/sm-icon-[^\s'"]+/); - if (c && icons[c[0]]) { - addIcon(el, icons[c[0]]); - } - } -}; \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/fonts/raw/style.css b/src/Presentation/SmartStore.Web/Content/fonts/raw/style.css deleted file mode 100644 index cf8e62665b..0000000000 --- a/src/Presentation/SmartStore.Web/Content/fonts/raw/style.css +++ /dev/null @@ -1,180 +0,0 @@ -@font-face { - font-family: 'Fontastic'; - src:url('fonts/Fontastic.eot'); - src:url('fonts/Fontastic.eot?#iefix') format('embedded-opentype'), - url('fonts/Fontastic.svg#Fontastic') format('svg'), - url('fonts/Fontastic.woff') format('woff'), - url('fonts/Fontastic.ttf') format('truetype'); - font-weight: normal; - font-style: normal; -} - -/* Use the following CSS code if you want to use data attributes for inserting your icons */ -[data-icon]:before { - font-family: 'Fontastic'; - content: attr(data-icon); - speak: none; - font-weight: normal; - line-height: 1; - -webkit-font-smoothing: antialiased; -} - -/* Use the following CSS code if you want to have a class per icon */ -[class^="sm-icon-"]:before, [class*=" sm-icon-"]:before { - font-family: 'Fontastic'; - font-style: normal; - speak: none; - font-weight: normal; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.sm-icon-database:before { - content: "\21"; -} -.sm-icon-user:before { - content: "\22"; -} -.sm-icon-cog:before { - content: "\23"; -} -.sm-icon-stats-up:before { - content: "\24"; -} -.sm-icon-gift:before { - content: "\25"; -} -.sm-icon-cube:before { - content: "\26"; -} -.sm-icon-help:before { - content: "\27"; -} -.sm-icon-loop:before { - content: "\28"; -} -.sm-icon-tag:before { - content: "\29"; -} -.sm-icon-gift-2:before { - content: "\2b"; -} -.sm-icon-home:before { - content: "\2d"; -} -.sm-icon-earth:before { - content: "\2e"; -} -.sm-icon-loop-alt2:before { - content: "\33"; -} -.sm-icon-layers:before { - content: "\34"; -} -.sm-icon-tag-stroke:before { - content: "\36"; -} -.sm-icon-archive:before { - content: "\3c"; -} -.sm-icon-cog-2:before { - content: "\3d"; -} -.sm-icon-refresh:before { - content: "\3e"; -} -.sm-icon-tag-2:before { - content: "\3f"; -} -.sm-icon-tag-3:before { - content: "\40"; -} -.sm-icon-chart:before { - content: "\43"; -} -.sm-icon-cube-2:before { - content: "\44"; -} -.sm-icon-megaphone:before { - content: "\45"; -} -.sm-icon-box:before { - content: "\46"; -} -.sm-icon-layout:before { - content: "\47"; -} -.sm-icon-layout-2:before { - content: "\48"; -} -.sm-icon-layout-3:before { - content: "\49"; -} -.sm-icon-layout-4:before { - content: "\4a"; -} -.sm-icon-stats:before { - content: "\4b"; -} -.sm-icon-discout:before { - content: "\4c"; -} -.sm-icon-retweet:before { - content: "\4d"; -} -.sm-icon-tags:before { - content: "\4e"; -} -.sm-icon-users:before { - content: "\4f"; -} -.sm-icon-user-2:before { - content: "\50"; -} -.sm-icon-price:before { - content: "\51"; -} -.sm-icon-retweet-2:before { - content: "\52"; -} -.sm-icon-download:before { - content: "\53"; -} -.sm-icon-upload:before { - content: "\54"; -} -.sm-icon-home-2:before { - content: "\55"; -} -.sm-icon-dots-three:before { - content: "\56"; -} -.sm-icon-users-2:before { - content: "\57"; -} -.sm-icon-contact:before { - content: "\2a"; -} -.sm-icon-camera:before { - content: "\2c"; -} -.sm-icon-cart:before { - content: "\2f"; -} -.sm-icon-bag:before { - content: "\30"; -} -.sm-icon-shopping:before { - content: "\31"; -} -.sm-icon-cart-2:before { - content: "\32"; -} -.sm-icon-cart-3:before { - content: "\35"; -} -.sm-icon-cart-4:before { - content: "\37"; -} -.sm-icon-basket:before { - content: "\38"; -} diff --git a/src/Presentation/SmartStore.Web/Content/image-gallery/css/blueimp-gallery-custom.css b/src/Presentation/SmartStore.Web/Content/image-gallery/css/blueimp-gallery-custom.css index 20597a70b8..bc6725bd86 100644 --- a/src/Presentation/SmartStore.Web/Content/image-gallery/css/blueimp-gallery-custom.css +++ b/src/Presentation/SmartStore.Web/Content/image-gallery/css/blueimp-gallery-custom.css @@ -1,8 +1,61 @@  +.smartgallery-overlay, +.blueimp-gallery { + -webkit-transition: opacity 0.2s linear, transform 0.2s ease-out; + transition: opacity 0.2s linear, transform 0.2s ease-out; +} + +.smartgallery-overlay { + position: fixed; + z-index: 999998; + background: #000; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 0; +} +.smartgallery-overlay.in { + opacity: 0.3; +} + + +.blueimp-gallery { + left: 40px; + right: 40px; + top: 40px; + bottom: 40px; + background: #fff; + border: 1px solid rgba(0,0,0, 0.25); + border-radius: 4px; + border-top-right-radius: 0; + -webkit-box-shadow: 0px 4px 20px rgba(0,0,0, 0.35); + box-shadow: 0px 4px 20px rgba(0,0,0, 0.35); + -webkit-transform: translate(0, -75px) scale(0.9, 0.9); + -moz-transform: translate(0, -75px) scale(0.9, 0.9); + -ms-transform: translate(0, -75px) scale(0.9, 0.9); + transform: translate(0, -75px) scale(0.9, 0.9); +} + +.blueimp-gallery-display { + -webkit-transform: none; + -moz-transform: none; + -ms-transform: none; + transform: none; +} + + +.blueimp-gallery > .prev, +.blueimp-gallery > .next, +.blueimp-gallery > .close { + -webkit-transition: all 0.1s ease-in-out; + transition: all 0.1s ease-in-out; +} + .blueimp-gallery > .prev, .blueimp-gallery > .next { border: none; - border-radius: none; + border-radius: 0; text-shadow: none; font-family: initial; background: transparent; @@ -12,25 +65,36 @@ color: #bbb; opacity: 1; } +.blueimp-gallery > .prev { left: 0; } +.blueimp-gallery > .next { right: 0; } + .blueimp-gallery > .prev:hover, .blueimp-gallery > .next:hover { - color: #08c; + color: #888; + background-color: #f5f5f5; +} +.blueimp-gallery > .prev:active, +.blueimp-gallery > .next:active { + background-color: #eaeaea; } .blueimp-gallery > .close { text-shadow: none; opacity: 1 !important; - color: #aaa; padding: 8px 12px; - padding-top: 4px; + padding-top: 2px; border-bottom-left-radius: 6px; - background-color: #fff; + background-color: #c5c5c5; + color: #fff; margin: 0; top: 0; right: 0; } .blueimp-gallery > .close:hover { - color: #333; + background-color: #ee5f5b; +} +.blueimp-gallery > .close:active { + background-color: #ea3d38; } .blueimp-gallery > .indicator { @@ -39,6 +103,7 @@ right: 0; bottom: 0; background-color: #fff; + background-color: rgba(255,255,255, 0.5); opacity: 0.95; padding: 10px 0; -webkit-transition: all 0.4s ease-in-out; @@ -82,3 +147,5 @@ height: 25px; } } + + diff --git a/src/Presentation/SmartStore.Web/Content/install/install.less b/src/Presentation/SmartStore.Web/Content/install/install.less index 3bab8b1a26..bdb13586e8 100644 --- a/src/Presentation/SmartStore.Web/Content/install/install.less +++ b/src/Presentation/SmartStore.Web/Content/install/install.less @@ -11,17 +11,6 @@ /* Fonts (Chrome DirectWrite & FF fix) -------------------------------------------------------------- */ -@font-face { - font-family: "Segoe UI Light"; - font-weight: 100; - src: local("Segoe UI Light"), url("../fonts/segoeui-semilight.eot") format('embedded-opentype'), url("../fonts/segoeui-semilight.woff") format('woff'), url("../fonts/segoeui-semilight.ttf") format('truetype'); -} -@font-face { - font-family: "Segoe UI Semibold"; - font-weight: 100; - src: local("Segoe UI Semibold"), url("../fonts/segoeui-semibold.eot") format('embedded-opentype'), url("../fonts/segoeui-semibold.woff") format('woff'), url("../fonts/segoeui-semibold.ttf") format('truetype'); -} - // CSS Reset @import "~/Content/bootstrap/reset.less"; @@ -66,6 +55,7 @@ @import "~/Content/bootstrap/progress-bars.less"; // (MC) extra 3rd party or own components +@import "~/Content/bootstrap/custom/spinner.less"; @import "~/Content/bootstrap/custom/throbber.less"; // (MC) extra tweaks and corrections @@ -77,61 +67,132 @@ /* INSTALL STYLES --------------------------------------------------------*/ -@installBaseColor: #29b6d9; body { - background: @installBaseColor url('images/bg.png') 50% 0% no-repeat; - background-attachment: fixed; + background: #f5f5f5; +} + +input:not([type=checkbox]):not([type=radio]), +select { + display: block; + box-sizing: border-box; + width: 100%; + height: 34px; + box-shadow: none; + border-color: #d2d2d2; +} + +textarea { + height: initial; + min-height: 34px; + box-shadow: none; + border-color: #d2d2d2; +} + +.bg-top { + position: fixed; + z-index: 0; + background: #3F51B5; + width: 100%; + height: 140px; +} + +.dropdown-menu li > a { + padding: 8px 20px 8px 12px; + > .fa { margin-right: 4px; } +} + +.help-block { + color: #a1a1a1; + margin-top: 8px; + font-size: 12px; } .install-head { - padding: 8px 0; - line-height: 40px; + position: relative; + z-index: 1; + margin: 20px 0; vertical-align: middle; } .install-panel { - margin-top: 4px; + position: relative; + z-index: 1; + margin-top: 20px; margin-bottom: 20px; padding: 30px; + padding-bottom: 0; background-color: #fff; - background-color: rgba(255,255,255, .5); - .box-shadow(~'0 0 15px rgba(0,0,0, .15), 0 10px 6px -10px rgba(0,0,0, .4)'); - .border-radius(4px); + .box-shadow(~'0 1px 1px 0 rgba(0,0,0,.06),0 2px 5px 0 rgba(0,0,0,.2)'); + .border-radius(3px); +} + +.buttonbar { + text-align: left; + background: #fafafa; + padding: 20px 30px; + margin-left: -30px; + margin-right: -30px; + border-radius: 0 0 3px 3px; +} + +.btn-install { + font-size: 22px; + line-height: 32px; + font-weight: 600; + padding-left: 1.5em; + padding-right: 1.5em; } .install-title { margin: 0; - color: #fff; - color: lighten(@installBaseColor, 40%); - text-shadow: 0 1px 0 darken(@installBaseColor, 12%); font-family: @pageTitleFontFamily; font-weight: @pageTitleFontWeight; + font-size: 22px; + color: #fff; + padding-left: 8px; + vertical-align: bottom; +} + +.install-intro { + font-size: 16px; + line-height: 22px; + color: #818181; + margin-bottom: 30px; } -.install-content { - //padding: 0 30px; +.install-content fieldset { + margin-bottom: 30px; } .install-content legend { - font-family: @headingsFontFamily; - font-weight: @headingsFontWeight; + font-weight: 600; font-size: 22px; - border-bottom-color: lighten(@installBaseColor, 12%); + border-bottom-color: rgba(0,0,0, 0.1); color: @headingsColor; margin-bottom: 0; + padding-bottom: 8px; + + > .fa { + color: #717171; + margin-right: 4px; + } } .install-icon { - font-size: 32px; - line-height: 32px; - vertical-align: bottom; - margin-right: 4px; - display: none; + margin-right: 10px; +} + +.install-icon .fa-circle { + color: #f90; +} +.install-icon .fa-inverse { + font-size: 0.7em; } .form-horizontal .control-label { width: 240px; + text-align: left; } .form-horizontal .controls { @@ -143,8 +204,10 @@ label.control-label { } .field-validation-error { - color: red; + display: inline-block; + color: @red; text-shadow: none !important; + padding-top: 4px; } label.control-label { @@ -152,3 +215,54 @@ label.control-label { } +#navbar-tools { + + .active-tool() { + .box-shadow(~'inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)'); + } + + position: absolute; + z-index: 100; + right: 0; + top: 15px; + margin: 0; + + .btn, + .btn-group { + margin-top: 0; + } + + .open .navbar-tool { + .active-tool(); + } + + .navbar-tool { + color: rgba(255,255,255, .87); + border: none; + background: transparent; + .box-shadow(none); + padding: 8px 6px; + .transition(color 0.1s linear); + + &:hover { + color: #fff; + } + + &:active { + .active-tool(); + } + + > .fa { + font-size: 20px; + } + + span { + display: inline-block; + margin-left: 3px; + vertical-align: top; + } + + } +} + + diff --git a/src/Presentation/SmartStore.Web/Content/install/variables.custom.less b/src/Presentation/SmartStore.Web/Content/install/variables.custom.less index 76db5fcef8..2bcad0ceeb 100644 --- a/src/Presentation/SmartStore.Web/Content/install/variables.custom.less +++ b/src/Presentation/SmartStore.Web/Content/install/variables.custom.less @@ -5,6 +5,6 @@ // Nicer page titles // ------------------------- -@pageTitleFontFamily: 'Segoe UI Light', 'Segoe UI', Arial, Helvetica, sans-serif; -@pageTitleFontWeight: 100; +@pageTitleFontFamily: 'Segoe UI', Arial, Helvetica, sans-serif; +@pageTitleFontWeight: 400; @pageTitleColor: @infoText; // @linkColor; // #009fff; diff --git a/src/Presentation/SmartStore.Web/Content/install/variables.less b/src/Presentation/SmartStore.Web/Content/install/variables.less index e9195c956e..d4daaab546 100644 --- a/src/Presentation/SmartStore.Web/Content/install/variables.less +++ b/src/Presentation/SmartStore.Web/Content/install/variables.less @@ -53,8 +53,8 @@ @baseLineHeight: 18px; // 20px @altFontFamily: @serifFontFamily; -@headingsFontFamily: 'Segoe UI Semibold', 'Segoe UI', Tahoma, 'Helvetica Neue', Helvetica, Arial, 'sans-serif'; // inherit; // empty to use BS default, @baseFontFamily -@headingsFontWeight: 600 /* Semibold doesn't get bolder anyway */; //bold; // instead of browser default, bold +@headingsFontFamily: 'Segoe UI Semibold', Tahoma, 'Helvetica Neue', Helvetica, Arial, 'sans-serif'; // inherit; // empty to use BS default, @baseFontFamily +@headingsFontWeight: 400; @headingsColor: darken(@textColor, 8%); //inherit; // empty to use BS default, @textColor @@ -71,9 +71,9 @@ @paddingSmall: 2px 10px; // 26px @paddingMini: 1px 6px; // 24px -@baseBorderRadius: 0; +@baseBorderRadius: 3px; @borderRadiusLarge: 4px; -@borderRadiusSmall: 0; +@borderRadiusSmall: 2px; // Tables @@ -128,10 +128,10 @@ @dropdownLinkColor: @grayDark; -@dropdownLinkColorHover: @white; -@dropdownLinkColorActive: @dropdownLinkColor; -@dropdownLinkBackgroundActive: #666; //@linkColor; -@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive; +@dropdownLinkColorHover: inherit; +@dropdownLinkColorActive: inherit; +@dropdownLinkBackgroundActive: #f5f5f5; +@dropdownLinkBackgroundHover: #f5f5f5; @@ -272,13 +272,13 @@ // Default 940px grid // ------------------------- -@gridColumns: 24; // 12 -@gridColumnWidth: 20px; // 60px +@gridColumns: 12; +@gridColumnWidth: 60px; @gridGutterWidth: 20px; @gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); // 1200px min -@gridColumnWidth1200: 20px; // 70px; +@gridColumnWidth1200: 70px; @gridGutterWidth1200: 30px; @gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1)); diff --git a/src/Presentation/SmartStore.Web/Content/jquery.pnotify.default.css b/src/Presentation/SmartStore.Web/Content/jquery.pnotify.default.css index e9d39b7cb6..8e7e891425 100644 --- a/src/Presentation/SmartStore.Web/Content/jquery.pnotify.default.css +++ b/src/Presentation/SmartStore.Web/Content/jquery.pnotify.default.css @@ -57,11 +57,7 @@ margin: 0; width: 70px; border-top: none; padding: 0; - -webkit-border-top-left-radius: 0; - -moz-border-top-left-radius: 0; border-top-left-radius: 0; - -webkit-border-top-right-radius: 0; - -moz-border-top-right-radius: 0; border-top-right-radius: 0; /* Ensures history container is above notices. */ z-index: 10000; @@ -144,6 +140,9 @@ margin: 0; .ui-pnotify .ui-pnotify-text { padding-top: 10px; padding-bottom: 10px; + max-height: 500px; + overflow: auto; + position: relative; } .ui-pnotify-closer, @@ -164,11 +163,16 @@ margin: 0; filter: alpha(75); } -.ui-pnotify.stack-topcenter { +.ui-pnotify.stack-topcenter, +.ui-pnotify.stack-bottomcenter { left: 50% !important; margin-left: -250px !important; } +.ui-pnotify.stack-bottomcenter { + top: auto; +} + /*.ui-pnotify.stack-bottomright { right: auto; top: auto; diff --git a/src/Presentation/SmartStore.Web/Content/samples/0000730-apple-iphone-4s-weis.jpg b/src/Presentation/SmartStore.Web/Content/samples/0000730-apple-iphone-4s-weis.jpg deleted file mode 100644 index c7b7632297..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0000730-apple-iphone-4s-weis.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0000746_apple_125.png b/src/Presentation/SmartStore.Web/Content/samples/0000746_apple_125.png deleted file mode 100644 index 8f67d7198f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0000746_apple_125.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0000898_blackberry.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0000898_blackberry.jpeg deleted file mode 100644 index 50c8347334..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0000898_blackberry.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0000899_htc.png b/src/Presentation/SmartStore.Web/Content/samples/0000899_htc.png deleted file mode 100644 index 87c2622369..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0000899_htc.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0000937_best-grilling-recipes.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0000937_best-grilling-recipes.jpeg deleted file mode 100644 index 6bdb6ba261..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0000937_best-grilling-recipes.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0000938_cooking-for-two.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0000938_cooking-for-two.jpeg deleted file mode 100644 index 9b4fc2b154..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0000938_cooking-for-two.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0000953_dell-inspiron-one-23.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0000953_dell-inspiron-one-23.jpeg deleted file mode 100644 index 82eed932cc..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0000953_dell-inspiron-one-23.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0000993_trekstor.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0000993_trekstor.jpeg deleted file mode 100644 index 58e2073688..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0000993_trekstor.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0000994_western-digital.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0000994_western-digital.jpeg deleted file mode 100644 index 68aed0358b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0000994_western-digital.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0001085_canon.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0001085_canon.jpeg deleted file mode 100644 index 247cad7f8f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0001085_canon.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0001086_casio.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0001086_casio.jpeg deleted file mode 100644 index 779b43dd4b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0001086_casio.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0001087_panasonic.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0001087_panasonic.jpeg deleted file mode 100644 index afed07672a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0001087_panasonic.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0001328_certina_125.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0001328_certina_125.jpeg deleted file mode 100644 index 421ef60e3c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0001328_certina_125.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0001338_samsung.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0001338_samsung.jpeg deleted file mode 100644 index 10d9718d26..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0001338_samsung.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0001424-certina-ds-podium-big-size-herrenchronograph.jpg b/src/Presentation/SmartStore.Web/Content/samples/0001424-certina-ds-podium-big-size-herrenchronograph.jpg deleted file mode 100644 index b5a0b69bc1..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0001424-certina-ds-podium-big-size-herrenchronograph.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0001514_uhren.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0001514_uhren.jpeg deleted file mode 100644 index 9cdf0698ec..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0001514_uhren.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/0001591_festina.jpeg b/src/Presentation/SmartStore.Web/Content/samples/0001591_festina.jpeg deleted file mode 100644 index e9ce4f5b67..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/0001591_festina.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/Dell_Logo.png b/src/Presentation/SmartStore.Web/Content/samples/Dell_Logo.png deleted file mode 100644 index 95243c36fc..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/Dell_Logo.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/acer-aspire-one.jpg b/src/Presentation/SmartStore.Web/Content/samples/acer-aspire-one.jpg deleted file mode 100644 index d895c2e894..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/acer-aspire-one.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_cell_phones.jpeg b/src/Presentation/SmartStore.Web/Content/samples/category_cell_phones.jpeg deleted file mode 100644 index 032c91c0a6..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_cell_phones.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_cellphone.png b/src/Presentation/SmartStore.Web/Content/samples/category_cellphone.png new file mode 100644 index 0000000000..bf2471f624 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/category_cellphone.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_computers.jpeg b/src/Presentation/SmartStore.Web/Content/samples/category_computers.jpeg deleted file mode 100644 index 5046bfbb6a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_computers.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_computers.png b/src/Presentation/SmartStore.Web/Content/samples/category_computers.png new file mode 100644 index 0000000000..f6e70dfd26 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/category_computers.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_desktops.jpg b/src/Presentation/SmartStore.Web/Content/samples/category_desktops.jpg deleted file mode 100644 index e93a9d61ea..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_desktops.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_desktops.png b/src/Presentation/SmartStore.Web/Content/samples/category_desktops.png new file mode 100644 index 0000000000..4bb5505f21 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/category_desktops.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_digital_downloads.jpeg b/src/Presentation/SmartStore.Web/Content/samples/category_digital_downloads.jpeg deleted file mode 100644 index 4b360fe7c6..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_digital_downloads.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_digitaldownloads.jpg b/src/Presentation/SmartStore.Web/Content/samples/category_digitaldownloads.jpg new file mode 100644 index 0000000000..06ab388542 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/category_digitaldownloads.jpg differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_games.jpg b/src/Presentation/SmartStore.Web/Content/samples/category_games.jpg deleted file mode 100644 index dca41590e6..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_games.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_games.png b/src/Presentation/SmartStore.Web/Content/samples/category_games.png new file mode 100644 index 0000000000..644f53246a Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/category_games.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_gaming.jpg b/src/Presentation/SmartStore.Web/Content/samples/category_gaming.jpg deleted file mode 100644 index c0fb6e09f7..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_gaming.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_gaming.png b/src/Presentation/SmartStore.Web/Content/samples/category_gaming.png new file mode 100644 index 0000000000..d29055409d Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/category_gaming.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_gaming_accessories.jpg b/src/Presentation/SmartStore.Web/Content/samples/category_gaming_accessories.jpg deleted file mode 100644 index d07f68ad55..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_gaming_accessories.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_gaming_accessories.png b/src/Presentation/SmartStore.Web/Content/samples/category_gaming_accessories.png new file mode 100644 index 0000000000..67dfefbf77 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/category_gaming_accessories.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_gift_cards.jpeg b/src/Presentation/SmartStore.Web/Content/samples/category_gift_cards.jpeg deleted file mode 100644 index 12417a7e76..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_gift_cards.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_giftcards.png b/src/Presentation/SmartStore.Web/Content/samples/category_giftcards.png new file mode 100644 index 0000000000..6acc89c44c Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/category_giftcards.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_jeans.jpg b/src/Presentation/SmartStore.Web/Content/samples/category_jeans.jpg deleted file mode 100644 index db3bdd0d6f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_jeans.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_jewelry.jpeg b/src/Presentation/SmartStore.Web/Content/samples/category_jewelry.jpeg deleted file mode 100644 index b67f7af988..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_jewelry.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_notebooks.jpg b/src/Presentation/SmartStore.Web/Content/samples/category_notebooks.jpg deleted file mode 100644 index 954814bca2..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_notebooks.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_notebooks.png b/src/Presentation/SmartStore.Web/Content/samples/category_notebooks.png new file mode 100644 index 0000000000..b833828247 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/category_notebooks.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_shirts.jpg b/src/Presentation/SmartStore.Web/Content/samples/category_shirts.jpg deleted file mode 100644 index c4dc7e2e53..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_shirts.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_shoes.jpg b/src/Presentation/SmartStore.Web/Content/samples/category_shoes.jpg deleted file mode 100644 index 4fe8c731a7..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_shoes.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_software.jpg b/src/Presentation/SmartStore.Web/Content/samples/category_software.jpg deleted file mode 100644 index 6ade50cbdc..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/category_software.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/category_watches.png b/src/Presentation/SmartStore.Web/Content/samples/category_watches.png new file mode 100644 index 0000000000..1a734fd98f Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/category_watches.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer-blackberry.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-blackberry.png new file mode 100644 index 0000000000..7f896ade4c Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-blackberry.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer-canon.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-canon.png new file mode 100644 index 0000000000..fa5abcf8ae Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-canon.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer-casio.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-casio.png new file mode 100644 index 0000000000..5af767b8fd Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-casio.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer-certina.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-certina.png new file mode 100644 index 0000000000..81d0e5756a Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-certina.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer-dell.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-dell.png new file mode 100644 index 0000000000..135bf0842c Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-dell.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/HP_Logo_2012.svg.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-hp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/samples/HP_Logo_2012.svg.png rename to src/Presentation/SmartStore.Web/Content/samples/manufacturer-hp.png diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer-htc.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-htc.png new file mode 100644 index 0000000000..902f2d0ee4 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-htc.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer-msi.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-msi.png new file mode 100644 index 0000000000..f0d5d01bce Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-msi.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer-panasonic.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-panasonic.png new file mode 100644 index 0000000000..615feb3e17 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-panasonic.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer-samsung.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-samsung.png new file mode 100644 index 0000000000..11fc4c4228 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-samsung.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer-trekstor.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-trekstor.png new file mode 100644 index 0000000000..3aa6598fb1 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-trekstor.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer-westerndigital.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-westerndigital.png new file mode 100644 index 0000000000..3c02f36445 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer-westerndigital.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer_apple.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer_apple.png new file mode 100644 index 0000000000..5c55c60676 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer_apple.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer_festina.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer_festina.png new file mode 100644 index 0000000000..72ed1b6d86 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer_festina.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer_sony.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer_sony.png new file mode 100644 index 0000000000..7e1ba52e06 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer_sony.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/manufacturer_ubisoft.png b/src/Presentation/SmartStore.Web/Content/samples/manufacturer_ubisoft.png new file mode 100644 index 0000000000..a6f27b1644 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/manufacturer_ubisoft.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/msi_logo1.jpg b/src/Presentation/SmartStore.Web/Content/samples/msi_logo1.jpg deleted file mode 100644 index abeec7a72a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/msi_logo1.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_100giftcart.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_100giftcart.jpeg deleted file mode 100644 index 231fcaca34..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_100giftcart.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_25giftcart.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_25giftcart.jpeg deleted file mode 100644 index 231fcaca34..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_25giftcart.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_50giftcart.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_50giftcart.jpeg deleted file mode 100644 index 231fcaca34..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_50giftcart.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_5giftcart.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_5giftcart.jpeg deleted file mode 100644 index 231fcaca34..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_5giftcart.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_AdidasShoe_1.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_AdidasShoe_1.jpg deleted file mode 100644 index 6523ac7b8c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_AdidasShoe_1.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_AdidasShoe_2.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_AdidasShoe_2.jpg deleted file mode 100644 index fa6ec3d4a3..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_AdidasShoe_2.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_AdobePhotoshop.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_AdobePhotoshop.jpeg deleted file mode 100644 index 0d29d67982..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_AdobePhotoshop.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_ApcUps.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_ApcUps.jpeg deleted file mode 100644 index 1323c2e9f8..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_ApcUps.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Baseball.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_Baseball.jpeg deleted file mode 100644 index 33e72d4f26..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Baseball.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_BattleOfLA.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_BattleOfLA.jpeg deleted file mode 100644 index 534916f9a9..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_BattleOfLA.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_BattleOfLa_1.zip b/src/Presentation/SmartStore.Web/Content/samples/product_BattleOfLa_1.zip deleted file mode 100644 index b1111ffb63..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_BattleOfLa_1.zip and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_BestGrillingRecipes.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_BestGrillingRecipes.jpeg deleted file mode 100644 index c76f7fb3ad..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_BestGrillingRecipes.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_BestSkilletRecipes.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_BestSkilletRecipes.jpeg deleted file mode 100644 index 0bb4389d17..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_BestSkilletRecipes.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_BlackBerry.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_BlackBerry.jpeg deleted file mode 100644 index e6f5ecf335..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_BlackBerry.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamcoder.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamcoder.jpeg deleted file mode 100644 index f4b449d29f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamcoder.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_1.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_1.jpeg deleted file mode 100644 index 670adda240..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_1.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_2.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_2.jpeg deleted file mode 100644 index 34353be01a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_2.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_black.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_black.jpeg deleted file mode 100644 index 03f20f47fc..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_black.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_silver.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_silver.jpeg deleted file mode 100644 index febcc2fdeb..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_CanonCamera_silver.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Compaq.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_Compaq.jpeg deleted file mode 100644 index 79dab83c58..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Compaq.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_CookingForTwo.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_CookingForTwo.jpeg deleted file mode 100644 index d365a5e5f2..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_CookingForTwo.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Corel.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_Corel.jpeg deleted file mode 100644 index 38af2d8761..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Corel.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_CustomTShirt.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_CustomTShirt.jpeg deleted file mode 100644 index ae9ff4c6eb..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_CustomTShirt.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_DenimShort.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_DenimShort.jpg deleted file mode 100644 index b385b0e113..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_DenimShort.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Desktops_1.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_Desktops_1.jpeg deleted file mode 100644 index af6447c685..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Desktops_1.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Desktops_2.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_Desktops_2.jpeg deleted file mode 100644 index 1763c576d7..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Desktops_2.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Desktops_3.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_Desktops_3.jpeg deleted file mode 100644 index a3393bdea3..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Desktops_3.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_DiamondBracelet_1.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_DiamondBracelet_1.jpg deleted file mode 100644 index db6442e572..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_DiamondBracelet_1.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_DiamondBracelet_2.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_DiamondBracelet_2.jpg deleted file mode 100644 index 1722b4d67c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_DiamondBracelet_2.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_DiamondEarrings.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_DiamondEarrings.jpg deleted file mode 100644 index 776a0e15bd..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_DiamondEarrings.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_DiamondHeart.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_DiamondHeart.jpg deleted file mode 100644 index bc4c80dfe4..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_DiamondHeart.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_EatingWell.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_EatingWell.jpeg deleted file mode 100644 index a9a6d2b352..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_EatingWell.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_EngagementRing_1.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_EngagementRing_1.jpg deleted file mode 100644 index 646382cd14..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_EngagementRing_1.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_EngagementRing_2.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_EngagementRing_2.jpg deleted file mode 100644 index 8b32814aec..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_EngagementRing_2.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Etnies.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_Etnies.jpg deleted file mode 100644 index 67198f7d85..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Etnies.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_GolfBelt.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_GolfBelt.jpeg deleted file mode 100644 index 5a8c02faa2..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_GolfBelt.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Hp506.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_Hp506.jpeg deleted file mode 100644 index b28b93359c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Hp506.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion1.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion1.jpeg deleted file mode 100644 index c4ddd21a99..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion1.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion2_1.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion2_1.jpeg deleted file mode 100644 index 2817082a10..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion2_1.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion2_2.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion2_2.jpeg deleted file mode 100644 index b53aea9f6a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion2_2.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion3.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion3.jpeg deleted file mode 100644 index 33dafa51cb..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_HpPavilion3.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Kensington.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_Kensington.jpeg deleted file mode 100644 index a9118d95d4..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Kensington.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_LeatherHandbag_1.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_LeatherHandbag_1.jpg deleted file mode 100644 index 2a571a81cd..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_LeatherHandbag_1.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_LeatherHandbag_2.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_LeatherHandbag_2.jpg deleted file mode 100644 index fe3ed5b1de..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_LeatherHandbag_2.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_LeviJeans_1.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_LeviJeans_1.jpg deleted file mode 100644 index 07d37b98ec..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_LeviJeans_1.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_LeviJeans_2.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_LeviJeans_2.jpg deleted file mode 100644 index 448f58f249..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_LeviJeans_2.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_MedalOfHonor.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_MedalOfHonor.jpeg deleted file mode 100644 index 431d920e87..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_MedalOfHonor.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Mouse.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_Mouse.jpeg deleted file mode 100644 index 791d505799..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Mouse.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Notebooks.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_Notebooks.jpeg deleted file mode 100644 index c445114c2b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Notebooks.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_PokerFace.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_PokerFace.jpeg deleted file mode 100644 index 9791bd6e5a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_PokerFace.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_PokerFace_1.zip b/src/Presentation/SmartStore.Web/Content/samples/product_PokerFace_1.zip deleted file mode 100644 index b1111ffb63..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_PokerFace_1.zip and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_PokerFace_2.txt b/src/Presentation/SmartStore.Web/Content/samples/product_PokerFace_2.txt deleted file mode 100644 index d5420f50b9..0000000000 --- a/src/Presentation/SmartStore.Web/Content/samples/product_PokerFace_2.txt +++ /dev/null @@ -1 +0,0 @@ -Example: This is a sample download \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_RockabillyPolka.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_RockabillyPolka.jpg deleted file mode 100644 index 8a082b595f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_RockabillyPolka.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_SamsungPhone_1.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_SamsungPhone_1.jpeg deleted file mode 100644 index 5867616ae1..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_SamsungPhone_1.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_SamsungPhone_2.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_SamsungPhone_2.jpeg deleted file mode 100644 index 6b3fcbcfbf..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_SamsungPhone_2.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_SingleLadies.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_SingleLadies.jpeg deleted file mode 100644 index da8c307144..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_SingleLadies.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_SingleLadies_1.zip b/src/Presentation/SmartStore.Web/Content/samples/product_SingleLadies_1.zip deleted file mode 100644 index b1111ffb63..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_SingleLadies_1.zip and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_SingleLadies_2.txt b/src/Presentation/SmartStore.Web/Content/samples/product_SingleLadies_2.txt deleted file mode 100644 index d5420f50b9..0000000000 --- a/src/Presentation/SmartStore.Web/Content/samples/product_SingleLadies_2.txt +++ /dev/null @@ -1 +0,0 @@ -Example: This is a sample download \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Soccer.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_Soccer.jpeg deleted file mode 100644 index 5e99314375..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Soccer.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_SonyCamcoder.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_SonyCamcoder.jpeg deleted file mode 100644 index 9e38c3497d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_SonyCamcoder.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_Sunglasses.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_Sunglasses.jpg deleted file mode 100644 index 0a66d2726f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_Sunglasses.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_aceraspiresl1500.png b/src/Presentation/SmartStore.Web/Content/samples/product_aceraspiresl1500.png new file mode 100644 index 0000000000..000927614e Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_aceraspiresl1500.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_arrow.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_arrow.jpg deleted file mode 100644 index 1c4699a959..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_arrow.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_asuspc1000.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_asuspc1000.jpeg deleted file mode 100644 index 7e906ed097..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_asuspc1000.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_asuspc900.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_asuspc900.jpeg deleted file mode 100644 index 1265d90c40..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_asuspc900.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_bestgrillingrecipes.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_bestgrillingrecipes.jpg new file mode 100644 index 0000000000..0334788cd1 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_bestgrillingrecipes.jpg differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_certina_ds_podium_big.png b/src/Presentation/SmartStore.Web/Content/samples/product_certina_ds_podium_big.png new file mode 100644 index 0000000000..7df48d65bb Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_certina_ds_podium_big.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_cookingfortwo.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_cookingfortwo.jpg new file mode 100644 index 0000000000..437949ad2f Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_cookingfortwo.jpg differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_dellinspiron23.png b/src/Presentation/SmartStore.Web/Content/samples/product_dellinspiron23.png new file mode 100644 index 0000000000..b48ef29b6a Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_dellinspiron23.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_giftcart.png b/src/Presentation/SmartStore.Web/Content/samples/product_giftcart.png new file mode 100644 index 0000000000..a6a00b22c8 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_giftcart.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_hat.jpg b/src/Presentation/SmartStore.Web/Content/samples/product_hat.jpg deleted file mode 100644 index c235aa65c7..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_hat.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_playstation3dualshock3.png b/src/Presentation/SmartStore.Web/Content/samples/product_playstation3dualshock3.png new file mode 100644 index 0000000000..2eaa94c448 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_playstation3dualshock3.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_productPanasonic.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_productPanasonic.jpeg deleted file mode 100644 index 091d8ba215..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_productPanasonic.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_sony_dualshock4_wirelesscontroller.png b/src/Presentation/SmartStore.Web/Content/samples/product_sony_dualshock4_wirelesscontroller.png new file mode 100644 index 0000000000..9172cc2186 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_sony_dualshock4_wirelesscontroller.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps3_black.png b/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps3_black.png new file mode 100644 index 0000000000..f92800cca8 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps3_black.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps3_plus_game.png b/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps3_plus_game.png new file mode 100644 index 0000000000..32e2c2a753 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps3_plus_game.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps4.png b/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps4.png new file mode 100644 index 0000000000..5e0aee3a67 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps4.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps4_bundle.png b/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps4_bundle.png new file mode 100644 index 0000000000..31bb5266a0 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps4_bundle.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps4_camera.png b/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps4_camera.png new file mode 100644 index 0000000000..403f0242d9 Binary files /dev/null and b/src/Presentation/SmartStore.Web/Content/samples/product_sony_ps4_camera.png differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/product_wow.jpeg b/src/Presentation/SmartStore.Web/Content/samples/product_wow.jpeg deleted file mode 100644 index f17104d24d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/product_wow.jpeg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/sony-Dualshock3WirelessCont.jpg b/src/Presentation/SmartStore.Web/Content/samples/sony-Dualshock3WirelessCont.jpg deleted file mode 100644 index 365ce8ddde..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/sony-Dualshock3WirelessCont.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/sony-Dualshock4WirelessCont.jpg b/src/Presentation/SmartStore.Web/Content/samples/sony-Dualshock4WirelessCont.jpg deleted file mode 100644 index ca7a60cccb..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/sony-Dualshock4WirelessCont.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/sony-logo.jpg b/src/Presentation/SmartStore.Web/Content/samples/sony-logo.jpg deleted file mode 100644 index 2f39e83b98..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/sony-logo.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/sony-ps3-black.jpg b/src/Presentation/SmartStore.Web/Content/samples/sony-ps3-black.jpg deleted file mode 100644 index 466ccb335c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/sony-ps3-black.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/sony-ps3-plus-game.jpg b/src/Presentation/SmartStore.Web/Content/samples/sony-ps3-plus-game.jpg deleted file mode 100644 index d3550e6b34..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/sony-ps3-plus-game.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/sony-ps4-bundle.jpg b/src/Presentation/SmartStore.Web/Content/samples/sony-ps4-bundle.jpg deleted file mode 100644 index 115e53a71d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/sony-ps4-bundle.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/sony-ps4-camera.jpg b/src/Presentation/SmartStore.Web/Content/samples/sony-ps4-camera.jpg deleted file mode 100644 index f7707e927e..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/sony-ps4-camera.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/sony-ps4.jpg b/src/Presentation/SmartStore.Web/Content/samples/sony-ps4.jpg deleted file mode 100644 index 558034bc18..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/sony-ps4.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/samples/ubisoft-logo.jpg b/src/Presentation/SmartStore.Web/Content/samples/ubisoft-logo.jpg deleted file mode 100644 index fb4a743012..0000000000 Binary files a/src/Presentation/SmartStore.Web/Content/samples/ubisoft-logo.jpg and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css b/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css new file mode 100644 index 0000000000..2c821195fb --- /dev/null +++ b/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css @@ -0,0 +1,124 @@ +/* -------------------------------------------------------------- + SmartStore Component: smartstore.entitypicker.css +-------------------------------------------------------------- */ + +.entity-picker-list { + position: relative; + margin-left: -5px; + margin-right: -5px; +} + +.entity-picker-list .item-wrap { + position: relative; + box-sizing: border-box; + display: block; + float: left; + width: 33.3332%; + padding: 0 5px; +} + +.entity-picker-list .item { + position: relative; + box-sizing: border-box; + display: block; + height: 70px; + padding: 8px; + margin: 5px 0; + border-radius: 3px; +} +.entity-picker-list .item:not(.disable):hover { + cursor: pointer; +} +.entity-picker-list .item:not(.selected):hover { + background-color: #f5f5f5; +} + +.entity-picker-list .disable { + opacity: 0.4; +} + +.entity-picker-list .title { + font-weight: 400; + max-height: 36px; + overflow: hidden; + text-overflow: ellipsis; +} + +.entity-picker-list .highlight { + font-weight: 700; +} + +.entity-picker-list .summary { + color: #aaa; + font-size: 12px; + height: 18px; + line-height: 18px; + vertical-align: middle; + overflow: hidden; +} +.entity-picker-list .published { + margin-left: 1px; + margin-right: 6px; + font-size: 15px; + line-height: 18px; + vertical-align: sub; +} +.entity-picker-list .published.fa-globe { + color: inherit; +} +.entity-picker-list .published.fa-eye-slash { + color: #aaa; +} +.entity-picker-list .item.selected .published { + color: #fff; +} + +.entity-picker-list .list-footer { + clear: both; + padding: 12px; + text-align: center; +} + +.entity-picker-list .thumb, +.entity-picker-list .data { + position: relative; + box-sizing: border-box; + display: block; +} +.entity-picker-list .thumb { + display: table-cell; + width: 54px; + height: 54px; + max-width: 54px; + max-height: 54px; + padding: 2px; + text-align: center; + vertical-align: middle; +} +.entity-picker-list .item:hover .thumb, +.entity-picker-list .item.selected .thumb { + background: #fff; + box-shadow: 1px 1px 2px rgba(0,0,0, 0.1); +} +.entity-picker-list .thumb img { + max-width: 100%; + max-height: 100%; +} +.entity-picker-list .thumb + .data { + position: absolute; + left: 72px; + right: 8px; + top: 8px; + bottom: 8px; +} + +.entity-picker-list .item.selected { + background-color: #4060c0; +} +.entity-picker-list .item.selected .title { + color: #fff; +} +.entity-picker-list .item.selected .summary { + color: rgba(255,255,255, 0.6); +} + diff --git a/src/Presentation/SmartStore.Web/Content/smartstore.smartgallery.css b/src/Presentation/SmartStore.Web/Content/smartstore.smartgallery.css index 2e196f8168..3ab48b4a6b 100644 --- a/src/Presentation/SmartStore.Web/Content/smartstore.smartgallery.css +++ b/src/Presentation/SmartStore.Web/Content/smartstore.smartgallery.css @@ -9,6 +9,7 @@ } .sg-image-wrapper { + position: relative !important; width: 100%; min-height: 300px; /* just to avoid page load flickering. This is overwritten per inline css anyway */ margin-bottom: 5px; @@ -16,8 +17,6 @@ overflow: hidden; outline: 1px solid transparent; text-align: center; - /*background-color: #fff; - border: 1px solid #d5d5d5;*/ } .sg-image-wrapper > img { @@ -27,9 +26,10 @@ .sg-loader { position: absolute; z-index: 10; - top: 48%; - left: 48%; - border: 1px solid #ccc; + left: 0; + top: 0; + right: 0; + bottom: 0; } .sg-image { diff --git a/src/Presentation/SmartStore.Web/Controllers/BlogController.cs b/src/Presentation/SmartStore.Web/Controllers/BlogController.cs index a1aa99e822..bae872f9e9 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BlogController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BlogController.cs @@ -6,23 +6,24 @@ using System.Web.Routing; using SmartStore.Core; using SmartStore.Core.Caching; -using SmartStore.Core.Domain; using SmartStore.Core.Domain.Blogs; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; +using SmartStore.Core.Logging; using SmartStore.Services.Blogs; using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; -using SmartStore.Core.Logging; using SmartStore.Services.Media; using SmartStore.Services.Messages; using SmartStore.Services.Seo; using SmartStore.Services.Stores; -using SmartStore.Web.Framework; +using SmartStore.Utilities; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.UI.Captcha; using SmartStore.Web.Infrastructure.Cache; @@ -47,6 +48,7 @@ public partial class BlogController : PublicControllerBase private readonly ICacheManager _cacheManager; private readonly ICustomerActivityService _customerActivityService; private readonly IStoreMappingService _storeMappingService; + private readonly ILanguageService _languageService; private readonly MediaSettings _mediaSettings; private readonly BlogSettings _blogSettings; @@ -70,6 +72,7 @@ public BlogController(IBlogService blogService, ICacheManager cacheManager, ICustomerActivityService customerActivityService, IStoreMappingService storeMappingService, + ILanguageService languageService, MediaSettings mediaSettings, BlogSettings blogSettings, LocalizationSettings localizationSettings, @@ -88,6 +91,7 @@ public BlogController(IBlogService blogService, this._cacheManager = cacheManager; this._customerActivityService = customerActivityService; this._storeMappingService = storeMappingService; + this._languageService = languageService; this._mediaSettings = mediaSettings; this._blogSettings = blogSettings; @@ -117,7 +121,7 @@ protected void PrepareBlogPostModel(BlogPostModel model, BlogPost blogPost, bool model.Title = blogPost.Title; model.Body = blogPost.Body; model.AllowComments = blogPost.AllowComments; - model.AvatarPictureSize = _mediaSettings.AvatarPictureSize; // codehint: sm-add + model.AvatarPictureSize = _mediaSettings.AvatarPictureSize; model.CreatedOn = _dateTimeHelper.ConvertToUserTime(blogPost.CreatedOnUtc, DateTimeKind.Utc); model.Tags = blogPost.ParseTags().ToList(); model.NumberOfComments = blogPost.ApprovedCommentCount; @@ -228,28 +232,43 @@ public ActionResult BlogByMonth(BlogPagingFilteringModel command) return View("List", model); } + [Compress] public ActionResult ListRss(int languageId) { - var feed = new SyndicationFeed( - string.Format("{0}: Blog", _storeContext.CurrentStore.Name), - "Blog", - new Uri(_webHelper.GetStoreLocation(false)), - "BlogRSS", - DateTime.UtcNow); + DateTime? maxAge = null; + var protocol = _webHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var selfLink = Url.RouteUrl("BlogRSS", new { languageId = languageId }, protocol); + var blogLink = Url.RouteUrl("Blog", null, protocol); - if (!_blogSettings.Enabled) - return new RssActionResult() { Feed = feed }; + var title = "{0} - Blog".FormatInvariant(_storeContext.CurrentStore.Name); - var items = new List(); - var blogPosts = _blogService.GetAllBlogPosts(_storeContext.CurrentStore.Id, languageId, - null, null, 0, int.MaxValue); - foreach (var blogPost in blogPosts) - { - string blogPostUrl = Url.RouteUrl("BlogPost", new { SeName = blogPost.GetSeName(blogPost.LanguageId, ensureTwoPublishedLanguages: false) }, "http"); - items.Add(new SyndicationItem(blogPost.Title, blogPost.Body, new Uri(blogPostUrl), String.Format("Blog:{0}", blogPost.Id), blogPost.CreatedOnUtc)); - } - feed.Items = items; - return new RssActionResult() { Feed = feed }; + if (_blogSettings.MaxAgeInDays > 0) + maxAge = DateTime.UtcNow.Subtract(new TimeSpan(_blogSettings.MaxAgeInDays, 0, 0, 0)); + + var language = _languageService.GetLanguageById(languageId); + var feed = new SmartSyndicationFeed(new Uri(blogLink), title); + + feed.AddNamespaces(false); + feed.Init(selfLink, language); + + if (!_blogSettings.Enabled) + return new RssActionResult { Feed = feed }; + + var items = new List(); + var blogPosts = _blogService.GetAllBlogPosts(_storeContext.CurrentStore.Id, languageId, null, null, 0, int.MaxValue, false, maxAge); + + foreach (var blogPost in blogPosts) + { + var blogPostUrl = Url.RouteUrl("BlogPost", new { SeName = blogPost.GetSeName(blogPost.LanguageId, ensureTwoPublishedLanguages: false) }, "http"); + + var item = feed.CreateItem(blogPost.Title, blogPost.Body, blogPostUrl, blogPost.CreatedOnUtc); + + items.Add(item); + } + + feed.Items = items; + + return new RssActionResult { Feed = feed }; } public ActionResult BlogPost(int blogPostId) @@ -320,12 +339,9 @@ public ActionResult BlogCommentAdd(int blogPostId, BlogPostModel model, bool cap //activity log _customerActivityService.InsertActivity("PublicStore.AddBlogComment", _localizationService.GetResource("ActivityLog.PublicStore.AddBlogComment")); - //The text boxes should be cleared after a comment has been posted - //That' why we reload the page - TempData["sm.blog.addcomment.result"] = _localizationService.GetResource("Blog.Comments.SuccessfullyAdded"); + NotifySuccess(T("Blog.Comments.SuccessfullyAdded")); - // codehint: sm-add (MC) > append url fragment to route url - string url = UrlHelper.GenerateUrl( + var url = UrlHelper.GenerateUrl( routeName: "BlogPost", actionName: null, controllerName: null, @@ -337,10 +353,8 @@ public ActionResult BlogCommentAdd(int blogPostId, BlogPostModel model, bool cap requestContext: this.ControllerContext.RequestContext, includeImplicitMvcValues: true /*helps fill in the nulls above*/ ); - return Redirect(url); - // codehint: sm-delete - //return RedirectToRoute("BlogPost", new { SeName = blogPost.GetSeName(blogPost.LanguageId, ensureTwoPublishedLanguages: false) }); + return Redirect(url); } //If we got this far, something failed, redisplay form @@ -449,11 +463,12 @@ public ActionResult RssHeaderLink() if (!_blogSettings.Enabled || !_blogSettings.ShowHeaderRssUrl) return Content(""); - string link = string.Format("", + string link = string.Format("", Url.RouteUrl("BlogRSS", new { languageId = _workContext.WorkingLanguage.Id }, _webHelper.IsCurrentConnectionSecured() ? "https" : "http"), _storeContext.CurrentStore.Name); return Content(link); } + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 25390a8c2b..ed096936d8 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -4,7 +4,6 @@ using System.ServiceModel.Syndication; using System.Web.Mvc; using SmartStore.Core; -using SmartStore.Core.Domain; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Media; @@ -17,8 +16,11 @@ using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Seo; +using SmartStore.Utilities; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.Security; using SmartStore.Web.Models.Boards; @@ -77,7 +79,7 @@ public BoardsController(IForumService forumService, [NonAction] protected ForumTopicRowModel PrepareForumTopicRowModel(ForumTopic topic) { - var topicModel = new ForumTopicRowModel() + var topicModel = new ForumTopicRowModel { Id = topic.Id, Subject = topic.Subject, @@ -102,12 +104,12 @@ protected ForumTopicRowModel PrepareForumTopicRowModel(ForumTopic topic) [NonAction] protected ForumRowModel PrepareForumRowModel(Forum forum) { - var forumModel = new ForumRowModel() + var forumModel = new ForumRowModel { Id = forum.Id, - Name = forum.Name, + Name = forum.GetLocalized(x => x.Name), SeName = forum.GetSeName(), - Description = forum.Description, + Description = forum.GetLocalized(x => x.Description), NumTopics = forum.NumTopics, NumPosts = forum.NumPosts, LastPostId = forum.LastPostId, @@ -118,14 +120,16 @@ protected ForumRowModel PrepareForumRowModel(Forum forum) [NonAction] protected ForumGroupModel PrepareForumGroupModel(ForumGroup forumGroup) { - var forumGroupModel = new ForumGroupModel() + var forumGroupModel = new ForumGroupModel { Id = forumGroup.Id, - Name = forumGroup.Name, - SeName = forumGroup.GetSeName(), - Description = forumGroup.Description, + Name = forumGroup.GetLocalized(x => x.Name), + Description = forumGroup.GetLocalized(x => x.Description), + SeName = forumGroup.GetSeName() }; + var forums = _forumService.GetAllForumsByGroupId(forumGroup.Id); + foreach (var forum in forums) { var forumModel = PrepareForumRowModel(forum); @@ -170,12 +174,12 @@ protected IEnumerable ForumGroupsForumsList() foreach (var fg in forumGroups) { // Add the forum group with Value of 0 so it won't be used as a target forum - forumsList.Add(new SelectListItem { Text = fg.Name, Value = "0" }); + forumsList.Add(new SelectListItem { Text = fg.GetLocalized(x => x.Name), Value = "0" }); var forums = _forumService.GetAllForumsByGroupId(fg.Id); foreach (var f in forums) { - forumsList.Add(new SelectListItem { Text = string.Format("{0}{1}", separator, f.Name), Value = f.Id.ToString() }); + forumsList.Add(new SelectListItem { Text = string.Format("{0}{1}", separator, f.GetLocalized(x => x.Name)), Value = f.Id.ToString() }); } } @@ -195,10 +199,11 @@ public ActionResult Index() var forumGroups = _forumService.GetAllForumGroups(); - var model = new BoardsIndexModel() + var model = new BoardsIndexModel { CurrentTime = _dateTimeHelper.ConvertToUserTime(DateTime.UtcNow) }; + foreach (var forumGroup in forumGroups) { @@ -222,11 +227,13 @@ public ActionResult ActiveDiscussionsSmall() return Content(""); var model = new ActiveDiscussionsModel(); - foreach (var topic in topics) + + foreach (var topic in topics) { var topicModel = PrepareForumTopicRowModel(topic); model.ForumTopics.Add(topicModel); } + model.ViewAllLinkEnabled = true; model.ActiveDiscussionsFeedEnabled = _forumSettings.ActiveDiscussionsFeedEnabled; model.PostsPageSize = _forumSettings.PostsPageSize; @@ -245,59 +252,60 @@ public ActionResult ActiveDiscussions(int forumId = 0) var topics = _forumService.GetActiveTopics(forumId, topicLimit); var model = new ActiveDiscussionsModel(); + foreach (var topic in topics) { var topicModel = PrepareForumTopicRowModel(topic); model.ForumTopics.Add(topicModel); } + model.ViewAllLinkEnabled = false; model.ActiveDiscussionsFeedEnabled = _forumSettings.ActiveDiscussionsFeedEnabled; model.PostsPageSize = _forumSettings.PostsPageSize; + return View(model); } + [Compress] public ActionResult ActiveDiscussionsRss(int forumId = 0) { if (!_forumSettings.ForumsEnabled) - { return HttpNotFound(); - } - if (!_forumSettings.ActiveDiscussionsFeedEnabled) - { - return HttpNotFound(); - } + var language = _workContext.WorkingLanguage; + var protocol = _webHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var selfLink = Url.Action("ActiveDiscussionsRSS", "Boards", null, protocol); + var discussionLink = Url.Action("ActiveDiscussions", "Boards", null, protocol); - int topicLimit = _forumSettings.ActiveDiscussionsFeedCount; - var topics = _forumService.GetActiveTopics(forumId, topicLimit); - string url = Url.Action("ActiveDiscussionsRSS", null, (object)null, "http"); + var title = "{0} - {1}".FormatInvariant(_storeContext.CurrentStore.Name, T("Forum.ActiveDiscussionsFeedTitle")); - var feedTitle = _localizationService.GetResource("Forum.ActiveDiscussionsFeedTitle"); - var feedDescription = _localizationService.GetResource("Forum.ActiveDiscussionsFeedDescription"); + var feed = new SmartSyndicationFeed(new Uri(discussionLink), title, T("Forum.ActiveDiscussionsFeedDescription")); - var feed = new SyndicationFeed( - string.Format(feedTitle, _storeContext.CurrentStore.Name), - feedDescription, - new Uri(url), - "ActiveDiscussionsRSS", - DateTime.UtcNow); + feed.AddNamespaces(false); + feed.Init(selfLink, language); - var items = new List(); + if (!_forumSettings.ActiveDiscussionsFeedEnabled) + return new RssActionResult { Feed = feed }; - var viewsText = _localizationService.GetResource("Forum.Views"); - var repliesText = _localizationService.GetResource("Forum.Replies"); + var items = new List(); + var topics = _forumService.GetActiveTopics(forumId, _forumSettings.ActiveDiscussionsFeedCount); - foreach (var topic in topics) - { - string topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, "http"); - string content = String.Format("{2}: {0}, {3}: {1}", topic.NumReplies.ToString(), topic.Views.ToString(), repliesText, viewsText); + var viewsText = T("Forum.Views"); + var repliesText = T("Forum.Replies"); - items.Add(new SyndicationItem(topic.Subject, content, new Uri(topicUrl), - String.Format("Topic:{0}", topic.Id), (topic.LastPostTime ?? topic.UpdatedOnUtc))); - } - feed.Items = items; + foreach (var topic in topics) + { + string topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, "http"); + var synopsis = "{0}: {1}, {2}: {3}".FormatInvariant(repliesText, topic.NumReplies, viewsText, topic.Views); + + var item = feed.CreateItem(topic.Subject, synopsis, topicUrl, topic.LastPostTime ?? topic.UpdatedOnUtc); + + items.Add(item); + } - return new RssActionResult() { Feed = feed }; + feed.Items = items; + + return new RssActionResult { Feed = feed }; } public ActionResult ForumGroup(int id) @@ -328,9 +336,9 @@ public ActionResult Forum(int id, int page = 1) { var model = new ForumPageModel(); model.Id = forum.Id; - model.Name = forum.Name; + model.Name = forum.GetLocalized(x => x.Name); model.SeName = forum.GetSeName(); - model.Description = forum.Description; + model.Description = forum.GetLocalized(x => x.Description); int pageSize = 10; if (_forumSettings.TopicsPageSize > 0) @@ -347,20 +355,22 @@ public ActionResult Forum(int id, int page = 1) if (forumSubscription != null) { model.WatchForumText = _localizationService.GetResource("Forum.UnwatchForum"); - model.WatchForumSubscribed = true; // codehint: sm-add + model.WatchForumSubscribed = true; } } - var topics = _forumService.GetAllTopics(forum.Id, 0, string.Empty, - ForumSearchType.All, 0, (page - 1), pageSize); + var topics = _forumService.GetAllTopics(forum.Id, 0, string.Empty, ForumSearchType.All, 0, (page - 1), pageSize); + model.TopicPageSize = topics.PageSize; model.TopicTotalRecords = topics.TotalCount; model.TopicPageIndex = topics.PageIndex; + foreach (var topic in topics) { var topicModel = PrepareForumTopicRowModel(topic); model.ForumTopics.Add(topicModel); } + model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer); model.ForumFeedsEnabled = _forumSettings.ForumFeedsEnabled; model.PostsPageSize = _forumSettings.PostsPageSize; @@ -370,59 +380,51 @@ public ActionResult Forum(int id, int page = 1) return RedirectToRoute("Boards"); } + [Compress] public ActionResult ForumRss(int id) { if (!_forumSettings.ForumsEnabled) - { return HttpNotFound(); - } - if (!_forumSettings.ForumFeedsEnabled) - { - return HttpNotFound(); - } + var language = _workContext.WorkingLanguage; + var protocol = _webHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var selfLink = Url.Action("ForumRSS", "Boards", null, protocol); + var forumLink = Url.Action("Forum", "Boards", new { id = id }, protocol); - int topicLimit = _forumSettings.ForumFeedCount; - var forum = _forumService.GetForumById(id); + var feed = new SmartSyndicationFeed(new Uri(forumLink), _storeContext.CurrentStore.Name, T("Forum.ForumFeedDescription")); - if (forum != null) - { - //Order by newest topic posts & limit the number of topics to return - var topics = _forumService.GetAllTopics(forum.Id, 0, string.Empty, - ForumSearchType.All, 0, 0, topicLimit); + feed.AddNamespaces(false); + feed.Init(selfLink, language); - string url = Url.Action("ForumRSS", null, new { id = forum.Id }, "http"); + if (!_forumSettings.ForumFeedsEnabled) + return new RssActionResult { Feed = feed }; - var feedTitle = _localizationService.GetResource("Forum.ForumFeedTitle"); - var feedDescription = _localizationService.GetResource("Forum.ForumFeedDescription"); + var forum = _forumService.GetForumById(id); - var feed = new SyndicationFeed( - string.Format(feedTitle, _storeContext.CurrentStore.Name, forum.Name), - feedDescription, - new Uri(url), - string.Format("ForumRSS:{0}", forum.Id), - DateTime.UtcNow); + if (forum == null) + return new RssActionResult { Feed = feed }; - var items = new List(); + feed.Title = new TextSyndicationContent("{0} - {1}".FormatInvariant(_storeContext.CurrentStore.Name, forum.GetLocalized(x => x.Name, language.Id))); - var viewsText = _localizationService.GetResource("Forum.Views"); - var repliesText = _localizationService.GetResource("Forum.Replies"); + var items = new List(); + var topics = _forumService.GetAllTopics(id, 0, string.Empty, ForumSearchType.All, 0, 0, _forumSettings.ForumFeedCount); - foreach (var topic in topics) - { - string topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, "http"); - string content = string.Format("{2}: {0}, {3}: {1}", topic.NumReplies.ToString(), topic.Views.ToString(), repliesText, viewsText); + var viewsText = T("Forum.Views"); + var repliesText = T("Forum.Replies"); - items.Add(new SyndicationItem(topic.Subject, content, new Uri(topicUrl), String.Format("Topic:{0}", topic.Id), - (topic.LastPostTime ?? topic.UpdatedOnUtc))); - } + foreach (var topic in topics) + { + string topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, "http"); + var synopsis = "{0}: {1}, {2}: {3}".FormatInvariant(repliesText, topic.NumReplies, viewsText, topic.Views); - feed.Items = items; + var item = feed.CreateItem(topic.Subject, synopsis, topicUrl, topic.LastPostTime ?? topic.UpdatedOnUtc); - return new RssActionResult() { Feed = feed }; - } + items.Add(item); + } + + feed.Items = items; - return new RssActionResult() { Feed = new SyndicationFeed() }; + return new RssActionResult { Feed = feed }; } [HttpPost] @@ -444,18 +446,18 @@ public ActionResult ForumWatch(int id) return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, - forum.Id, 0, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, forum.Id, 0, 0, 1).FirstOrDefault(); if (forumSubscription == null) { - forumSubscription = new ForumSubscription() + forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), CustomerId = _workContext.CurrentCustomer.Id, ForumId = forum.Id, CreatedOnUtc = DateTime.UtcNow }; + _forumService.InsertSubscription(forumSubscription); subscribed = true; returnText = unwatchTopic; @@ -481,8 +483,8 @@ public ActionResult Topic(int id, int page = 1) if (forumTopic != null) { //load posts - var posts = _forumService.GetAllPosts(forumTopic.Id, 0, string.Empty, - page - 1, _forumSettings.PostsPageSize); + var posts = _forumService.GetAllPosts(forumTopic.Id, 0, string.Empty, page - 1, _forumSettings.PostsPageSize); + // if no posts area loaded, redirect to the first page if (posts.Count == 0 && page > 1) { @@ -508,19 +510,20 @@ public ActionResult Topic(int id, int page = 1) { model.WatchTopicText = _localizationService.GetResource("Forum.WatchTopic"); - var forumTopicSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, - 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumTopicSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); if (forumTopicSubscription != null) { model.WatchTopicText = _localizationService.GetResource("Forum.UnwatchTopic"); } } - model.PostsPageIndex = posts.PageIndex; + + model.PostsPageIndex = posts.PageIndex; model.PostsPageSize = posts.PageSize; model.PostsTotalRecords = posts.TotalCount; + foreach (var post in posts) { - var forumPostModel = new ForumPostModel() + var forumPostModel = new ForumPostModel { Id = post.Id, ForumTopicId = post.TopicId, @@ -541,12 +544,14 @@ public ActionResult Topic(int id, int page = 1) SignaturesEnabled = _forumSettings.SignaturesEnabled, FormattedSignature = post.Customer.GetAttribute(SystemCustomerAttributeNames.Signature).FormatForumSignatureText(), }; + //created on string if (_forumSettings.RelativeDateTimeFormattingEnabled) forumPostModel.PostCreatedOnStr = post.CreatedOnUtc.RelativeFormat(true, "f"); else forumPostModel.PostCreatedOnStr =_dateTimeHelper.ConvertToUserTime(post.CreatedOnUtc, DateTimeKind.Utc).ToString("f"); - //avatar + + //avatar if (_customerSettings.AllowCustomersToUploadAvatars) { var customer = post.Customer; @@ -555,6 +560,7 @@ public ActionResult Topic(int id, int page = 1) avatarUrl = _pictureService.GetDefaultPictureUrl(_mediaSettings.AvatarPictureSize, PictureType.Avatar); forumPostModel.CustomerAvatarUrl = avatarUrl; } + //location forumPostModel.ShowCustomersLocation = _customerSettings.ShowCustomersLocation; if (_customerSettings.ShowCustomersLocation) @@ -594,18 +600,18 @@ public ActionResult TopicWatch(int id) return Json(new { Subscribed = subscribed, Text = returnText, Error = true }); } - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, - 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); if (forumSubscription == null) { - forumSubscription = new ForumSubscription() + forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), CustomerId = _workContext.CurrentCustomer.Id, TopicId = forumTopic.Id, CreatedOnUtc = DateTime.UtcNow }; + _forumService.InsertSubscription(forumSubscription); subscribed = true; returnText = unwatchTopic; @@ -638,6 +644,7 @@ public ActionResult TopicMove(int id) model.Id = forumTopic.Id; model.TopicSeName = forumTopic.GetSeName(); model.ForumSelected = forumTopic.ForumId; + return View(model); } @@ -681,6 +688,7 @@ public ActionResult TopicDelete(int id) { return new HttpUnauthorizedResult(); } + var forum = _forumService.GetForumById(forumTopic.ForumId); _forumService.DeleteTopic(forumTopic); @@ -717,13 +725,14 @@ public ActionResult TopicCreate(int id) model.Id = 0; model.IsEdit = false; model.ForumId = forum.Id; - model.ForumName = forum.Name; + model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); model.ForumEditor = _forumSettings.ForumEditor; model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(_workContext.CurrentCustomer); model.TopicPriorities = ForumTopicTypesList(); model.IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer); model.Subscribed = false; + return View(model); } @@ -767,18 +776,16 @@ public ActionResult TopicCreate(EditForumTopicModel model) } var topicType = ForumTopicType.Normal; - + var nowUtc = DateTime.UtcNow; string ipAddress = _webHelper.GetCurrentIpAddress(); - var nowUtc = DateTime.UtcNow; - if (_forumService.IsCustomerAllowedToSetTopicPriority(_workContext.CurrentCustomer)) { topicType = (ForumTopicType) Enum.ToObject(typeof (ForumTopicType), model.TopicTypeId); } //forum topic - var forumTopic = new ForumTopic() + var forumTopic = new ForumTopic { ForumId = forum.Id, CustomerId = _workContext.CurrentCustomer.Id, @@ -787,10 +794,11 @@ public ActionResult TopicCreate(EditForumTopicModel model) CreatedOnUtc = nowUtc, UpdatedOnUtc = nowUtc }; + _forumService.InsertTopic(forumTopic, true); //forum post - var forumPost = new ForumPost() + var forumPost = new ForumPost { TopicId = forumTopic.Id, CustomerId = _workContext.CurrentCustomer.Id, @@ -799,6 +807,7 @@ public ActionResult TopicCreate(EditForumTopicModel model) CreatedOnUtc = nowUtc, UpdatedOnUtc = nowUtc }; + _forumService.InsertPost(forumPost, false); //update forum topic @@ -807,6 +816,7 @@ public ActionResult TopicCreate(EditForumTopicModel model) forumTopic.LastPostCustomerId = forumPost.CustomerId; forumTopic.LastPostTime = forumPost.CreatedOnUtc; forumTopic.UpdatedOnUtc = nowUtc; + _forumService.UpdateTopic(forumTopic); //subscription @@ -814,7 +824,7 @@ public ActionResult TopicCreate(EditForumTopicModel model) { if (model.Subscribed) { - var forumSubscription = new ForumSubscription() + var forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), CustomerId = _workContext.CurrentCustomer.Id, @@ -838,7 +848,7 @@ public ActionResult TopicCreate(EditForumTopicModel model) model.TopicPriorities = ForumTopicTypesList(); model.IsEdit = false; model.ForumId = forum.Id; - model.ForumName = forum.Name; + model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); model.Id = 0; model.IsCustomerAllowedToSetTopicPriority = _forumService.IsCustomerAllowedToSetTopicPriority(_workContext.CurrentCustomer); @@ -873,12 +883,13 @@ public ActionResult TopicEdit(int id) return RedirectToRoute("Boards"); } + var firstPost = forumTopic.GetFirstPost(_forumService); var model = new EditForumTopicModel(); + model.IsEdit = true; model.TopicPriorities = ForumTopicTypesList(); - model.ForumName = forum.Name; + model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); - var firstPost = forumTopic.GetFirstPost(_forumService); model.Text = firstPost.Text; model.Subject = forumTopic.Subject; model.TopicTypeId = forumTopic.TopicTypeId; @@ -892,8 +903,7 @@ public ActionResult TopicEdit(int id) //subscription if (model.IsCustomerAllowedToSubscribe) { - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, - 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); model.Subscribed = forumSubscription != null; } @@ -959,6 +969,7 @@ public ActionResult TopicEdit(EditForumTopicModel model) forumTopic.TopicTypeId = (int) topicType; forumTopic.Subject = subject; forumTopic.UpdatedOnUtc = nowUtc; + _forumService.UpdateTopic(forumTopic); //forum post @@ -987,14 +998,13 @@ public ActionResult TopicEdit(EditForumTopicModel model) //subscription if (_forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer)) { - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, - 0, forumTopic.Id, 0, 1).FirstOrDefault - (); + var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); + if (model.Subscribed) { if (forumSubscription == null) { - forumSubscription = new ForumSubscription() + forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), CustomerId = _workContext.CurrentCustomer.Id, @@ -1026,7 +1036,7 @@ public ActionResult TopicEdit(EditForumTopicModel model) // redisplay form model.TopicPriorities = ForumTopicTypesList(); model.IsEdit = true; - model.ForumName = forum.Name; + model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); model.ForumId = forum.Id; model.ForumEditor = _forumSettings.ForumEditor; @@ -1100,13 +1110,13 @@ public ActionResult PostCreate(int id, int? quote) return RedirectToRoute("Boards"); } - var model = new EditForumPostModel() + var model = new EditForumPostModel { Id = 0, ForumTopicId = forumTopic.Id, IsEdit = false, ForumEditor = _forumSettings.ForumEditor, - ForumName = forum.Name, + ForumName = forum.GetLocalized(x => x.Name), ForumTopicSubject = forumTopic.Subject, ForumTopicSeName = forumTopic.GetSeName(), IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer), @@ -1172,11 +1182,10 @@ public ActionResult PostCreate(EditForumPostModel model) if (maxPostLength > 0 && text.Length > maxPostLength) text = text.Substring(0, maxPostLength); + var nowUtc = DateTime.UtcNow; string ipAddress = _webHelper.GetCurrentIpAddress(); - DateTime nowUtc = DateTime.UtcNow; - - var forumPost = new ForumPost() + var forumPost = new ForumPost { TopicId = forumTopic.Id, CustomerId = _workContext.CurrentCustomer.Id, @@ -1185,18 +1194,18 @@ public ActionResult PostCreate(EditForumPostModel model) CreatedOnUtc = nowUtc, UpdatedOnUtc = nowUtc }; + _forumService.InsertPost(forumPost, true); //subscription if (_forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer)) { - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, - 0, forumPost.TopicId, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumPost.TopicId, 0, 1).FirstOrDefault(); if (model.Subscribed) { if (forumSubscription == null) { - forumSubscription = new ForumSubscription() + forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), CustomerId = _workContext.CurrentCustomer.Id, @@ -1222,6 +1231,7 @@ public ActionResult PostCreate(EditForumPostModel model) int pageIndex = (_forumService.CalculateTopicPageIndex(forumPost.TopicId, pageSize, forumPost.Id) + 1); var url = string.Empty; + if (pageIndex > 1) { url = Url.RouteUrl("TopicSlug", new { id = forumPost.TopicId, slug = forumPost.ForumTopic.GetSeName(), page = pageIndex }); @@ -1230,6 +1240,7 @@ public ActionResult PostCreate(EditForumPostModel model) { url = Url.RouteUrl("TopicSlug", new { id = forumPost.TopicId, slug = forumPost.ForumTopic.GetSeName() }); } + return Redirect(string.Format("{0}#{1}", url, forumPost.Id)); } catch (Exception ex) @@ -1244,7 +1255,7 @@ public ActionResult PostCreate(EditForumPostModel model) return RedirectToRoute("Boards"); model.IsEdit = false; - model.ForumName = forum.Name; + model.ForumName = forum.GetLocalized(x => x.Name); model.ForumTopicId = forumTopic.Id; model.ForumTopicSubject = forumTopic.Subject; model.ForumTopicSeName = forumTopic.GetSeName(); @@ -1284,13 +1295,13 @@ public ActionResult PostEdit(int id) } - var model = new EditForumPostModel() + var model = new EditForumPostModel { Id = forumPost.Id, ForumTopicId = forumTopic.Id, IsEdit = true, ForumEditor = _forumSettings.ForumEditor, - ForumName = forum.Name, + ForumName = forum.GetLocalized(x => x.Name), ForumTopicSubject = forumTopic.Subject, ForumTopicSeName = forumTopic.GetSeName(), IsCustomerAllowedToSubscribe = _forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer), @@ -1301,8 +1312,7 @@ public ActionResult PostEdit(int id) //subscription if (model.IsCustomerAllowedToSubscribe) { - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, - 0, forumTopic.Id, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumTopic.Id, 0, 1).FirstOrDefault(); model.Subscribed = forumSubscription != null; } @@ -1345,7 +1355,7 @@ public ActionResult PostEdit(EditForumPostModel model) { try { - DateTime nowUtc = DateTime.UtcNow; + var nowUtc = DateTime.UtcNow; var text = model.Text; var maxPostLength = _forumSettings.PostMaxLength; @@ -1356,24 +1366,25 @@ public ActionResult PostEdit(EditForumPostModel model) forumPost.UpdatedOnUtc = nowUtc; forumPost.Text = text; + _forumService.UpdatePost(forumPost); //subscription if (_forumService.IsCustomerAllowedToSubscribe(_workContext.CurrentCustomer)) { - var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, - 0, forumPost.TopicId, 0, 1).FirstOrDefault(); + var forumSubscription = _forumService.GetAllSubscriptions(_workContext.CurrentCustomer.Id, 0, forumPost.TopicId, 0, 1).FirstOrDefault(); if (model.Subscribed) { if (forumSubscription == null) { - forumSubscription = new ForumSubscription() + forumSubscription = new ForumSubscription { SubscriptionGuid = Guid.NewGuid(), CustomerId = _workContext.CurrentCustomer.Id, TopicId = forumPost.TopicId, CreatedOnUtc = nowUtc }; + _forumService.InsertSubscription(forumSubscription); } } @@ -1393,6 +1404,7 @@ public ActionResult PostEdit(EditForumPostModel model) } int pageIndex = (_forumService.CalculateTopicPageIndex(forumPost.TopicId, pageSize, forumPost.Id) + 1); var url = string.Empty; + if (pageIndex > 1) { url = Url.RouteUrl("TopicSlug", new { id = forumPost.TopicId, slug = forumPost.ForumTopic.GetSeName(), page = pageIndex }); @@ -1401,6 +1413,7 @@ public ActionResult PostEdit(EditForumPostModel model) { url = Url.RouteUrl("TopicSlug", new { id = forumPost.TopicId, slug = forumPost.ForumTopic.GetSeName() }); } + return Redirect(string.Format("{0}#{1}", url, forumPost.Id)); } catch (Exception ex) @@ -1411,7 +1424,7 @@ public ActionResult PostEdit(EditForumPostModel model) //redisplay form model.IsEdit = true; - model.ForumName = forum.Name; + model.ForumName = forum.GetLocalized(x => x.Name); model.ForumTopicId = forumTopic.Id; model.ForumTopicSubject = forumTopic.Subject; model.ForumTopicSeName = forumTopic.GetSeName(); @@ -1422,8 +1435,7 @@ public ActionResult PostEdit(EditForumPostModel model) return View(model); } - public ActionResult Search(string searchterms, bool? adv, string forumId, - string within, string limitDays, int page = 1) + public ActionResult Search(string searchterms, bool? adv, string forumId, string within, string limitDays, int page = 1) { if (!_forumSettings.ForumsEnabled) { @@ -1436,98 +1448,99 @@ public ActionResult Search(string searchterms, bool? adv, string forumId, // Create the values for the "Limit results to previous" select list var limitList = new List - { - new SelectListItem - { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.AllResults"), - Value = "0" - }, - new SelectListItem - { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.1day"), - Value = "1" - }, - new SelectListItem - { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.7days"), - Value = "7" - }, - new SelectListItem - { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.2weeks"), - Value = "14" - }, - new SelectListItem - { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.1month"), - Value = "30" - }, - new SelectListItem - { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.3months"), - Value = "92" - }, - new SelectListItem - { - Text= _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.6months"), - Value = "183" - }, - new SelectListItem - { - Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.1year"), - Value = "365" - } - }; + { + new SelectListItem + { + Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.AllResults"), + Value = "0" + }, + new SelectListItem + { + Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.1day"), + Value = "1" + }, + new SelectListItem + { + Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.7days"), + Value = "7" + }, + new SelectListItem + { + Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.2weeks"), + Value = "14" + }, + new SelectListItem + { + Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.1month"), + Value = "30" + }, + new SelectListItem + { + Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.3months"), + Value = "92" + }, + new SelectListItem + { + Text= _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.6months"), + Value = "183" + }, + new SelectListItem + { + Text = _localizationService.GetResource("Forum.Search.LimitResultsToPrevious.1year"), + Value = "365" + } + }; + model.LimitList = limitList; // Create the values for the "Search in forum" select list var forumsSelectList = new List(); - forumsSelectList.Add( - new SelectListItem - { - Text = _localizationService.GetResource("Forum.Search.SearchInForum.All"), - Value = "0", - Selected = true, - }); + forumsSelectList.Add(new SelectListItem + { + Text = _localizationService.GetResource("Forum.Search.SearchInForum.All"), + Value = "0", + Selected = true, + }); + var separator = "--"; var forumGroups = _forumService.GetAllForumGroups(); foreach (var fg in forumGroups) { // Add the forum group with value as '-' so it can't be used as a target forum id - forumsSelectList.Add(new SelectListItem { Text = fg.Name, Value = "-" }); + forumsSelectList.Add(new SelectListItem { Text = fg.GetLocalized(x => x.Name), Value = "-" }); var forums = _forumService.GetAllForumsByGroupId(fg.Id); foreach (var f in forums) { - forumsSelectList.Add( - new SelectListItem - { - Text = string.Format("{0}{1}", separator, f.Name), - Value = f.Id.ToString() - }); + forumsSelectList.Add(new SelectListItem + { + Text = string.Format("{0}{1}", separator, f.GetLocalized(x => x.Name)), + Value = f.Id.ToString() + }); } } model.ForumList = forumsSelectList; // Create the values for "Search within" select list var withinList = new List - { - new SelectListItem - { - Value = ((int)ForumSearchType.All).ToString(), - Text = _localizationService.GetResource("Forum.Search.SearchWithin.All") - }, - new SelectListItem - { - Value = ((int)ForumSearchType.TopicTitlesOnly).ToString(), - Text = _localizationService.GetResource("Forum.Search.SearchWithin.TopicTitlesOnly") - }, - new SelectListItem - { - Value = ((int)ForumSearchType.PostTextOnly).ToString(), - Text = _localizationService.GetResource("Forum.Search.SearchWithin.PostTextOnly") - } - }; + { + new SelectListItem + { + Value = ((int)ForumSearchType.All).ToString(), + Text = _localizationService.GetResource("Forum.Search.SearchWithin.All") + }, + new SelectListItem + { + Value = ((int)ForumSearchType.TopicTitlesOnly).ToString(), + Text = _localizationService.GetResource("Forum.Search.SearchWithin.TopicTitlesOnly") + }, + new SelectListItem + { + Value = ((int)ForumSearchType.PostTextOnly).ToString(), + Text = _localizationService.GetResource("Forum.Search.SearchWithin.PostTextOnly") + } + }; + model.WithinList = withinList; int forumIdSelected; @@ -1558,8 +1571,7 @@ public ActionResult Search(string searchterms, bool? adv, string forumId, if (searchterms.Length < searchTermMinimumLength) { - throw new SmartException(string.Format(_localizationService.GetResource("Forum.SearchTermMinimumLengthIsNCharacters"), - searchTermMinimumLength)); + throw new SmartException(string.Format(_localizationService.GetResource("Forum.SearchTermMinimumLengthIsNCharacters"), searchTermMinimumLength)); } ForumSearchType searchWithin = 0; @@ -1575,11 +1587,12 @@ public ActionResult Search(string searchterms, bool? adv, string forumId, pageSize = _forumSettings.SearchResultsPageSize; } - var topics = _forumService.GetAllTopics(forumIdSelected, 0, searchterms, searchWithin, - limitResultsToPrevious, page - 1, pageSize); + var topics = _forumService.GetAllTopics(forumIdSelected, 0, searchterms, searchWithin, limitResultsToPrevious, page - 1, pageSize); + model.TopicPageSize = topics.PageSize; model.TopicTotalRecords = topics.TotalCount; model.TopicPageIndex = topics.PageIndex; + foreach (var topic in topics) { var topicModel = PrepareForumTopicRowModel(topic); @@ -1656,7 +1669,7 @@ public ActionResult ForumBreadcrumb(int? forumGroupId, int? forumId, int? forumT if (forum != null) { model.ForumId = forum.Id; - model.ForumName = forum.Name; + model.ForumName = forum.GetLocalized(x => x.Name); model.ForumSeName = forum.GetSeName(); } @@ -1664,7 +1677,7 @@ public ActionResult ForumBreadcrumb(int? forumGroupId, int? forumId, int? forumT if (forumGroup != null) { model.ForumGroupId = forumGroup.Id; - model.ForumGroupName = forumGroup.Name; + model.ForumGroupName = forumGroup.GetLocalized(x => x.Name); model.ForumGroupSeName = forumGroup.GetSeName(); } @@ -1673,5 +1686,4 @@ public ActionResult ForumBreadcrumb(int? forumGroupId, int? forumId, int? forumT #endregion } - } diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs index fc289ba397..9db9fa78bb 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs @@ -8,7 +8,6 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Media; -using SmartStore.Core.Localization; using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Common; @@ -20,7 +19,10 @@ using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Stores; +using SmartStore.Utilities; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.UI; using SmartStore.Web.Infrastructure.Cache; @@ -29,7 +31,7 @@ namespace SmartStore.Web.Controllers { - public partial class CatalogController : PublicControllerBase + public partial class CatalogController : PublicControllerBase { #region Fields @@ -104,22 +106,10 @@ public CatalogController( this._catalogSettings = catalogSettings; this._helper = helper; - - T = NullLocalizer.Instance; } #endregion - #region Properties - - public Localizer T - { - get; - set; - } - - #endregion - #region Categories [RequireHttpsByConfigAttribute(SslRequirement.No)] @@ -143,10 +133,13 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command return HttpNotFound(); //'Continue shopping' URL - _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, - SystemCustomerAttributeNames.LastContinueShoppingPage, - _services.WebHelper.GetThisPageUrl(false), - _services.StoreContext.CurrentStore.Id); + if (!_services.WorkContext.CurrentCustomer.IsSystemAccount) + { + _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, + SystemCustomerAttributeNames.LastContinueShoppingPage, + _services.WebHelper.GetThisPageUrl(false), + _services.StoreContext.CurrentStore.Id); + } if (command.PageNumber <= 0) command.PageNumber = 1; @@ -156,6 +149,11 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command command.ViewMode = category.DefaultViewMode; } + if (command.OrderBy == (int)ProductSortingEnum.Initial) + { + command.OrderBy = (int)_catalogSettings.DefaultSortOrder; + } + var model = category.ToModel(); _helper.PreparePagingFilteringModel(model.PagingFilteringContext, command, new PageSizeContext @@ -170,6 +168,7 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command var selectedPriceRange = model.PagingFilteringContext.PriceRangeFilter.GetSelectedPriceRange(_services.WebHelper, category.PriceRanges); decimal? minPriceConverted = null; decimal? maxPriceConverted = null; + if (selectedPriceRange != null) { if (selectedPriceRange.From.HasValue) @@ -179,8 +178,6 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command maxPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(selectedPriceRange.To.Value, _services.WorkContext.WorkingCurrency); } - - //category breadcrumb model.DisplayCategoryBreadcrumb = _catalogSettings.CategoryBreadcrumbEnabled; if (model.DisplayCategoryBreadcrumb) @@ -189,18 +186,17 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command } model.DisplayFilter = _catalogSettings.FilterEnabled; - model.ShowSubcategoriesAboveProductLists = _catalogSettings.ShowSubcategoriesAboveProductLists; + model.SubCategoryDisplayType = _catalogSettings.SubCategoryDisplayType; var customerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); - - //subcategories + // subcategories model.SubCategories = _categoryService .GetAllCategoriesByParentCategoryId(categoryId) .Select(x => { var subCatName = x.GetLocalized(y => y.Name); - var subCatModel = new CategoryModel.SubCategoryModel() + var subCatModel = new CategoryModel.SubCategoryModel { Id = x.Id, Name = subCatName, @@ -217,7 +213,7 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command { PictureId = x.PictureId.GetValueOrDefault(), FullSizeImageUrl = _pictureService.GetPictureUrl(picture), - ImageUrl = _pictureService.GetPictureUrl(picture, targetSize: pictureSize), + ImageUrl = _pictureService.GetPictureUrl(picture, pictureSize, !_catalogSettings.HideCategoryDefaultPictures), Title = string.Format(T("Media.Category.ImageLinkTitleFormat"), subCatName), AlternateText = string.Format(T("Media.Category.ImageAlternateTextFormat"), subCatName) }; @@ -229,8 +225,6 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command .ToList(); - - // Featured products if (!_catalogSettings.IgnoreFeaturedProducts) { @@ -264,7 +258,9 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command if (featuredProducts != null) { - model.FeaturedProducts = _helper.PrepareProductOverviewModels(featuredProducts, prepareColorAttributes: true).ToList(); + model.FeaturedProducts = _helper.PrepareProductOverviewModels( + featuredProducts, + prepareColorAttributes: true).ToList(); } } @@ -285,7 +281,11 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command var filterQuery = _filterService.ProductFilter(context); var products = new PagedList(filterQuery, command.PageIndex, command.PageSize); - model.Products = _helper.PrepareProductOverviewModels(products, prepareColorAttributes: true).ToList(); + model.Products = _helper.PrepareProductOverviewModels( + products, + prepareColorAttributes: true, + prepareManufacturers: command.ViewMode.IsCaseInsensitiveEqual("list")).ToList(); + model.PagingFilteringContext.LoadPagedList(products); } else @@ -317,7 +317,10 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command var products = _productService.SearchProducts(ctx2); - model.Products = _helper.PrepareProductOverviewModels(products, prepareColorAttributes: true).ToList(); + model.Products = _helper.PrepareProductOverviewModels( + products, + prepareColorAttributes: true, + prepareManufacturers: command.ViewMode.IsCaseInsensitiveEqual("list")).ToList(); model.PagingFilteringContext.LoadPagedList(products); //model.PagingFilteringContext.ViewMode = viewMode; @@ -363,7 +366,7 @@ public ActionResult ProductBreadcrumb(int productId) { var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); if (!_catalogSettings.CategoryBreadcrumbEnabled) return Content(""); @@ -403,7 +406,7 @@ public ActionResult HomepageCategories() { PictureId = x.PictureId.GetValueOrDefault(), FullSizeImageUrl = _pictureService.GetPictureUrl(x.PictureId.GetValueOrDefault()), - ImageUrl = _pictureService.GetPictureUrl(x.PictureId.GetValueOrDefault(), pictureSize), + ImageUrl = _pictureService.GetPictureUrl(x.PictureId.GetValueOrDefault(), pictureSize, !_catalogSettings.HideCategoryDefaultPictures), Title = string.Format(T("Media.Category.ImageLinkTitleFormat"), catModel.Name), AlternateText = string.Format(T("Media.Category.ImageAlternateTextFormat"), catModel.Name) }; @@ -440,11 +443,14 @@ public ActionResult Manufacturer(int manufacturerId, CatalogPagingFilteringModel if (!_storeMappingService.Authorize(manufacturer)) return HttpNotFound(); - //'Continue shopping' URL - _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, - SystemCustomerAttributeNames.LastContinueShoppingPage, - _services.WebHelper.GetThisPageUrl(false), - _services.StoreContext.CurrentStore.Id); + //'Continue shopping' URL + if (!_services.WorkContext.CurrentCustomer.IsSystemAccount) + { + _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, + SystemCustomerAttributeNames.LastContinueShoppingPage, + _services.WebHelper.GetThisPageUrl(false), + _services.StoreContext.CurrentStore.Id); + } if (command.PageNumber <= 0) command.PageNumber = 1; @@ -452,7 +458,12 @@ public ActionResult Manufacturer(int manufacturerId, CatalogPagingFilteringModel var model = manufacturer.ToModel(); // prepare picture model - model.PictureModel = this.PrepareManufacturerPictureModel(manufacturer, model.Name); + model.PictureModel = _helper.PrepareManufacturerPictureModel(manufacturer, model.Name); + + if (command.OrderBy == (int)ProductSortingEnum.Initial) + { + command.OrderBy = (int)_catalogSettings.DefaultSortOrder; + } _helper.PreparePagingFilteringModel(model.PagingFilteringContext, command, new PageSizeContext { @@ -527,7 +538,10 @@ public ActionResult Manufacturer(int manufacturerId, CatalogPagingFilteringModel var products = _productService.SearchProducts(ctx2); - model.Products = _helper.PrepareProductOverviewModels(products, prepareColorAttributes: true).ToList(); + model.Products = _helper.PrepareProductOverviewModels( + products, + prepareColorAttributes: false, + prepareManufacturers: command.ViewMode.IsCaseInsensitiveEqual("list")).ToList(); model.PagingFilteringContext.LoadPagedList(products); //model.PagingFilteringContext.ViewMode = viewMode; @@ -553,72 +567,53 @@ public ActionResult Manufacturer(int manufacturerId, CatalogPagingFilteringModel public ActionResult ManufacturerAll() { var model = new List(); - var manufacturers = _manufacturerService.GetAllManufacturers(); + var manufacturers = _manufacturerService.GetAllManufacturers(null, _services.StoreContext.CurrentStore.Id); foreach (var manufacturer in manufacturers) { var modelMan = manufacturer.ToModel(); // prepare picture model - modelMan.PictureModel = this.PrepareManufacturerPictureModel(manufacturer, modelMan.Name); + modelMan.PictureModel = _helper.PrepareManufacturerPictureModel(manufacturer, modelMan.Name); model.Add(modelMan); } return View(model); } - private PictureModel PrepareManufacturerPictureModel(Manufacturer manufacturer, string localizedName) - { - var model = new PictureModel(); - - int pictureSize = _mediaSettings.ManufacturerThumbPictureSize; - var manufacturerPictureCacheKey = string.Format(ModelCacheEventConsumer.MANUFACTURER_PICTURE_MODEL_KEY, - manufacturer.Id, - pictureSize, - true, - _services.WorkContext.WorkingLanguage.Id, - _services.WebHelper.IsCurrentConnectionSecured(), - _services.StoreContext.CurrentStore.Id); - - model = _services.Cache.Get(manufacturerPictureCacheKey, () => - { - var pictureModel = new PictureModel() - { - PictureId = manufacturer.PictureId.GetValueOrDefault(), - FullSizeImageUrl = _pictureService.GetPictureUrl(manufacturer.PictureId.GetValueOrDefault()), - ImageUrl = _pictureService.GetPictureUrl(manufacturer.PictureId.GetValueOrDefault(), pictureSize), - Title = string.Format(T("Media.Manufacturer.ImageLinkTitleFormat"), localizedName), - AlternateText = string.Format(T("Media.Manufacturer.ImageAlternateTextFormat"), localizedName) - }; - return pictureModel; - }); - - return model; - } - [ChildActionOnly] public ActionResult ManufacturerNavigation(int currentManufacturerId) { - if (_catalogSettings.ManufacturersBlockItemsToDisplay == 0) + if (_catalogSettings.ManufacturersBlockItemsToDisplay == 0 || _catalogSettings.ShowManufacturersOnHomepage == false) return Content(""); - string cacheKey = string.Format(ModelCacheEventConsumer.MANUFACTURER_NAVIGATION_MODEL_KEY, currentManufacturerId, _services.WorkContext.WorkingLanguage.Id, _services.StoreContext.CurrentStore.Id); + var cacheKey = string.Format(ModelCacheEventConsumer.MANUFACTURER_NAVIGATION_MODEL_KEY, + currentManufacturerId, + !_catalogSettings.HideManufacturerDefaultPictures, + _services.WorkContext.WorkingLanguage.Id, + _services.StoreContext.CurrentStore.Id); + var cacheModel = _services.Cache.Get(cacheKey, () => { var currentManufacturer = _manufacturerService.GetManufacturerById(currentManufacturerId); - var manufacturers = _manufacturerService.GetAllManufacturers(); - var model = new ManufacturerNavigationModel() + var manufacturers = _manufacturerService.GetAllManufacturers(null, _services.StoreContext.CurrentStore.Id); + + var model = new ManufacturerNavigationModel { - TotalManufacturers = manufacturers.Count + TotalManufacturers = manufacturers.Count, + DisplayManufacturers = _catalogSettings.ShowManufacturersOnHomepage, + DisplayImages = _catalogSettings.ShowManufacturerPictures }; foreach (var manufacturer in manufacturers.Take(_catalogSettings.ManufacturersBlockItemsToDisplay)) { - var modelMan = new ManufacturerBriefInfoModel() + var modelMan = new ManufacturerBriefInfoModel { Id = manufacturer.Id, Name = manufacturer.GetLocalized(x => x.Name), SeName = manufacturer.GetSeName(), + PictureUrl = _pictureService.GetPictureUrl(manufacturer.PictureId.GetValueOrDefault(), _mediaSettings.ManufacturerThumbPictureSize, + !_catalogSettings.HideManufacturerDefaultPictures), IsActive = currentManufacturer != null && currentManufacturer.Id == manufacturer.Id, }; model.Manufacturers.Add(modelMan); @@ -642,16 +637,20 @@ public ActionResult HomepageBestSellers(int? productThumbPictureSize) if (!_catalogSettings.ShowBestsellersOnHomepage || _catalogSettings.NumberOfBestsellersOnHomepage == 0) return Content(""); - //load and cache report - var report = _services.Cache.Get(string.Format(ModelCacheEventConsumer.HOMEPAGE_BESTSELLERS_IDS_KEY, _services.StoreContext.CurrentStore.Id), () => - _orderReportService.BestSellersReport(_services.StoreContext.CurrentStore.Id, null, null, null, null, null, 0, _catalogSettings.NumberOfBestsellersOnHomepage)); + // load and cache report + var report = _services.Cache.Get(string.Format(ModelCacheEventConsumer.HOMEPAGE_BESTSELLERS_IDS_KEY, _services.StoreContext.CurrentStore.Id), () => + { + return _orderReportService.BestSellersReport(_services.StoreContext.CurrentStore.Id, null, null, null, null, null, 0, _catalogSettings.NumberOfBestsellersOnHomepage); + }); - //load products + // load products var products = _productService.GetProductsByIds(report.Select(x => x.ProductId).ToArray()); - //ACL and store mapping + + // ACL and store mapping products = products.Where(p => _aclService.Authorize(p) && _storeMappingService.Authorize(p)).ToList(); - //prepare model - var model = new HomePageBestsellersModel() + + // prepare model + var model = new HomePageBestsellersModel { UseSmallProductBox = _catalogSettings.UseSmallProductBoxOnHomePage, Products = _helper.PrepareProductOverviewModels(products, true, true, productThumbPictureSize).ToList() @@ -738,6 +737,11 @@ public ActionResult ProductsByTag(int productTagId, CatalogPagingFilteringModel TagName = productTag.GetLocalized(y => y.Name) }; + if (command.OrderBy == (int)ProductSortingEnum.Initial) + { + command.OrderBy = (int)_catalogSettings.DefaultSortOrder; + } + _helper.PreparePagingFilteringModel(model.PagingFilteringContext, command, new PageSizeContext { AllowCustomersToSelectPageSize = _catalogSettings.ProductsByTagAllowCustomersToSelectPageSize, @@ -760,7 +764,10 @@ public ActionResult ProductsByTag(int productTagId, CatalogPagingFilteringModel var products = _productService.SearchProducts(ctx); - model.Products = _helper.PrepareProductOverviewModels(products, prepareColorAttributes: true).ToList(); + model.Products = _helper.PrepareProductOverviewModels( + products, + prepareColorAttributes: true, + prepareManufacturers: command.ViewMode.IsCaseInsensitiveEqual("list")).ToList(); model.PagingFilteringContext.LoadPagedList(products); //model.PagingFilteringContext.ViewMode = viewMode; @@ -779,7 +786,7 @@ public ActionResult ProductTagsAll() .OrderBy(x => x.GetLocalized(y => y.Name)) .Select(x => { - var ptModel = new ProductTagModel() + var ptModel = new ProductTagModel { Id = x.Id, Name = x.GetLocalized(y => y.Name), @@ -851,39 +858,67 @@ public ActionResult RecentlyAddedProducts(CatalogPagingFilteringModel command) return View(model); } + [Compress] public ActionResult RecentlyAddedProductsRss() { - var feed = new SyndicationFeed( - string.Format("{0}: {1}", _services.StoreContext.CurrentStore.Name, T("RSS.RecentlyAddedProducts")), - T("RSS.InformationAboutProducts"), - new Uri(_services.WebHelper.GetStoreLocation(false)), - "RecentlyAddedProductsRSS", - DateTime.UtcNow); + var protocol = _services.WebHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var selfLink = Url.RouteUrl("RecentlyAddedProductsRSS", null, protocol); + var recentProductsLink = Url.RouteUrl("RecentlyAddedProducts", null, protocol); + + var title = "{0} - {1}".FormatInvariant(_services.StoreContext.CurrentStore.Name, T("RSS.RecentlyAddedProducts")); + + var feed = new SmartSyndicationFeed(new Uri(recentProductsLink), title, T("RSS.InformationAboutProducts")); + + feed.AddNamespaces(true); + feed.Init(selfLink, _services.WorkContext.WorkingLanguage); if (!_catalogSettings.RecentlyAddedProductsEnabled) - return new RssActionResult() { Feed = feed }; + return new RssActionResult { Feed = feed }; var items = new List(); + var searchContext = new ProductSearchContext + { + LanguageId = _services.WorkContext.WorkingLanguage.Id, + OrderBy = ProductSortingEnum.CreatedOn, + PageSize = _catalogSettings.RecentlyAddedProductsNumber, + StoreId = _services.StoreContext.CurrentStoreIdIfMultiStoreMode, + VisibleIndividuallyOnly = true + }; - var ctx = new ProductSearchContext(); - ctx.LanguageId = _services.WorkContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.CreatedOn; - ctx.PageSize = _catalogSettings.RecentlyAddedProductsNumber; - ctx.StoreId = _services.StoreContext.CurrentStoreIdIfMultiStoreMode; - ctx.VisibleIndividuallyOnly = true; - - var products = _productService.SearchProducts(ctx); + var products = _productService.SearchProducts(searchContext); + var storeUrl = _services.StoreContext.CurrentStore.Url; foreach (var product in products) { string productUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }, "http"); - if (!String.IsNullOrEmpty(productUrl)) + if (productUrl.HasValue()) { - items.Add(new SyndicationItem(product.GetLocalized(x => x.Name), product.GetLocalized(x => x.ShortDescription), new Uri(productUrl), String.Format("RecentlyAddedProduct:{0}", product.Id), product.CreatedOnUtc)); + var item = feed.CreateItem( + product.GetLocalized(x => x.Name), + product.GetLocalized(x => x.ShortDescription), + productUrl, + product.CreatedOnUtc, + product.FullDescription); + + try + { + // we add only the first picture + var picture = product.ProductPictures.OrderBy(x => x.DisplayOrder).Select(x => x.Picture).FirstOrDefault(); + + if (picture != null) + { + feed.AddEnclosue(item, picture, _pictureService.GetPictureUrl(picture, _mediaSettings.ProductDetailsPictureSize, false, storeUrl)); + } + } + catch { } + + items.Add(item); } } + feed.Items = items; - return new RssActionResult() { Feed = feed }; + + return new RssActionResult { Feed = feed }; } #endregion @@ -985,10 +1020,13 @@ public ActionResult CompareProducts() IncludeShortDescriptionInCompareProducts = _catalogSettings.IncludeShortDescriptionInCompareProducts, IncludeFullDescriptionInCompareProducts = _catalogSettings.IncludeFullDescriptionInCompareProducts, }; + var products = _compareProductsService.GetComparedProducts(); - _helper.PrepareProductOverviewModels(products, prepareSpecificationAttributes: true) + + _helper.PrepareProductOverviewModels(products, prepareSpecificationAttributes: true, prepareFullDescription: true, isCompareList: true) .ToList() .ForEach(model.Products.Add); + return View(model); } @@ -1027,13 +1065,15 @@ public ActionResult CompareSummary() public ActionResult FlyoutCompare() { - var model = new CompareProductsModel() + var model = new CompareProductsModel { IncludeShortDescriptionInCompareProducts = _catalogSettings.IncludeShortDescriptionInCompareProducts, IncludeFullDescriptionInCompareProducts = _catalogSettings.IncludeFullDescriptionInCompareProducts, }; + var products = _compareProductsService.GetComparedProducts(); - _helper.PrepareProductOverviewModels(products, prepareSpecificationAttributes: true) + + _helper.PrepareProductOverviewModels(products, prepareSpecificationAttributes: true, isCompareList: true) .ToList() .ForEach(model.Products.Add); @@ -1051,17 +1091,23 @@ public ActionResult Search(SearchModel model, SearchPagingFilteringModel command if (model == null) model = new SearchModel(); - //'Continue shopping' URL - _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, - SystemCustomerAttributeNames.LastContinueShoppingPage, - _services.WebHelper.GetThisPageUrl(false), - _services.StoreContext.CurrentStore.Id); + // 'Continue shopping' URL + if (!_services.WorkContext.CurrentCustomer.IsSystemAccount) + { + _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, + SystemCustomerAttributeNames.LastContinueShoppingPage, + _services.WebHelper.GetThisPageUrl(false), + _services.StoreContext.CurrentStore.Id); + } if (command.PageSize <= 0) - command.PageSize = _catalogSettings.SearchPageProductsPerPage; //_catalogSettings.SearchPageProductsPerPage; + command.PageSize = _catalogSettings.SearchPageProductsPerPage; if (command.PageNumber <= 0) command.PageNumber = 1; + if (command.OrderBy == (int)ProductSortingEnum.Initial) + command.OrderBy = (int)_catalogSettings.DefaultSortOrder; + _helper.PreparePagingFilteringModel(model.PagingFilteringContext, command, new PageSizeContext { AllowCustomersToSelectPageSize = _catalogSettings.ProductSearchAllowCustomersToSelectPageSize, @@ -1069,17 +1115,10 @@ public ActionResult Search(SearchModel model, SearchPagingFilteringModel command PageSizeOptions = _catalogSettings.ProductSearchPageSizeOptions }); - if (model.Q == null) - model.Q = ""; - model.Q = model.Q.Trim(); + model.Q = model.Q.EmptyNull().Trim(); // Build AvailableCategories - // first empty entry - model.AvailableCategories.Add(new SelectListItem() - { - Value = "0", - Text = T("Common.All") - }); + model.AvailableCategories.Add(new SelectListItem { Value = "0", Text = T("Common.All") }); var navModel = _helper.PrepareCategoryNavigationModel(0, 0); @@ -1088,10 +1127,10 @@ public ActionResult Search(SearchModel model, SearchPagingFilteringModel command if (node.IsRoot) return; - int id = node.Value.EntityId; + var id = node.Value.EntityId; var breadcrumb = node.GetBreadcrumb().Select(x => x.Text).ToArray(); - model.AvailableCategories.Add(new SelectListItem() + model.AvailableCategories.Add(new SelectListItem { Value = id.ToString(), Text = String.Join(" > ", breadcrumb), @@ -1102,21 +1141,21 @@ public ActionResult Search(SearchModel model, SearchPagingFilteringModel command var manufacturers = _manufacturerService.GetAllManufacturers(); if (manufacturers.Count > 0) { - model.AvailableManufacturers.Add(new SelectListItem() - { - Value = "0", - Text = T("Common.All") - }); + model.AvailableManufacturers.Add(new SelectListItem { Value = "0", Text = T("Common.All") }); + foreach (var m in manufacturers) - model.AvailableManufacturers.Add(new SelectListItem() + { + model.AvailableManufacturers.Add(new SelectListItem { Value = m.Id.ToString(), Text = m.GetLocalized(x => x.Name), Selected = model.Mid == m.Id }); + } } IPagedList products = new PagedList(new List(), 0, 1); + // only search if query string search keyword is set (used to avoid searching or displaying search term min length error message on /search page load) if (Request.Params["Q"] != null) { @@ -1127,38 +1166,38 @@ public ActionResult Search(SearchModel model, SearchPagingFilteringModel command else { var categoryIds = new List(); - int manufacturerId = 0; + var manufacturerId = 0; decimal? minPriceConverted = null; decimal? maxPriceConverted = null; - bool searchInDescriptions = false; + var searchInDescriptions = _catalogSettings.SearchDescriptions; + if (model.As) { - //advanced search + // advanced search var categoryId = model.Cid; if (categoryId > 0) { categoryIds.Add(categoryId); if (model.Isc) { - //include subcategories + // include subcategories categoryIds.AddRange(_helper.GetChildCategoryIds(categoryId)); } } - manufacturerId = model.Mid; - //min price - if (!string.IsNullOrEmpty(model.Pf)) + // min price + if (model.Pf.HasValue()) { - decimal minPrice = decimal.Zero; + var minPrice = decimal.Zero; if (decimal.TryParse(model.Pf, out minPrice)) minPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(minPrice, _services.WorkContext.WorkingCurrency); } - //max price - if (!string.IsNullOrEmpty(model.Pt)) + // max price + if (model.Pt.HasValue()) { - decimal maxPrice = decimal.Zero; + var maxPrice = decimal.Zero; if (decimal.TryParse(model.Pt, out maxPrice)) maxPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(maxPrice, _services.WorkContext.WorkingCurrency); } @@ -1169,8 +1208,6 @@ public ActionResult Search(SearchModel model, SearchPagingFilteringModel command //var searchInProductTags = false; var searchInProductTags = searchInDescriptions; - //products - var ctx = new ProductSearchContext(); ctx.CategoryIds = categoryIds; ctx.ManufacturerId = manufacturerId; @@ -1189,11 +1226,18 @@ public ActionResult Search(SearchModel model, SearchPagingFilteringModel command products = _productService.SearchProducts(ctx); - model.Products = _helper.PrepareProductOverviewModels(products, prepareColorAttributes: true).ToList(); + model.Products = _helper.PrepareProductOverviewModels( + products, + prepareColorAttributes: true, + prepareManufacturers: command.ViewMode.IsCaseInsensitiveEqual("list")).ToList(); model.NoResults = !model.Products.Any(); } } + else + { + model.Sid = _catalogSettings.SearchDescriptions; + } model.PagingFilteringContext.LoadPagedList(products); return View(model); @@ -1202,7 +1246,7 @@ public ActionResult Search(SearchModel model, SearchPagingFilteringModel command [ChildActionOnly] public ActionResult SearchBox() { - var model = new SearchBoxModel() + var model = new SearchBoxModel { AutoCompleteEnabled = _catalogSettings.ProductSearchAutoCompleteEnabled, ShowProductImagesInSearchAutoComplete = _catalogSettings.ShowProductImagesInSearchAutoComplete, @@ -1216,30 +1260,32 @@ public ActionResult SearchTermAutoComplete(string term) if (String.IsNullOrWhiteSpace(term) || term.Length < _catalogSettings.ProductSearchTermMinimumLength) return Content(""); - //products - var productNumber = _catalogSettings.ProductSearchAutoCompleteNumberOfProducts > 0 ? - _catalogSettings.ProductSearchAutoCompleteNumberOfProducts : 10; - var ctx = new ProductSearchContext(); ctx.LanguageId = _services.WorkContext.WorkingLanguage.Id; ctx.Keywords = term; ctx.SearchSku = !_catalogSettings.SuppressSkuSearch; + ctx.SearchDescriptions = _catalogSettings.SearchDescriptions; ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageSize = productNumber; + ctx.PageSize = (_catalogSettings.ProductSearchAutoCompleteNumberOfProducts > 0 ? _catalogSettings.ProductSearchAutoCompleteNumberOfProducts : 10); ctx.StoreId = _services.StoreContext.CurrentStoreIdIfMultiStoreMode; ctx.VisibleIndividuallyOnly = true; var products = _productService.SearchProducts(ctx); - var models = _helper.PrepareProductOverviewModels(products, false, _catalogSettings.ShowProductImagesInSearchAutoComplete, _mediaSettings.AutoCompleteSearchThumbPictureSize).ToList(); - var result = (from p in models - select new - { - label = p.Name, - producturl = Url.RouteUrl("Product", new { SeName = p.SeName }), - productpictureurl = p.DefaultPictureModel.ImageUrl - }) - .ToList(); + var models = _helper.PrepareProductOverviewModels(products, + false, + _catalogSettings.ShowProductImagesInSearchAutoComplete, + _mediaSettings.ProductThumbPictureSizeOnProductDetailsPage + ).ToList(); + + var result = models.Select(x => new + { + label = x.Name, + secondary = x.ShortDescription.Truncate(70, "...") ?? "", + producturl = Url.RouteUrl("Product", new { SeName = x.SeName }), + productpictureurl = x.DefaultPictureModel.ImageUrl + }).ToList(); + return Json(result, JsonRequestBehavior.AllowGet); } diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index f789cc2cb8..ebc506887c 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using System.Web; @@ -11,7 +12,7 @@ using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Tax; -using SmartStore.Core.Localization; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Services; using SmartStore.Services.Catalog; @@ -20,10 +21,11 @@ using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; -using SmartStore.Services.Media; +using SmartStore.Services.Media; using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Tax; +using SmartStore.Services.Topics; using SmartStore.Web.Framework.UI; using SmartStore.Web.Framework.UI.Captcha; using SmartStore.Web.Infrastructure.Cache; @@ -58,7 +60,6 @@ public class CatalogHelper private readonly CatalogSettings _catalogSettings; private readonly CustomerSettings _customerSettings; private readonly CaptchaSettings _captchaSettings; - private readonly CurrencySettings _currencySettings; private readonly TaxSettings _taxSettings; private readonly IMeasureService _measureService; private readonly IQuantityUnitService _quantityUnitService; @@ -66,6 +67,7 @@ public class CatalogHelper private readonly IDeliveryTimeService _deliveryTimeService; private readonly ISettingService _settingService; private readonly Lazy _menuPublisher; + private readonly Lazy _topicService; private readonly HttpRequestBase _httpRequest; private readonly UrlHelper _urlHelper; @@ -91,7 +93,6 @@ public CatalogHelper( MediaSettings mediaSettings, CatalogSettings catalogSettings, CustomerSettings customerSettings, - CurrencySettings currencySettings, CaptchaSettings captchaSettings, IMeasureService measureService, IQuantityUnitService quantityUnitService, @@ -100,6 +101,7 @@ public CatalogHelper( IDeliveryTimeService deliveryTimeService, ISettingService settingService, Lazy _menuPublisher, + Lazy topicService, HttpRequestBase httpRequest, UrlHelper urlHelper) { @@ -131,8 +133,8 @@ public CatalogHelper( this._catalogSettings = catalogSettings; this._customerSettings = customerSettings; this._captchaSettings = captchaSettings; - this._currencySettings = currencySettings; this._menuPublisher = _menuPublisher; + this._topicService = topicService; this._httpRequest = httpRequest; this._urlHelper = urlHelper; @@ -143,8 +145,13 @@ public CatalogHelper( public ILogger Logger { get; set; } - public ProductDetailsModel PrepareProductDetailsPageModel(Product product, bool isAssociatedProduct = false, - ProductBundleItemData productBundleItem = null, IList productBundleItems = null, FormCollection selectedAttributes = null) + public ProductDetailsModel PrepareProductDetailsPageModel( + Product product, + bool isAssociatedProduct = false, + ProductBundleItemData productBundleItem = null, + IList productBundleItems = null, + NameValueCollection selectedAttributes = null, + NameValueCollection queryData = null) { if (product == null) throw new ArgumentNullException("product"); @@ -162,7 +169,7 @@ public ProductDetailsModel PrepareProductDetailsPageModel(Product product, bool ProductType = product.ProductType, VisibleIndividually = product.VisibleIndividually, //Manufacturers = _manufacturerService.GetProductManufacturersByProductId(product.Id), - Manufacturers = PrepareManufacturersOverviewModel(_manufacturerService.GetProductManufacturersByProductId(product.Id)), + Manufacturers = PrepareManufacturersOverviewModel(_manufacturerService.GetProductManufacturersByProductId(product.Id), null, true), ReviewCount = product.ApprovedTotalReviews, DisplayAdminLink = _services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel), //EnableHtmlTextCollapser = Convert.ToBoolean(_settingService.GetSettingByKey("CatalogSettings.EnableHtmlTextCollapser")), @@ -179,6 +186,30 @@ public ProductDetailsModel PrepareProductDetailsPageModel(Product product, bool IsCurrentCustomerRegistered = _services.WorkContext.CurrentCustomer.IsRegistered() }; + // get gift card values from query string + if (queryData != null && queryData.Count > 0) + { + var giftCardItems = queryData.AllKeys + .Where(x => x.EmptyNull().StartsWith("giftcard_")) + .SelectMany(queryData.GetValues, (k, v) => new { key = k, value = v.TrimSafe() }); + + foreach (var item in giftCardItems) + { + var key = item.key.EmptyNull().ToLower(); + + if (key.EndsWith("recipientname")) + model.GiftCard.RecipientName = item.value; + else if (key.EndsWith("recipientemail")) + model.GiftCard.RecipientEmail = item.value; + else if (key.EndsWith("sendername")) + model.GiftCard.SenderName = item.value; + else if (key.EndsWith("senderemail")) + model.GiftCard.SenderEmail = item.value; + else if (key.EndsWith("message")) + model.GiftCard.Message = item.value; + } + } + // Back in stock subscriptions if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock && product.BackorderMode == BackorderMode.NoBackorders && @@ -203,12 +234,12 @@ public ProductDetailsModel PrepareProductDetailsPageModel(Product product, bool IList bundleItems = null; ProductVariantAttributeCombination combination = null; - var combinationImageIds = new List(); if (product.ProductType == ProductType.GroupedProduct && !isAssociatedProduct) // associated products { - var searchContext = new ProductSearchContext() + var searchContext = new ProductSearchContext { + OrderBy = ProductSortingEnum.Position, StoreId = _services.StoreContext.CurrentStore.Id, ParentGroupedProductId = product.Id, PageSize = int.MaxValue, @@ -227,7 +258,19 @@ public ProductDetailsModel PrepareProductDetailsPageModel(Product product, bool foreach (var itemData in bundleItems.Where(x => x.Item.Product.CanBeBundleItem())) { var item = itemData.Item; - var bundledProductModel = PrepareProductDetailsPageModel(item.Product, false, itemData); + var bundleItemAttributes = new NameValueCollection(); + + if (selectedAttributes != null) + { + var keyPrefix = "product_attribute_{0}_{1}".FormatInvariant(item.ProductId, item.Id); + + foreach (var key in selectedAttributes.AllKeys.Where(x => x.HasValue() && x.StartsWith(keyPrefix))) + { + bundleItemAttributes.Add(key, selectedAttributes[key]); + } + } + + var bundledProductModel = PrepareProductDetailsPageModel(item.Product, false, itemData, null, bundleItemAttributes); bundledProductModel.BundleItem.Id = item.Id; bundledProductModel.BundleItem.Quantity = item.Quantity; @@ -235,11 +278,11 @@ public ProductDetailsModel PrepareProductDetailsPageModel(Product product, bool bundledProductModel.BundleItem.Visible = item.Visible; bundledProductModel.BundleItem.IsBundleItemPricing = item.BundleProduct.BundlePerItemPricing; - string bundleItemName = item.GetLocalized(x => x.Name); + var bundleItemName = item.GetLocalized(x => x.Name); if (bundleItemName.HasValue()) bundledProductModel.Name = bundleItemName; - string bundleItemShortDescription = item.GetLocalized(x => x.ShortDescription); + var bundleItemShortDescription = item.GetLocalized(x => x.ShortDescription); if (bundleItemShortDescription.HasValue()) bundledProductModel.ShortDescription = bundleItemShortDescription; @@ -249,17 +292,18 @@ public ProductDetailsModel PrepareProductDetailsPageModel(Product product, bool model = PrepareProductDetailModel(model, product, isAssociatedProduct, productBundleItem, bundleItems, selectedAttributes); + IList combinationPictureIds = null; + if (productBundleItem == null) { - model.Combinations.GetAllCombinationImageIds(combinationImageIds); - - if (combination == null && model.CombinationSelected != null) - combination = model.CombinationSelected; + combinationPictureIds = _productAttributeService.GetAllProductVariantAttributeCombinationPictureIds(product.Id); + if (combination == null && model.SelectedCombination != null) + combination = model.SelectedCombination; } // pictures var pictures = _pictureService.GetPicturesByProductId(product.Id); - PrepareProductDetailsPictureModel(model.DetailsPictureModel, pictures, model.Name, combinationImageIds, isAssociatedProduct, productBundleItem, combination); + PrepareProductDetailsPictureModel(model.DetailsPictureModel, pictures, model.Name, combinationPictureIds, isAssociatedProduct, productBundleItem, combination); return model; } @@ -304,11 +348,11 @@ public void PrepareProductReviewsModel(ProductReviewsModel model, Product produc private PictureModel CreatePictureModel(ProductDetailsPictureModel model, Picture picture, int pictureSize) { - var result = new PictureModel() + var result = new PictureModel { PictureId = picture.Id, ThumbImageUrl = _pictureService.GetPictureUrl(picture, _mediaSettings.ProductThumbPictureSizeOnProductDetailsPage), - ImageUrl = _pictureService.GetPictureUrl(picture, pictureSize), + ImageUrl = _pictureService.GetPictureUrl(picture, pictureSize, !_catalogSettings.HideProductDefaultPictures), FullSizeImageUrl = _pictureService.GetPictureUrl(picture), Title = model.Name, AlternateText = model.AlternateText @@ -317,8 +361,14 @@ private PictureModel CreatePictureModel(ProductDetailsPictureModel model, Pictur return result; } - public void PrepareProductDetailsPictureModel(ProductDetailsPictureModel model, IList pictures, string name, List allCombinationImageIds, - bool isAssociatedProduct, ProductBundleItemData bundleItem = null, ProductVariantAttributeCombination combination = null) + public void PrepareProductDetailsPictureModel( + ProductDetailsPictureModel model, + IList pictures, + string name, + IList allCombinationImageIds, + bool isAssociatedProduct, + ProductBundleItemData bundleItem = null, + ProductVariantAttributeCombination combination = null) { model.Name = name; model.DefaultPictureZoomEnabled = _mediaSettings.DefaultPictureZoomEnabled; @@ -355,7 +405,8 @@ public void PrepareProductDetailsPictureModel(ProductDetailsPictureModel model, else { // images not belonging to any combination... - foreach (var picture in pictures.Where(p => !allCombinationImageIds.Contains(p.Id))) + allCombinationImageIds = allCombinationImageIds ?? new List(); + foreach (var picture in pictures.Where(p => !allCombinationImageIds.Contains(p.Id))) { model.PictureModels.Add(CreatePictureModel(model, picture, _mediaSettings.ProductDetailsPictureSize)); } @@ -386,14 +437,18 @@ public void PrepareProductDetailsPictureModel(ProductDetailsPictureModel model, // default picture if (defaultPicture == null) { - model.DefaultPictureModel = new PictureModel() + model.DefaultPictureModel = new PictureModel { - ThumbImageUrl = _pictureService.GetDefaultPictureUrl(_mediaSettings.ProductThumbPictureSizeOnProductDetailsPage), - ImageUrl = _pictureService.GetDefaultPictureUrl(defaultPictureSize), - FullSizeImageUrl = _pictureService.GetDefaultPictureUrl(), Title = T("Media.Product.ImageLinkTitleFormat", model.Name), AlternateText = model.AlternateText }; + + if (!_catalogSettings.HideProductDefaultPictures) + { + model.DefaultPictureModel.ThumbImageUrl = _pictureService.GetDefaultPictureUrl(_mediaSettings.ProductThumbPictureSizeOnProductDetailsPage); + model.DefaultPictureModel.ImageUrl = _pictureService.GetDefaultPictureUrl(defaultPictureSize); + model.DefaultPictureModel.FullSizeImageUrl = _pictureService.GetDefaultPictureUrl(); + } } else { @@ -408,7 +463,7 @@ public ProductDetailsModel PrepareProductDetailModel( bool isAssociatedProduct = false, ProductBundleItemData productBundleItem = null, IList productBundleItems = null, - FormCollection selectedAttributes = null, + NameValueCollection selectedAttributes = null, int selectedQuantity = 1) { if (product == null) @@ -418,7 +473,11 @@ public ProductDetailsModel PrepareProductDetailModel( throw new ArgumentNullException("model"); if (selectedAttributes == null) - selectedAttributes = new FormCollection(); + selectedAttributes = new NameValueCollection(); + + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; + var currency = _services.WorkContext.WorkingCurrency; decimal preSelectedPriceAdjustmentBase = decimal.Zero; decimal preSelectedWeightAdjustment = decimal.Zero; @@ -446,9 +505,7 @@ public ProductDetailsModel PrepareProductDetailModel( { foreach (var attribute in variantAttributes) { - int preSelectedValueId = 0; - - var pvaModel = new ProductDetailsModel.ProductVariantAttributeModel() + var pvaModel = new ProductDetailsModel.ProductVariantAttributeModel { Id = attribute.Id, ProductId = attribute.ProductId, @@ -471,78 +528,98 @@ public ProductDetailsModel PrepareProductDetailModel( pvaModel.BeginYear = match.Groups[1].Value.ToInt(); pvaModel.EndYear = match.Groups[2].Value.ToInt(); } - } - if (attribute.ShouldHaveValues()) + if (hasSelectedAttributes) + { + var attributeKey = "product_attribute_{0}_{1}_{2}_{3}".FormatInvariant(product.Id, bundleItemId, attribute.ProductAttributeId, attribute.Id); + var day = selectedAttributes[attributeKey + "_day"].ToInt(); + var month = selectedAttributes[attributeKey + "_month"].ToInt(); + var year = selectedAttributes[attributeKey + "_year"].ToInt(); + if (day > 0 && month > 0 && year > 0) + { + pvaModel.SelectedDay = day; + pvaModel.SelectedMonth = month; + pvaModel.SelectedYear = year; + } + } + } + else if (attribute.AttributeControlType == AttributeControlType.TextBox || attribute.AttributeControlType == AttributeControlType.MultilineTextbox) { - var pvaValues = _productAttributeService.GetProductVariantAttributeValues(attribute.Id); - - foreach (var pvaValue in pvaValues) + if (hasSelectedAttributes) { - ProductBundleItemAttributeFilter attributeFilter = null; - - if (productBundleItem.FilterOut(pvaValue, out attributeFilter)) - continue; + var attributeKey = "product_attribute_{0}_{1}_{2}_{3}".FormatInvariant(product.Id, bundleItemId, attribute.ProductAttributeId, attribute.Id); + pvaModel.TextValue = selectedAttributes[attributeKey]; + } + } - if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected) - preSelectedValueId = attributeFilter.AttributeValueId; + var preSelectedValueId = 0; + var pvaValues = (attribute.ShouldHaveValues() ? _productAttributeService.GetProductVariantAttributeValues(attribute.Id) : new List()); - var linkedProduct = _productService.GetProductById(pvaValue.LinkedProductId); + foreach (var pvaValue in pvaValues) + { + ProductBundleItemAttributeFilter attributeFilter = null; - var pvaValueModel = new ProductDetailsModel.ProductVariantAttributeValueModel(); - pvaValueModel.Id = pvaValue.Id; - pvaValueModel.Name = pvaValue.GetLocalized(x => x.Name); - pvaValueModel.Alias = pvaValue.Alias; - pvaValueModel.ColorSquaresRgb = pvaValue.ColorSquaresRgb; //used with "Color squares" attribute type - pvaValueModel.IsPreSelected = pvaValue.IsPreSelected; + if (productBundleItem.FilterOut(pvaValue, out attributeFilter)) + continue; - if (linkedProduct != null && linkedProduct.VisibleIndividually) - pvaValueModel.SeName = linkedProduct.GetSeName(); + if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected) + preSelectedValueId = attributeFilter.AttributeValueId; - if (hasSelectedAttributes) - pvaValueModel.IsPreSelected = false; // explicitly selected always discards pre-selected by merchant + var linkedProduct = _productService.GetProductById(pvaValue.LinkedProductId); - // display price if allowed - if (displayPrices && !isBundlePricing) - { - decimal taxRate = decimal.Zero; - decimal attributeValuePriceAdjustment = _priceCalculationService.GetProductVariantAttributeValuePriceAdjustment(pvaValue); - decimal priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, out taxRate); - decimal priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, _services.WorkContext.WorkingCurrency); + var pvaValueModel = new ProductDetailsModel.ProductVariantAttributeValueModel(); + pvaValueModel.Id = pvaValue.Id; + pvaValueModel.Name = pvaValue.GetLocalized(x => x.Name); + pvaValueModel.Alias = pvaValue.Alias; + pvaValueModel.ColorSquaresRgb = pvaValue.ColorSquaresRgb; //used with "Color squares" attribute type + pvaValueModel.IsPreSelected = pvaValue.IsPreSelected; - if (priceAdjustmentBase > decimal.Zero) - pvaValueModel.PriceAdjustment = "+" + _priceFormatter.FormatPrice(priceAdjustment, true, false); - else if (priceAdjustmentBase < decimal.Zero) - pvaValueModel.PriceAdjustment = "-" + _priceFormatter.FormatPrice(-priceAdjustment, true, false); + if (linkedProduct != null && linkedProduct.VisibleIndividually) + pvaValueModel.SeName = linkedProduct.GetSeName(); - if (pvaValueModel.IsPreSelected) - { - preSelectedPriceAdjustmentBase = decimal.Add(preSelectedPriceAdjustmentBase, priceAdjustmentBase); - preSelectedWeightAdjustment = decimal.Add(preSelectedWeightAdjustment, pvaValue.WeightAdjustment); - } + if (hasSelectedAttributes) + pvaValueModel.IsPreSelected = false; // explicitly selected always discards pre-selected by merchant - if (_catalogSettings.ShowLinkedAttributeValueQuantity && pvaValue.ValueType == ProductVariantAttributeValueType.ProductLinkage) - { - pvaValueModel.QuantityInfo = pvaValue.Quantity; - } + // display price if allowed + if (displayPrices && !isBundlePricing) + { + decimal taxRate = decimal.Zero; + decimal attributeValuePriceAdjustment = _priceCalculationService.GetProductVariantAttributeValuePriceAdjustment(pvaValue); + decimal priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, out taxRate); + decimal priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, currency); - pvaValueModel.PriceAdjustmentValue = priceAdjustment; - } + if (priceAdjustmentBase > decimal.Zero) + pvaValueModel.PriceAdjustment = "+" + _priceFormatter.FormatPrice(priceAdjustment, true, false); + else if (priceAdjustmentBase < decimal.Zero) + pvaValueModel.PriceAdjustment = "-" + _priceFormatter.FormatPrice(-priceAdjustment, true, false); - if (!_catalogSettings.ShowVariantCombinationPriceAdjustment) + if (pvaValueModel.IsPreSelected) { - pvaValueModel.PriceAdjustment = ""; + preSelectedPriceAdjustmentBase = decimal.Add(preSelectedPriceAdjustmentBase, priceAdjustmentBase); + preSelectedWeightAdjustment = decimal.Add(preSelectedWeightAdjustment, pvaValue.WeightAdjustment); } - if (_catalogSettings.ShowLinkedAttributeValueImage && pvaValue.ValueType == ProductVariantAttributeValueType.ProductLinkage) + if (_catalogSettings.ShowLinkedAttributeValueQuantity && pvaValue.ValueType == ProductVariantAttributeValueType.ProductLinkage) { - var linkagePicture = _pictureService.GetPicturesByProductId(pvaValue.LinkedProductId, 1).FirstOrDefault(); - if (linkagePicture != null) - pvaValueModel.ImageUrl = _pictureService.GetPictureUrl(linkagePicture, _mediaSettings.AutoCompleteSearchThumbPictureSize, false); + pvaValueModel.QuantityInfo = pvaValue.Quantity; } - pvaModel.Values.Add(pvaValueModel); + pvaValueModel.PriceAdjustmentValue = priceAdjustment; } + + if (!_catalogSettings.ShowVariantCombinationPriceAdjustment) + { + pvaValueModel.PriceAdjustment = ""; + } + + if (_catalogSettings.ShowLinkedAttributeValueImage && pvaValue.ValueType == ProductVariantAttributeValueType.ProductLinkage) + { + var linkagePicture = _pictureService.GetPicturesByProductId(pvaValue.LinkedProductId, 1).FirstOrDefault(); + if (linkagePicture != null) + pvaValueModel.ImageUrl = _pictureService.GetPictureUrl(linkagePicture, _mediaSettings.VariantValueThumbPictureSize, false); + } + + pvaModel.Values.Add(pvaValueModel); } // we need selected attributes to get initially displayed combination images @@ -555,17 +632,25 @@ public ProductDetailsModel PrepareProductDetailModel( pvaModel.Values.Each(x => x.IsPreSelected = false); if ((defaultValue = pvaModel.Values.FirstOrDefault(v => v.Id == preSelectedValueId)) != null) + { defaultValue.IsPreSelected = true; + selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId); + } } if (defaultValue == null) - defaultValue = pvaModel.Values.FirstOrDefault(v => v.IsPreSelected); + { + foreach (var value in pvaModel.Values.Where(x => x.IsPreSelected)) + { + selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, value.Id, product.Id, bundleItemId); + } + } - if (defaultValue == null && pvaModel.Values.Count > 0 && attribute.IsRequired) - defaultValue = pvaModel.Values.First(); + //if (defaultValue == null) + // defaultValue = pvaModel.Values.FirstOrDefault(v => v.IsPreSelected); - if (defaultValue != null) - selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId); + //if (defaultValue != null) + // selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId); } model.ProductVariantAttributes.Add(pvaModel); @@ -578,8 +663,6 @@ public ProductDetailsModel PrepareProductDetailModel( if (!isBundle) { - model.Combinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id); - if (selectedAttributes.Count > 0) { // merge with combination data if there's a match @@ -592,20 +675,19 @@ public ProductDetailsModel PrepareProductDetailModel( if (isBundlePricing) { - model.AttributeInfo = _productAttributeFormatter.FormatAttributes(product, attributeXml, _services.WorkContext.CurrentCustomer, + model.AttributeInfo = _productAttributeFormatter.FormatAttributes(product, attributeXml, customer, renderPrices: false, renderGiftCardAttributes: false, allowHyperlinks: false); } - model.CombinationSelected = model.Combinations - .FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml)); + model.SelectedCombination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, attributeXml); - if (model.CombinationSelected != null && model.CombinationSelected.IsActive == false) + if (model.SelectedCombination != null && model.SelectedCombination.IsActive == false) { model.IsAvailable = false; - model.StockAvailability = T("Products.Availability.OutOfStock"); + model.StockAvailability = T("Products.Availability.IsNotActive"); } - product.MergeWithCombination(model.CombinationSelected); + product.MergeWithCombination(model.SelectedCombination); // mark explicitly selected as pre-selected foreach (var attribute in model.ProductVariantAttributes) @@ -631,8 +713,9 @@ public ProductDetailsModel PrepareProductDetailModel( { // cases where stock inventory is not functional. determined by what ShoppingCartService.GetStandardWarnings and ProductService.AdjustInventory is not handling. model.IsAvailable = true; - model.StockAvailability = ""; - } + var hasAttributeCombinations = _services.DbContext.QueryForCollection(product, (Product p) => p.ProductVariantAttributeCombinations).Any(); + model.StockAvailability = !hasAttributeCombinations ? product.FormatStockMessage(_localizationService) : ""; + } else if (model.IsAvailable) { model.IsAvailable = product.IsAvailableByStock(); @@ -656,9 +739,9 @@ public ProductDetailsModel PrepareProductDetailModel( model.ShowGtin = _catalogSettings.ShowGtin; model.Gtin = product.Gtin; model.HasSampleDownload = product.IsDownload && product.HasSampleDownload; - model.IsCurrentCustomerRegistered = _services.WorkContext.CurrentCustomer.IsRegistered(); + model.IsCurrentCustomerRegistered = customer.IsRegistered(); model.IsBasePriceEnabled = product.BasePriceEnabled; - model.BasePriceInfo = product.GetBasePriceInfo(_localizationService, _priceFormatter); + model.BasePriceInfo = product.GetBasePriceInfo(_localizationService, _priceFormatter, _currencyService, _taxService, _priceCalculationService, currency); model.ShowLegalInfo = _taxSettings.ShowLegalHintsInProductDetails; model.BundleTitleText = product.GetLocalized(x => x.BundleTitleText); model.BundlePerItemPricing = product.BundlePerItemPricing; @@ -667,28 +750,53 @@ public ProductDetailsModel PrepareProductDetailModel( //_taxSettings.TaxDisplayType == TaxDisplayType.ExcludingTax; - string taxInfo = (_services.WorkContext.GetTaxDisplayTypeFor(_services.WorkContext.CurrentCustomer, _services.StoreContext.CurrentStore.Id) == TaxDisplayType.IncludingTax) - ? T("Tax.InclVAT") - : T("Tax.ExclVAT"); + var taxDisplayType = _services.WorkContext.GetTaxDisplayTypeFor(customer, store.Id); + string taxInfo = T(taxDisplayType == TaxDisplayType.IncludingTax ? "Tax.InclVAT" : "Tax.ExclVAT"); - string defaultTaxRate = ""; - var taxrate = Convert.ToString(_taxService.GetTaxRate(product, _services.WorkContext.CurrentCustomer)); + var defaultTaxRate = ""; + var taxrate = Convert.ToString(_taxService.GetTaxRate(product, customer)); if (_taxSettings.DisplayTaxRates && !taxrate.Equals("0", StringComparison.InvariantCultureIgnoreCase)) { defaultTaxRate = "({0}%)".FormatWith(taxrate); } - var addShippingPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.AdditionalShippingCharge, _services.WorkContext.WorkingCurrency); - string additionalShippingCosts = ""; + var additionalShippingCosts = String.Empty; + var addShippingPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.AdditionalShippingCharge, currency); + if (addShippingPrice > 0) { - additionalShippingCosts = T("Common.AdditionalShippingSurcharge").Text.FormatWith(_priceFormatter.FormatPrice(addShippingPrice, true, false)) + ", "; + additionalShippingCosts = T("Common.AdditionalShippingSurcharge").Text.FormatInvariant(_priceFormatter.FormatPrice(addShippingPrice, true, false)) + ", "; } - string shippingInfoLink = _urlHelper.RouteUrl("Topic", new { SystemName = "shippinginfo" }); - model.LegalInfo = T("Tax.LegalInfoProductDetail", taxInfo, defaultTaxRate, additionalShippingCosts, shippingInfoLink); + if (!product.IsShipEnabled || (addShippingPrice == 0 && product.IsFreeShipping)) + { + model.LegalInfo += "{0} {1}, {2}".FormatInvariant( + product.IsTaxExempt ? "" : taxInfo, + product.IsTaxExempt ? "" : defaultTaxRate, + T("Common.FreeShipping")); + } + else + { + var topic = _topicService.Value.GetTopicBySystemName("ShippingInfo", store.Id); - string dimension = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId).Name; + if (topic == null) + { + model.LegalInfo = T("Tax.LegalInfoProductDetail2", + product.IsTaxExempt ? "" : taxInfo, + product.IsTaxExempt ? "" : defaultTaxRate, + additionalShippingCosts); + } + else + { + model.LegalInfo = T("Tax.LegalInfoProductDetail", + product.IsTaxExempt ? "" : taxInfo, + product.IsTaxExempt ? "" : defaultTaxRate, + additionalShippingCosts, + _urlHelper.RouteUrl("Topic", new { SystemName = "shippinginfo" })); + } + } + + var dimension = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId).Name; model.WeightValue = product.Weight; if (!isBundle) @@ -747,8 +855,7 @@ public ProductDetailsModel PrepareProductDetailModel( { //out of stock model.DisplayBackInStockSubscription = true; - model.BackInStockAlreadySubscribed = _backInStockSubscriptionService - .FindSubscription(_services.WorkContext.CurrentCustomer.Id, product.Id, _services.StoreContext.CurrentStore.Id) != null; + model.BackInStockAlreadySubscribed = _backInStockSubscriptionService.FindSubscription(customer.Id, product.Id, store.Id) != null; } #endregion @@ -801,18 +908,18 @@ public ProductDetailsModel PrepareProductDetailModel( } finalPriceWithoutDiscountBase = _priceCalculationService.GetFinalPrice(product, productBundleItems, - _services.WorkContext.CurrentCustomer, attributesTotalPriceBase, false, selectedQuantity, productBundleItem); + customer, attributesTotalPriceBase, false, selectedQuantity, productBundleItem); finalPriceWithDiscountBase = _priceCalculationService.GetFinalPrice(product, productBundleItems, - _services.WorkContext.CurrentCustomer, attributesTotalPriceBase, true, selectedQuantity, productBundleItem); + customer, attributesTotalPriceBase, true, selectedQuantity, productBundleItem); finalPriceWithoutDiscountBase = _taxService.GetProductPrice(product, finalPriceWithoutDiscountBase, out taxRate); finalPriceWithDiscountBase = _taxService.GetProductPrice(product, finalPriceWithDiscountBase, out taxRate); oldPrice = _currencyService.ConvertFromPrimaryStoreCurrency(oldPriceBase, _services.WorkContext.WorkingCurrency); - finalPriceWithoutDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceWithoutDiscountBase, _services.WorkContext.WorkingCurrency); - finalPriceWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceWithDiscountBase, _services.WorkContext.WorkingCurrency); + finalPriceWithoutDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceWithoutDiscountBase, currency); + finalPriceWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceWithDiscountBase, currency); if (productBundleItem == null || isBundleItemPricing) { @@ -827,20 +934,35 @@ public ProductDetailsModel PrepareProductDetailModel( model.ProductPrice.PriceValue = finalPriceWithoutDiscount; model.ProductPrice.PriceWithDiscountValue = finalPriceWithDiscount; - - if (!(isBundleItemPricing && !model.ProductPrice.BundleItemShowBasePrice)) - { - model.BasePriceInfo = product.GetBasePriceInfo(_localizationService, _priceFormatter, attributesTotalPriceBase); - } + model.BasePriceInfo = product.GetBasePriceInfo( + _localizationService, + _priceFormatter, + _currencyService, + _taxService, + _priceCalculationService, + currency, + attributesTotalPriceBase); if (!string.IsNullOrWhiteSpace(model.ProductPrice.OldPrice) || !string.IsNullOrWhiteSpace(model.ProductPrice.PriceWithDiscount)) { model.ProductPrice.NoteWithoutDiscount = T(isBundle && product.BundlePerItemPricing ? "Products.Bundle.PriceWithoutDiscount.Note" : "Products.Price"); } - if (isBundle && product.BundlePerItemPricing && !string.IsNullOrWhiteSpace(model.ProductPrice.PriceWithDiscount)) + if ((isBundle && product.BundlePerItemPricing && !string.IsNullOrWhiteSpace(model.ProductPrice.PriceWithDiscount)) || product.HasTierPrices) { - model.ProductPrice.NoteWithDiscount = T("Products.Bundle.PriceWithDiscount.Note"); + if (!product.HasTierPrices) + { + model.ProductPrice.NoteWithDiscount = T("Products.Bundle.PriceWithDiscount.Note"); + } + + model.BasePriceInfo = product.GetBasePriceInfo( + _localizationService, + _priceFormatter, + _currencyService, + _taxService, + _priceCalculationService, + currency, + (product.Price - finalPriceWithDiscount) * (-1)); } } } @@ -875,19 +997,21 @@ public ProductDetailsModel PrepareProductDetailModel( model.AddToCart.CustomerEntersPrice = product.CustomerEntersPrice; if (model.AddToCart.CustomerEntersPrice) { - decimal minimumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MinimumCustomerEnteredPrice, _services.WorkContext.WorkingCurrency); - decimal maximumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MaximumCustomerEnteredPrice, _services.WorkContext.WorkingCurrency); + var minimumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MinimumCustomerEnteredPrice, currency); + var maximumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MaximumCustomerEnteredPrice, currency); model.AddToCart.CustomerEnteredPrice = minimumCustomerEnteredPrice; + model.AddToCart.CustomerEnteredPriceRange = string.Format(T("Products.EnterProductPrice.Range"), _priceFormatter.FormatPrice(minimumCustomerEnteredPrice, true, false), _priceFormatter.FormatPrice(maximumCustomerEnteredPrice, true, false)); } + //allowed quantities var allowedQuantities = product.ParseAllowedQuatities(); foreach (var qty in allowedQuantities) { - model.AddToCart.AllowedQuantities.Add(new SelectListItem() + model.AddToCart.AllowedQuantities.Add(new SelectListItem { Text = qty.ToString(), Value = qty.ToString() @@ -902,8 +1026,8 @@ public ProductDetailsModel PrepareProductDetailModel( if (model.GiftCard.IsGiftCard) { model.GiftCard.GiftCardType = product.GiftCardType; - model.GiftCard.SenderName = _services.WorkContext.CurrentCustomer.GetFullName(); - model.GiftCard.SenderEmail = _services.WorkContext.CurrentCustomer.Email; + model.GiftCard.SenderName = customer.GetFullName(); + model.GiftCard.SenderEmail = customer.Email; } #endregion @@ -970,205 +1094,240 @@ public IList GetCategoryBreadCrumb(int currentCategoryId, int currentP return breadcrumb; } - public IEnumerable PrepareProductOverviewModels(IEnumerable products, - bool preparePriceModel = true, bool preparePictureModel = true, - int? productThumbPictureSize = null, bool prepareSpecificationAttributes = false, - bool forceRedirectionAfterAddingToCart = false, bool prepareColorAttributes = false) + public IEnumerable PrepareProductOverviewModels( + IEnumerable products, + bool preparePriceModel = true, + bool preparePictureModel = true, + int? productThumbPictureSize = null, + bool prepareSpecificationAttributes = false, + bool forceRedirectionAfterAddingToCart = false, + bool prepareColorAttributes = false, + bool prepareManufacturers = false, + bool isCompact = false, + bool prepareFullDescription = false, + bool isCompareList = false) { if (products == null) throw new ArgumentNullException("products"); + // PERF!! + var currentStore = _services.StoreContext.CurrentStore; + var currentCustomer = _services.WorkContext.CurrentCustomer; + var workingCurrency = _services.WorkContext.WorkingCurrency; + var displayPrices = _services.Permissions.Authorize(StandardPermissionProvider.DisplayPrices); + var enableShoppingCart = _services.Permissions.Authorize(StandardPermissionProvider.EnableShoppingCart); + var enableWishlist = _services.Permissions.Authorize(StandardPermissionProvider.EnableWishlist); + var taxDisplayType = _services.WorkContext.GetTaxDisplayTypeFor(currentCustomer, currentStore.Id); + var cachedManufacturerModels = new Dictionary(); + + string taxInfo = T(taxDisplayType == TaxDisplayType.IncludingTax ? "Tax.InclVAT" : "Tax.ExclVAT"); + var legalInfo = ""; + + var res = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Products.CallForPrice", T("Products.CallForPrice") }, + { "Products.PriceRangeFrom", T("Products.PriceRangeFrom") }, + { "Media.Product.ImageLinkTitleFormat", T("Media.Product.ImageLinkTitleFormat") }, + { "Media.Product.ImageAlternateTextFormat", T("Media.Product.ImageAlternateTextFormat") }, + { "Products.DimensionsValue", T("Products.DimensionsValue") }, + { "Common.AdditionalShippingSurcharge", T("Common.AdditionalShippingSurcharge") } + }; + + if (_taxSettings.ShowLegalHintsInProductList) + { + if (_topicService.Value.GetTopicBySystemName("ShippingInfo", currentStore.Id) == null) + { + legalInfo = T("Tax.LegalInfoFooter2").Text.FormatInvariant(taxInfo); + } + else + { + var shippingInfoLink = _urlHelper.RouteUrl("Topic", new { SystemName = "shippinginfo" }); + legalInfo = T("Tax.LegalInfoFooter").Text.FormatInvariant(taxInfo, shippingInfoLink); + } + } + + var cargoData = _priceCalculationService.CreatePriceCalculationContext(products); + var models = new List(); foreach (var product in products) { - var minPriceProduct = product; + var contextProduct = product; + var finalPrice = decimal.Zero; - var model = new ProductOverviewModel() + var model = new ProductOverviewModel { Id = product.Id, Name = product.GetLocalized(x => x.Name).EmptyNull(), ShortDescription = product.GetLocalized(x => x.ShortDescription), - FullDescription = product.GetLocalized(x => x.FullDescription), SeName = product.GetSeName() }; + if (prepareFullDescription) + { + model.FullDescription = product.GetLocalized(x => x.FullDescription); + } + // price if (preparePriceModel) { #region Prepare product price - var priceModel = new ProductOverviewModel.ProductPriceModel() + var priceModel = new ProductOverviewModel.ProductPriceModel { ForceRedirectionAfterAddingToCart = forceRedirectionAfterAddingToCart, ShowDiscountSign = _catalogSettings.ShowDiscountSign }; - switch (product.ProductType) + if (product.ProductType == ProductType.GroupedProduct) { - case ProductType.GroupedProduct: - { - #region Grouped product + #region Grouped product - var searchContext = new ProductSearchContext() - { - StoreId = _services.StoreContext.CurrentStore.Id, - ParentGroupedProductId = product.Id, - PageSize = int.MaxValue, - VisibleIndividuallyOnly = false - }; + priceModel.DisableBuyButton = true; + priceModel.DisableWishListButton = true; + priceModel.AvailableForPreOrder = false; - var associatedProducts = _productService.SearchProducts(searchContext); + var searchContext = new ProductSearchContext + { + OrderBy = ProductSortingEnum.Position, + StoreId = currentStore.Id, + ParentGroupedProductId = product.Id, + PageSize = int.MaxValue, + VisibleIndividuallyOnly = false + }; + + var associatedProducts = _productService.SearchProducts(searchContext); + + if (associatedProducts.Count > 0) + { + contextProduct = associatedProducts.OrderBy(x => x.DisplayOrder).First(); - if (associatedProducts.Count <= 0) + if (displayPrices && _catalogSettings.PriceDisplayType != PriceDisplayType.Hide) + { + decimal? displayPrice = null; + bool displayFromMessage = false; + + if (_catalogSettings.PriceDisplayType == PriceDisplayType.PreSelectedPrice) { - priceModel.OldPrice = null; - priceModel.Price = null; - priceModel.DisableBuyButton = true; - priceModel.DisableWishListButton = true; - priceModel.AvailableForPreOrder = false; + displayPrice = _priceCalculationService.GetPreselectedPrice(contextProduct, cargoData); + } + else if (_catalogSettings.PriceDisplayType == PriceDisplayType.PriceWithoutDiscountsAndAttributes) + { + displayPrice = _priceCalculationService.GetFinalPrice(contextProduct, null, currentCustomer, decimal.Zero, false, 1, null, cargoData); } else { - priceModel.DisableBuyButton = true; - priceModel.DisableWishListButton = true; - priceModel.AvailableForPreOrder = false; + displayFromMessage = true; + displayPrice = _priceCalculationService.GetLowestPrice(product, cargoData, associatedProducts, out contextProduct); + } - if (_services.Permissions.Authorize(StandardPermissionProvider.DisplayPrices)) + if (contextProduct != null && !contextProduct.CustomerEntersPrice) + { + if (contextProduct.CallForPrice) { - decimal? minPossiblePrice = _priceCalculationService.GetLowestPrice(product, associatedProducts, out minPriceProduct); - - if (minPriceProduct != null && !minPriceProduct.CustomerEntersPrice) - { - if (minPriceProduct.CallForPrice) - { - priceModel.OldPrice = null; - priceModel.Price = T("Products.CallForPrice"); - } - else if (minPossiblePrice.HasValue) - { - //calculate prices - decimal taxRate = decimal.Zero; - decimal oldPriceBase = _taxService.GetProductPrice(minPriceProduct, minPriceProduct.OldPrice, out taxRate); - decimal finalPriceBase = _taxService.GetProductPrice(minPriceProduct, minPossiblePrice.Value, out taxRate); - decimal finalPrice = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, _services.WorkContext.WorkingCurrency); - - priceModel.OldPrice = null; - priceModel.Price = String.Format(T("Products.PriceRangeFrom"), _priceFormatter.FormatPrice(finalPrice)); - priceModel.HasDiscount = finalPriceBase != oldPriceBase && oldPriceBase != decimal.Zero; - } - else - { - //Actually it's not possible (we presume that minimalPrice always has a value) - //We never should get here - Debug.WriteLine(string.Format("Cannot calculate minPrice for product #{0}", product.Id)); - } - } + priceModel.OldPrice = null; + priceModel.Price = res["Products.CallForPrice"]; } - else + else if (displayPrice.HasValue) { - //hide prices + //calculate prices + decimal taxRate = decimal.Zero; + decimal oldPriceBase = _taxService.GetProductPrice(contextProduct, contextProduct.OldPrice, out taxRate); + decimal finalPriceBase = _taxService.GetProductPrice(contextProduct, displayPrice.Value, out taxRate); + finalPrice = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, workingCurrency); + priceModel.OldPrice = null; - priceModel.Price = null; + + if (displayFromMessage) + priceModel.Price = String.Format(res["Products.PriceRangeFrom"], _priceFormatter.FormatPrice(finalPrice)); + else + priceModel.Price = _priceFormatter.FormatPrice(finalPrice); + + priceModel.HasDiscount = (finalPriceBase != oldPriceBase && oldPriceBase != decimal.Zero); + } + else + { + // Actually it's not possible (we presume that displayPrice always has a value). We never should get here + Debug.WriteLine(string.Format("Cannot calculate displayPrice for product #{0}", product.Id)); } } + } + } - #endregion + #endregion + } + else + { + #region Simple product + + //add to cart button + priceModel.DisableBuyButton = product.DisableBuyButton || !enableShoppingCart || !displayPrices; + + //add to wishlist button + priceModel.DisableWishListButton = product.DisableWishlistButton || !enableWishlist || !displayPrices; + + //pre-order + priceModel.AvailableForPreOrder = product.AvailableForPreOrder; + + //prices + if (displayPrices && _catalogSettings.PriceDisplayType != PriceDisplayType.Hide && !product.CustomerEntersPrice) + { + if (product.CallForPrice) + { + //call for price + priceModel.OldPrice = null; + priceModel.Price = res["Products.CallForPrice"]; } - break; - case ProductType.SimpleProduct: - default: + else { - #region Simple product + //calculate prices + bool displayFromMessage = false; + decimal displayPrice = decimal.Zero; - //add to cart button - priceModel.DisableBuyButton = product.DisableBuyButton || - !_services.Permissions.Authorize(StandardPermissionProvider.EnableShoppingCart) || - !_services.Permissions.Authorize(StandardPermissionProvider.DisplayPrices); + if (_catalogSettings.PriceDisplayType == PriceDisplayType.PreSelectedPrice) + { + displayPrice = _priceCalculationService.GetPreselectedPrice(product, cargoData); + } + else if (_catalogSettings.PriceDisplayType == PriceDisplayType.PriceWithoutDiscountsAndAttributes) + { + displayPrice = _priceCalculationService.GetFinalPrice(product, null, currentCustomer, decimal.Zero, false, 1, null, cargoData); + } + else + { + displayPrice = _priceCalculationService.GetLowestPrice(product, cargoData, out displayFromMessage); + } + + decimal taxRate = decimal.Zero; + decimal oldPriceBase = _taxService.GetProductPrice(product, product.OldPrice, out taxRate); + decimal finalPriceBase = _taxService.GetProductPrice(product, displayPrice, out taxRate); - //add to wishlist button - priceModel.DisableWishListButton = product.DisableWishlistButton || - !_services.Permissions.Authorize(StandardPermissionProvider.EnableWishlist) || - !_services.Permissions.Authorize(StandardPermissionProvider.DisplayPrices); + decimal oldPrice = _currencyService.ConvertFromPrimaryStoreCurrency(oldPriceBase, workingCurrency); + finalPrice = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, workingCurrency); - //pre-order - priceModel.AvailableForPreOrder = product.AvailableForPreOrder; + priceModel.HasDiscount = (finalPriceBase != oldPriceBase && oldPriceBase != decimal.Zero); - //prices - if (_services.Permissions.Authorize(StandardPermissionProvider.DisplayPrices)) + if (displayFromMessage) { - if (!product.CustomerEntersPrice) - { - if (product.CallForPrice) - { - //call for price - priceModel.OldPrice = null; - priceModel.Price = T("Products.CallForPrice"); - } - else - { - //calculate prices - bool isBundlePerItemPricing = (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing); - - bool displayFromMessage = false; - decimal minPossiblePrice = _priceCalculationService.GetLowestPrice(product, out displayFromMessage); - - decimal taxRate = decimal.Zero; - decimal oldPriceBase = _taxService.GetProductPrice(product, product.OldPrice, out taxRate); - decimal finalPriceBase = _taxService.GetProductPrice(product, minPossiblePrice, out taxRate); - - decimal oldPrice = _currencyService.ConvertFromPrimaryStoreCurrency(oldPriceBase, _services.WorkContext.WorkingCurrency); - decimal finalPrice = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, _services.WorkContext.WorkingCurrency); - - priceModel.HasDiscount = (finalPriceBase != oldPriceBase && oldPriceBase != decimal.Zero); - - // check tier prices - if (product.HasTierPrices && !isBundlePerItemPricing && !displayFromMessage) - { - var tierPrices = new List(); - - tierPrices.AddRange(product.TierPrices - .OrderBy(tp => tp.Quantity) - .FilterByStore(_services.StoreContext.CurrentStore.Id) - .FilterForCustomer(_services.WorkContext.CurrentCustomer) - .ToList() - .RemoveDuplicatedQuantities()); - - // When there is just one tier (with qty 1), there are no actual savings in the list. - displayFromMessage = (tierPrices.Count > 0 && !(tierPrices.Count == 1 && tierPrices[0].Quantity <= 1)); - } - - if (displayFromMessage) - { - priceModel.OldPrice = null; - priceModel.Price = String.Format(T("Products.PriceRangeFrom"), _priceFormatter.FormatPrice(finalPrice)); - } - else - { - if (priceModel.HasDiscount) - { - priceModel.OldPrice = _priceFormatter.FormatPrice(oldPrice); - priceModel.Price = _priceFormatter.FormatPrice(finalPrice); - } - else - { - priceModel.OldPrice = null; - priceModel.Price = _priceFormatter.FormatPrice(finalPrice); - } - } - } - } + priceModel.OldPrice = null; + priceModel.Price = String.Format(res["Products.PriceRangeFrom"], _priceFormatter.FormatPrice(finalPrice)); } else { - //hide prices - priceModel.OldPrice = null; - priceModel.Price = null; + if (priceModel.HasDiscount) + { + priceModel.OldPrice = _priceFormatter.FormatPrice(oldPrice); + priceModel.Price = _priceFormatter.FormatPrice(finalPrice); + } + else + { + priceModel.OldPrice = null; + priceModel.Price = _priceFormatter.FormatPrice(finalPrice); + } } - - #endregion } - break; + } + + #endregion } model.ProductPrice = priceModel; @@ -1177,6 +1336,35 @@ public IEnumerable PrepareProductOverviewModels(IEnumerabl #endregion } + // color squares + if (prepareColorAttributes && _catalogSettings.ShowColorSquaresInLists) + { + #region Prepare color attributes + + var attributes = cargoData.Attributes.Load(contextProduct.Id); + var colorAttribute = attributes.FirstOrDefault(x => x.AttributeControlType == AttributeControlType.ColorSquares); + + if (colorAttribute != null) + { + var colorValues = + from a in colorAttribute.ProductVariantAttributeValues.Take(50) + where (a.ColorSquaresRgb.HasValue() && !a.ColorSquaresRgb.IsCaseInsensitiveEqual("transparent")) + select new ProductOverviewModel.ColorAttributeModel + { + Color = a.ColorSquaresRgb, + Alias = a.Alias, + FriendlyName = a.GetLocalized(l => l.Name) + }; + + if (colorValues.Any()) + { + model.ColorAttributes.AddRange(colorValues.Distinct()); + } + } + + #endregion + } + // picture if (preparePictureModel) { @@ -1187,17 +1375,17 @@ public IEnumerable PrepareProductOverviewModels(IEnumerabl //prepare picture model var defaultProductPictureCacheKey = string.Format(ModelCacheEventConsumer.PRODUCT_DEFAULTPICTURE_MODEL_KEY, product.Id, pictureSize, true, - _services.WorkContext.WorkingLanguage.Id, _services.WebHelper.IsCurrentConnectionSecured(), _services.StoreContext.CurrentStore.Id); + _services.WorkContext.WorkingLanguage.Id, _services.WebHelper.IsCurrentConnectionSecured(), currentStore.Id); model.DefaultPictureModel = _services.Cache.Get(defaultProductPictureCacheKey, () => { var picture = product.GetDefaultProductPicture(_pictureService); var pictureModel = new PictureModel { - ImageUrl = _pictureService.GetPictureUrl(picture, pictureSize), - FullSizeImageUrl = _pictureService.GetPictureUrl(picture), - Title = string.Format(T("Media.Product.ImageLinkTitleFormat"), model.Name), - AlternateText = string.Format(T("Media.Product.ImageAlternateTextFormat"), model.Name) + ImageUrl = _pictureService.GetPictureUrl(picture, pictureSize, !_catalogSettings.HideProductDefaultPictures), + FullSizeImageUrl = _pictureService.GetPictureUrl(picture, 0, !_catalogSettings.HideProductDefaultPictures), + Title = string.Format(res["Media.Product.ImageLinkTitleFormat"], model.Name), + AlternateText = string.Format(res["Media.Product.ImageAlternateTextFormat"], model.Name) }; return pictureModel; }); @@ -1210,99 +1398,79 @@ public IEnumerable PrepareProductOverviewModels(IEnumerabl { model.SpecificationAttributeModels = PrepareProductSpecificationModel(product); } - - // available colors - if (prepareColorAttributes && _catalogSettings.ShowColorSquaresInLists) - { - #region Prepare color attributes - - // get the FIRST color type attribute - var colorAttr = _productAttributeService.GetProductVariantAttributesByProductId(minPriceProduct.Id) - .FirstOrDefault(x => x.AttributeControlType == AttributeControlType.ColorSquares); - - if (colorAttr != null) - { - var colorValues = - from a in colorAttr.ProductVariantAttributeValues.Take(50) - where (a.ColorSquaresRgb.HasValue() && !a.ColorSquaresRgb.IsCaseInsensitiveEqual("transparent")) - select new ProductOverviewModel.ColorAttributeModel() - { - Color = a.ColorSquaresRgb, - Alias = a.Alias, - FriendlyName = a.GetLocalized(l => l.Name) - }; - - if (colorValues.Any()) - { - model.ColorAttributes.AddRange(colorValues.Distinct()); - } - } - - #endregion - } - - var currentCustomer = _services.WorkContext.CurrentCustomer; - var taxDisplayType = _services.WorkContext.GetTaxDisplayTypeFor(currentCustomer, _services.StoreContext.CurrentStore.Id); - string taxInfo = T(taxDisplayType == TaxDisplayType.IncludingTax ? "Tax.InclVAT" : "Tax.ExclVAT"); - string shippingInfoLink = _urlHelper.RouteUrl("Topic", new { SystemName = "shippinginfo" }); - - model.ProductMinPriceId = minPriceProduct.Id; - model.Manufacturers = PrepareManufacturersOverviewModel(_manufacturerService.GetProductManufacturersByProductId(product.Id)); + + model.MinPriceProductId = contextProduct.Id; model.ShowSku = _catalogSettings.ShowProductSku; model.ShowWeight = _catalogSettings.ShowWeight; model.ShowDimensions = _catalogSettings.ShowDimensions; - model.Sku = minPriceProduct.Sku; - model.Dimensions = T("Products.DimensionsValue").Text.FormatCurrent( - minPriceProduct.Width.ToString("F2"), - minPriceProduct.Height.ToString("F2"), - minPriceProduct.Length.ToString("F2") + model.Sku = contextProduct.Sku; + model.Dimensions = res["Products.DimensionsValue"].Text.FormatCurrent( + contextProduct.Width.ToString("F2"), + contextProduct.Height.ToString("F2"), + contextProduct.Length.ToString("F2") ); model.DimensionMeasureUnit = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId).Name; model.ThumbDimension = _mediaSettings.ProductThumbPictureSize; model.ShowLegalInfo = _taxSettings.ShowLegalHintsInProductList; - model.LegalInfo = T("Tax.LegalInfoFooter").Text.FormatWith(taxInfo, shippingInfoLink); + model.LegalInfo = legalInfo; model.RatingSum = product.ApprovedRatingSum; model.TotalReviews = product.ApprovedTotalReviews; model.ShowReviews = _catalogSettings.ShowProductReviewsInProductLists; model.ShowDeliveryTimes = _catalogSettings.ShowDeliveryTimesInProductLists; + model.InvisibleDeliveryTime = (product.ProductType == ProductType.GroupedProduct); - var deliveryTime = _deliveryTimeService.GetDeliveryTime(minPriceProduct); - if (deliveryTime != null) - { - model.DeliveryTimeName = deliveryTime.GetLocalized(x => x.Name); - model.DeliveryTimeHexValue = deliveryTime.ColorHexValue; - } - - model.IsShipEnabled = minPriceProduct.IsShipEnabled; - model.DisplayDeliveryTimeAccordingToStock = minPriceProduct.DisplayDeliveryTimeAccordingToStock(_catalogSettings); - model.StockAvailablity = minPriceProduct.FormatStockMessage(_localizationService); + model.IsShipEnabled = contextProduct.IsShipEnabled; + model.DisplayDeliveryTimeAccordingToStock = contextProduct.DisplayDeliveryTimeAccordingToStock(_catalogSettings); + model.StockAvailablity = contextProduct.FormatStockMessage(_localizationService); model.DisplayBasePrice = _catalogSettings.ShowBasePriceInProductLists; - model.BasePriceInfo = minPriceProduct.GetBasePriceInfo(_localizationService, _priceFormatter); model.CompareEnabled = _catalogSettings.CompareProductsEnabled; model.HideBuyButtonInLists = _catalogSettings.HideBuyButtonInLists; - var addShippingPrice = _currencyService.ConvertCurrency(minPriceProduct.AdditionalShippingCharge, - _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId), _services.WorkContext.WorkingCurrency); + if (model.ShowDeliveryTimes) + { + var deliveryTime = _deliveryTimeService.GetDeliveryTime(contextProduct); + if (deliveryTime != null) + { + model.DeliveryTimeName = deliveryTime.GetLocalized(x => x.Name); + model.DeliveryTimeHexValue = deliveryTime.ColorHexValue; + } + } - if (addShippingPrice > 0 && _services.Permissions.Authorize(StandardPermissionProvider.DisplayPrices)) + if (prepareManufacturers) { - model.TransportSurcharge = T("Common.AdditionalShippingSurcharge").Text.FormatWith(_priceFormatter.FormatPrice(addShippingPrice, true, false)); + model.Manufacturers = PrepareManufacturersOverviewModel(_manufacturerService.GetProductManufacturersByProductId(product.Id), cachedManufacturerModels, false); } - if (minPriceProduct.Weight > 0) + if (finalPrice != decimal.Zero && (_catalogSettings.ShowBasePriceInProductLists || isCompareList)) { - model.Weight = "{0} {1}".FormatCurrent(minPriceProduct.Weight.ToString("F2"), _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name); + model.BasePriceInfo = contextProduct.GetBasePriceInfo(finalPrice, _localizationService, _priceFormatter, workingCurrency); + } + + if (displayPrices) + { + var addShippingPrice = _currencyService.ConvertCurrency(contextProduct.AdditionalShippingCharge, currentStore.PrimaryStoreCurrency, workingCurrency); + + if (addShippingPrice > 0) + { + model.TransportSurcharge = res["Common.AdditionalShippingSurcharge"].Text.FormatCurrent(_priceFormatter.FormatPrice(addShippingPrice, true, false)); + } + } + + if (contextProduct.Weight > 0) + { + model.Weight = "{0} {1}".FormatCurrent(contextProduct.Weight.ToString("F2"), _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name); } // IsNew if (_catalogSettings.LabelAsNewForMaxDays.HasValue) { - model.IsNew = (DateTime.UtcNow - product.CreatedOnUtc).Days <= _catalogSettings.LabelAsNewForMaxDays.Value; + model.IsNew = ((DateTime.UtcNow - product.CreatedOnUtc).Days <= _catalogSettings.LabelAsNewForMaxDays.Value); } models.Add(model); } + return models; } @@ -1317,7 +1485,7 @@ public IList PrepareProductSpecificationModel(Product var model = _specificationAttributeService.GetProductSpecificationAttributesByProductId(product.Id, null, true) .Select(psa => { - return new ProductSpecificationModel() + return new ProductSpecificationModel { SpecificationAttributeId = psa.SpecificationAttributeOption.SpecificationAttributeId, SpecificationAttributeName = psa.SpecificationAttributeOption.SpecificationAttribute.GetLocalized(x => x.Name), @@ -1481,7 +1649,7 @@ public void PreparePagingFilteringModel(PagingFilteringModel model, PagingFilter foreach (ProductSortingEnum enumValue in Enum.GetValues(typeof(ProductSortingEnum))) { - if (enumValue == ProductSortingEnum.CreatedOnAsc) + if (enumValue == ProductSortingEnum.CreatedOnAsc || enumValue == ProductSortingEnum.Initial) { // TODO: (MC) das von uns eingeführte "CreatedOnAsc" schmeiß ich // jetzt deshalb aus der UI raus, weil wir diese Sortier-Option @@ -1603,32 +1771,40 @@ public void PreparePagingFilteringModel(PagingFilteringModel model, PagingFilter command.PageSize = pageSizeContext.PageSize; } - public List PrepareManufacturersOverviewModel(ICollection manufacturers) + public List PrepareManufacturersOverviewModel( + ICollection manufacturers, + IDictionary cachedModels = null, + bool forProductDetailPage = false) { var model = new List(); + if (cachedModels == null) + { + cachedModels = new Dictionary(); + } + foreach (var pm in manufacturers) { var manufacturer = pm.Manufacturer; - var item = new ManufacturerOverviewModel - { - Id = manufacturer.Id, - Name = manufacturer.Name, - Description = manufacturer.Description, - SeName = manufacturer.GetSeName() + ManufacturerOverviewModel item; - }; - - Picture pic = manufacturer.Picture; - if (pic != null) + if (!cachedModels.TryGetValue(manufacturer.Id, out item)) { - item.PictureModel = new PictureModel + item = new ManufacturerOverviewModel { - PictureId = pic.Id, - Title = T("Media.Product.ImageLinkTitleFormat", manufacturer.Name), - AlternateText = T("Media.Product.ImageAlternateTextFormat", manufacturer.Name), - ImageUrl = _pictureService.GetPictureUrl(pic), + Id = manufacturer.Id, + Name = manufacturer.Name, + Description = manufacturer.Description, + SeName = manufacturer.GetSeName() + }; + + if (_catalogSettings.ShowManufacturerPicturesInProductDetail) + { + item.PictureModel = PrepareManufacturerPictureModel(manufacturer, manufacturer.GetLocalized(x => x.Name)); + } + + cachedModels.Add(item.Id, item); } model.Add(item); @@ -1637,6 +1813,35 @@ public List PrepareManufacturersOverviewModel(ICollec return model; } + public PictureModel PrepareManufacturerPictureModel(Manufacturer manufacturer, string localizedName) + { + var model = new PictureModel(); + + var pictureSize = _mediaSettings.ManufacturerThumbPictureSize; + var manufacturerPictureCacheKey = string.Format(ModelCacheEventConsumer.MANUFACTURER_PICTURE_MODEL_KEY, + manufacturer.Id, + pictureSize, + !_catalogSettings.HideManufacturerDefaultPictures, + _services.WorkContext.WorkingLanguage.Id, + _services.WebHelper.IsCurrentConnectionSecured(), + _services.StoreContext.CurrentStore.Id); + + model = _services.Cache.Get(manufacturerPictureCacheKey, () => + { + var pictureModel = new PictureModel + { + PictureId = manufacturer.PictureId.GetValueOrDefault(), + //FullSizeImageUrl = _pictureService.GetPictureUrl(manufacturer.PictureId.GetValueOrDefault()), + ImageUrl = _pictureService.GetPictureUrl(manufacturer.PictureId.GetValueOrDefault(), pictureSize, !_catalogSettings.HideManufacturerDefaultPictures), + Title = string.Format(T("Media.Manufacturer.ImageLinkTitleFormat"), localizedName), + AlternateText = string.Format(T("Media.Manufacturer.ImageAlternateTextFormat"), localizedName) + }; + return pictureModel; + }); + + return model; + } + } #region Nested Classes diff --git a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs index 8b1e37d65e..5047764d52 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Web; using System.Web.Mvc; -using System.Web.Routing; using SmartStore.Core; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; @@ -11,24 +10,24 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Plugins; +using SmartStore.Core.Html; +using SmartStore.Core.Logging; using SmartStore.Services.Catalog; using SmartStore.Services.Common; +using SmartStore.Services.Configuration; using SmartStore.Services.Customers; using SmartStore.Services.Directory; using SmartStore.Services.Localization; -using SmartStore.Core.Logging; using SmartStore.Services.Orders; using SmartStore.Services.Payments; using SmartStore.Services.Shipping; using SmartStore.Services.Tax; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Plugins; using SmartStore.Web.Framework.Security; using SmartStore.Web.Models.Checkout; using SmartStore.Web.Models.Common; -using SmartStore.Services.Configuration; -using SmartStore.Web.Framework.Plugins; -using SmartStore.Web.Models.ShoppingCart; namespace SmartStore.Web.Controllers { @@ -205,6 +204,7 @@ protected CheckoutShippingMethodModel PrepareShippingMethodModel(IList !String.IsNullOrEmpty(so.Name) && so.Name.Equals(selectedShippingOption.Name, StringComparison.InvariantCultureIgnoreCase) && !String.IsNullOrEmpty(so.ShippingRateComputationMethodSystemName) && so.ShippingRateComputationMethodSystemName.Equals(selectedShippingOption.ShippingRateComputationMethodSystemName, StringComparison.InvariantCultureIgnoreCase)); + if (shippingOptionToSelect != null) shippingOptionToSelect.Selected = true; } @@ -268,6 +269,7 @@ protected CheckoutPaymentMethodModel PreparePaymentMethodModel(IList decimal.Zero) { model.DisplayRewardPoints = true; @@ -276,18 +278,22 @@ protected CheckoutPaymentMethodModel PreparePaymentMethodModel(IList pm.Value.PaymentMethodType == PaymentMethodType.Standard || pm.Value.PaymentMethodType == PaymentMethodType.Redirection || - pm.Value.PaymentMethodType == PaymentMethodType.StandardAndRedirection) + .LoadActivePaymentMethods(_workContext.CurrentCustomer, cart, _storeContext.CurrentStore.Id, paymentTypes) .ToList(); + var allPaymentMethods = _paymentService.GetAllPaymentMethods(); + foreach (var pm in boundPaymentMethods) { if (cart.IsRecurring() && pm.Value.RecurringPaymentType == RecurringPaymentType.NotSupported) continue; + + var paymentMethod = allPaymentMethods.FirstOrDefault(x => x.PaymentMethodSystemName.IsCaseInsensitiveEqual(pm.Metadata.SystemName)); - var pmModel = new CheckoutPaymentMethodModel.PaymentMethodModel() + var pmModel = new CheckoutPaymentMethodModel.PaymentMethodModel { Name = _pluginMediator.GetLocalizedFriendlyName(pm.Metadata), Description = _pluginMediator.GetLocalizedDescription(pm.Metadata), @@ -295,6 +301,11 @@ protected CheckoutPaymentMethodModel PreparePaymentMethodModel(IList x.FullDescription, _workContext.WorkingLanguage.Id); + } pmModel.BrandUrl = _pluginMediator.GetBrandImageUrl(pm.Metadata); @@ -349,6 +360,7 @@ protected CheckoutConfirmModel PrepareConfirmOrderModel(IList(_workContext.CurrentCustomer, SystemCustomerAttributeNames.SelectedShippingOption, null, _storeContext.CurrentStore.Id); return RedirectToAction("PaymentMethod"); - } - + } //model var model = PrepareShippingMethodModel(cart); @@ -619,9 +630,11 @@ public ActionResult SelectShippingMethod(string shippingoption) //parse selected method if (String.IsNullOrEmpty(shippingoption)) return ShippingMethod(); - var splittedOption = shippingoption.Split(new string[] { "___" }, StringSplitOptions.RemoveEmptyEntries); + + var splittedOption = shippingoption.Split(new string[] { "___" }, StringSplitOptions.RemoveEmptyEntries); if (splittedOption.Length != 2) return ShippingMethod(); + string selectedName = splittedOption[0]; string shippingRateComputationMethodSystemName = splittedOption[1]; @@ -643,8 +656,8 @@ public ActionResult SelectShippingMethod(string shippingoption) .ToList(); } - var shippingOption = shippingOptions - .Find(so => !String.IsNullOrEmpty(so.Name) && so.Name.Equals(selectedName, StringComparison.InvariantCultureIgnoreCase)); + var shippingOption = shippingOptions.Find(so => !String.IsNullOrEmpty(so.Name) && so.Name.Equals(selectedName, StringComparison.InvariantCultureIgnoreCase)); + if (shippingOption == null) return ShippingMethod(); @@ -681,7 +694,7 @@ public ActionResult PaymentMethod() _genericAttributeService.SaveAttribute( _workContext.CurrentCustomer, SystemCustomerAttributeNames.SelectedPaymentMethod, - (!isPaymentWorkflowRequired || !model.PaymentMethods.Any()) ? null : model.PaymentMethods[0].PaymentMethodSystemName, + !model.PaymentMethods.Any() ? null : model.PaymentMethods[0].PaymentMethodSystemName, _storeContext.CurrentStore.Id); _httpContext.GetCheckoutState().IsPaymentSelectionSkipped = true; @@ -783,94 +796,96 @@ public ActionResult Confirm() [ValidateInput(false)] public ActionResult ConfirmOrder(FormCollection form) { - //validation - var cart = _workContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, _storeContext.CurrentStore.Id); + //validation + var storeId = _storeContext.CurrentStore.Id; + var customer = _workContext.CurrentCustomer; + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, storeId); if (cart.Count == 0) return RedirectToRoute("ShoppingCart"); - if ((_workContext.CurrentCustomer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed)) + if ((customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed)) return new HttpUnauthorizedResult(); - - //model var model = new CheckoutConfirmModel(); - try - { - bool isPaymentPaymentWorkflowRequired = IsPaymentWorkflowRequired(cart); + PlaceOrderResult placeOrderResult = null; + PostProcessPaymentRequest postProcessPaymentRequest = null; + try + { var processPaymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; if (processPaymentRequest == null) { //Check whether payment workflow is required - if (isPaymentPaymentWorkflowRequired) + if (IsPaymentWorkflowRequired(cart)) return RedirectToAction("PaymentMethod"); processPaymentRequest = new ProcessPaymentRequest(); } //prevent 2 orders being placed within an X seconds time frame - if (!IsMinimumOrderPlacementIntervalValid(_workContext.CurrentCustomer)) - throw new Exception(_localizationService.GetResource("Checkout.MinOrderPlacementInterval")); + if (!IsMinimumOrderPlacementIntervalValid(customer)) + throw new Exception(T("Checkout.MinOrderPlacementInterval")); //place order - processPaymentRequest.StoreId = _storeContext.CurrentStore.Id; - processPaymentRequest.CustomerId = _workContext.CurrentCustomer.Id; - processPaymentRequest.PaymentMethodSystemName = _workContext.CurrentCustomer.GetAttribute( - SystemCustomerAttributeNames.SelectedPaymentMethod, _genericAttributeService, _storeContext.CurrentStore.Id); + processPaymentRequest.StoreId = storeId; + processPaymentRequest.CustomerId = customer.Id; + processPaymentRequest.PaymentMethodSystemName = customer.GetAttribute(SystemCustomerAttributeNames.SelectedPaymentMethod, _genericAttributeService, storeId); var placeOrderExtraData = new Dictionary(); placeOrderExtraData["CustomerComment"] = form["customercommenthidden"]; + placeOrderExtraData["SubscribeToNewsLetter"] = form["SubscribeToNewsLetterHidden"]; + placeOrderExtraData["AcceptThirdPartyEmailHandOver"] = form["AcceptThirdPartyEmailHandOverHidden"]; - var placeOrderResult = _orderProcessingService.PlaceOrder(processPaymentRequest, placeOrderExtraData); + placeOrderResult = _orderProcessingService.PlaceOrder(processPaymentRequest, placeOrderExtraData); - if (placeOrderResult.Success) + if (!placeOrderResult.Success) { - if (isPaymentPaymentWorkflowRequired) - { - var postProcessPaymentRequest = new PostProcessPaymentRequest() - { - Order = placeOrderResult.PlacedOrder - }; - _paymentService.PostProcessPayment(postProcessPaymentRequest); - } - - _httpContext.Session["PaymentData"] = null; - _httpContext.Session["OrderPaymentInfo"] = null; - _httpContext.RemoveCheckoutState(); - - if (_webHelper.IsRequestBeingRedirected || _webHelper.IsPostBeingDone) - { - //redirection or POST has been done in PostProcessPayment - return Content("Redirected"); - } - else - { - //if no redirection has been done (to a third-party payment page) - //theoretically it's not possible - return RedirectToAction("Completed"); - } - } - else - { - foreach (var error in placeOrderResult.Errors) - model.Warnings.Add(error); + model.Warnings.AddRange(placeOrderResult.Errors.Select(x => HtmlUtils.ConvertPlainTextToHtml(x))); } } - catch (Exception exc) + catch (Exception exception) { - Logger.Warning(exc.Message, exc); - model.Warnings.Add(exc.Message); + Logger.Warning(exception.Message, exception); + + if (!model.Warnings.Any(x => x == exception.Message)) + { + model.Warnings.Add(exception.Message); + } } - //If we got this far, something failed, redisplay form + if (placeOrderResult == null || !placeOrderResult.Success || model.Warnings.Any()) + { + return View(model); + } - //if (model.Warnings.Count > 0) - // TempData["ConfirmOrderWarnings"] = model.Warnings; + try + { + postProcessPaymentRequest = new PostProcessPaymentRequest + { + Order = placeOrderResult.PlacedOrder + }; - //return RedirectToRoute("CheckoutConfirm"); - return View(model); - } + _paymentService.PostProcessPayment(postProcessPaymentRequest); + } + catch (Exception exception) + { + NotifyError(exception); + } + finally + { + _httpContext.Session["PaymentData"] = null; + _httpContext.Session["OrderPaymentInfo"] = null; + _httpContext.RemoveCheckoutState(); + } + + if (postProcessPaymentRequest != null && postProcessPaymentRequest.RedirectUrl.HasValue()) + { + return Redirect(postProcessPaymentRequest.RedirectUrl); + } + + return RedirectToAction("Completed"); + } public ActionResult Completed() diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index 735b3891ee..05cdc3bdc6 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.Globalization; using System.Linq; using System.Text; -using System.Web; using System.Web.Mvc; +using SmartStore.Core.Data; +using SmartStore.Core.Domain; using SmartStore.Core.Domain.Blogs; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; @@ -15,7 +15,9 @@ 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.Security; using SmartStore.Core.Domain.Seo; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Domain.Themes; @@ -33,75 +35,92 @@ using SmartStore.Services.Orders; using SmartStore.Services.Security; using SmartStore.Services.Topics; +using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Localization; using SmartStore.Web.Framework.Pdf; -using SmartStore.Web.Framework.Themes; +using SmartStore.Web.Framework.Theming; using SmartStore.Web.Framework.UI; using SmartStore.Web.Infrastructure.Cache; using SmartStore.Web.Models.Common; +using SmartStore.Services.Seo; namespace SmartStore.Web.Controllers { - public partial class CommonController : PublicControllerBase + public partial class CommonController : PublicControllerBase { - #region Fields - - private readonly ITopicService _topicService; - private readonly ILanguageService _languageService; - private readonly ICurrencyService _currencyService; - private readonly IThemeContext _themeContext; - private readonly IThemeRegistry _themeRegistry; - private readonly IForumService _forumservice; - private readonly IGenericAttributeService _genericAttributeService; - private readonly IMobileDeviceHelper _mobileDeviceHelper; - private readonly static string[] s_hints = new string[] { "Shopsystem", "Onlineshop Software", "Shopsoftware", "E-Commerce Solution" }; - private readonly CustomerSettings _customerSettings; + private readonly ICommonServices _services; + private readonly ITopicService _topicService; + private readonly Lazy _languageService; + private readonly Lazy _currencyService; + private readonly IThemeContext _themeContext; + private readonly Lazy _themeRegistry; + private readonly Lazy _forumservice; + private readonly Lazy _genericAttributeService; + private readonly Lazy _mobileDeviceHelper; + private readonly Lazy _compareProductsService; + private readonly Lazy _urlRecordService; + + private readonly StoreInformationSettings _storeInfoSettings; + private readonly CustomerSettings _customerSettings; private readonly TaxSettings _taxSettings; private readonly CatalogSettings _catalogSettings; private readonly ThemeSettings _themeSettings; private readonly CommonSettings _commonSettings; + private readonly NewsSettings _newsSettings; private readonly BlogSettings _blogSettings; private readonly ForumSettings _forumSettings; private readonly LocalizationSettings _localizationSettings; - - private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly Lazy _securitySettings; + private readonly Lazy _socialSettings; + private readonly Lazy _mediaSettings; + private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly IPriceFormatter _priceFormatter; private readonly IPageAssetsBuilder _pageAssetsBuilder; private readonly Lazy _pictureService; - private readonly ICommonServices _services; - - #endregion - - #region Constructors + private readonly Lazy _manufacturerService; + private readonly Lazy _categoryService; + private readonly Lazy _productService; public CommonController( + ICommonServices services, ITopicService topicService, - ILanguageService languageService, - ICurrencyService currencyService, + Lazy languageService, + Lazy currencyService, IThemeContext themeContext, - IThemeRegistry themeRegistry, - IForumService forumService, - IGenericAttributeService genericAttributeService, - IMobileDeviceHelper mobileDeviceHelper, - CustomerSettings customerSettings, + Lazy themeRegistry, + Lazy forumService, + Lazy genericAttributeService, + Lazy mobileDeviceHelper, + Lazy compareProductsService, + Lazy urlRecordService, + StoreInformationSettings storeInfoSettings, + CustomerSettings customerSettings, TaxSettings taxSettings, CatalogSettings catalogSettings, EmailAccountSettings emailAccountSettings, CommonSettings commonSettings, + NewsSettings newsSettings, BlogSettings blogSettings, ForumSettings forumSettings, LocalizationSettings localizationSettings, - IOrderTotalCalculationService orderTotalCalculationService, + Lazy securitySettings, + Lazy socialSettings, + Lazy mediaSettings, + IOrderTotalCalculationService orderTotalCalculationService, IPriceFormatter priceFormatter, ThemeSettings themeSettings, IPageAssetsBuilder pageAssetsBuilder, Lazy pictureService, - ICommonServices services) + Lazy manufacturerService, + Lazy categoryService, + Lazy productService) { - this._topicService = topicService; + this._services = services; + this._topicService = topicService; this._languageService = languageService; this._currencyService = currencyService; this._themeContext = themeContext; @@ -109,14 +128,21 @@ public CommonController( this._forumservice = forumService; this._genericAttributeService = genericAttributeService; this._mobileDeviceHelper = mobileDeviceHelper; - - this._customerSettings = customerSettings; + this._compareProductsService = compareProductsService; + this._urlRecordService = urlRecordService; + + this._storeInfoSettings = storeInfoSettings; + this._customerSettings = customerSettings; this._taxSettings = taxSettings; this._catalogSettings = catalogSettings; this._commonSettings = commonSettings; + this._newsSettings = newsSettings; this._blogSettings = blogSettings; this._forumSettings = forumSettings; this._localizationSettings = localizationSettings; + this._securitySettings = securitySettings; + this._socialSettings = socialSettings; + this._mediaSettings = mediaSettings; this._orderTotalCalculationService = orderTotalCalculationService; this._priceFormatter = priceFormatter; @@ -124,15 +150,11 @@ public CommonController( this._themeSettings = themeSettings; this._pageAssetsBuilder = pageAssetsBuilder; this._pictureService = pictureService; - this._services = services; - - T = NullLocalizer.Instance; + this._manufacturerService = manufacturerService; + this._categoryService = categoryService; + this._productService = productService; } - public Localizer T { get; set; } - - #endregion - #region Utilities [NonAction] @@ -140,13 +162,13 @@ protected LanguageSelectorModel PrepareLanguageSelectorModel() { var availableLanguages = _services.Cache.Get(string.Format(ModelCacheEventConsumer.AVAILABLE_LANGUAGES_MODEL_KEY, _services.StoreContext.CurrentStore.Id), () => { - var result = _languageService + var result = _languageService.Value .GetAllLanguages(storeId: _services.StoreContext.CurrentStore.Id) .Select(x => new LanguageModel { Id = x.Id, Name = x.Name, - NativeName = GetLanguageNativeName(x.LanguageCulture) ?? x.Name, + NativeName = LocalizationHelper.GetLanguageNativeName(x.LanguageCulture) ?? x.Name, ISOCode = x.LanguageCulture, SeoCode = x.UniqueSeoCode, FlagImageFileName = x.FlagImageFileName @@ -157,20 +179,21 @@ protected LanguageSelectorModel PrepareLanguageSelectorModel() var workingLanguage = _services.WorkContext.WorkingLanguage; - var model = new LanguageSelectorModel() + var model = new LanguageSelectorModel { CurrentLanguageId = workingLanguage.Id, AvailableLanguages = availableLanguages, UseImages = _localizationSettings.UseImagesForLanguageSelection }; - string defaultSeoCode = _languageService.GetDefaultLanguageSeoCode(); + string defaultSeoCode = _languageService.Value.GetDefaultLanguageSeoCode(); foreach (var lang in model.AvailableLanguages) { - var helper = new LocalizedUrlHelper(HttpContext.Request, true); + //var helper = new LocalizedUrlHelper(HttpContext.Request, true); + var helper = CreateUrlHelperForLanguageSelector(lang, workingLanguage.Id); - if (_localizationSettings.SeoFriendlyUrlsForLanguagesEnabled) + if (_localizationSettings.SeoFriendlyUrlsForLanguagesEnabled) { if (lang.SeoCode == defaultSeoCode && (int)(_localizationSettings.DefaultLanguageRedirectBehaviour) > 0) { @@ -188,41 +211,53 @@ protected LanguageSelectorModel PrepareLanguageSelectorModel() return model; } - // TODO: (MC) zentral auslagern - private string GetLanguageNativeName(string locale) - { - try - { - if (!string.IsNullOrEmpty(locale)) - { - var info = CultureInfo.GetCultureInfoByIetfLanguageTag(locale); - if (info == null) - { - return null; - } - return info.NativeName; - } - return null; - } - catch - { - return null; - } - } + private LocalizedUrlHelper CreateUrlHelperForLanguageSelector(LanguageModel model, int currentLanguageId) + { + if (currentLanguageId != model.Id) + { + var routeValues = this.Request.RequestContext.RouteData.Values; + var controller = routeValues["controller"].ToString(); + + object val; + if (!routeValues.TryGetValue(controller + "id", out val)) + { + controller = routeValues["action"].ToString(); + routeValues.TryGetValue(controller + "id", out val); + } + + int entityId = 0; + if (val != null) + { + entityId = val.Convert(); + } + + if (entityId > 0) + { + var activeSlug = _urlRecordService.Value.GetActiveSlug(entityId, controller, model.Id); + if (activeSlug.HasValue()) + { + var helper = new LocalizedUrlHelper(Request.ApplicationPath, activeSlug, false); + return helper; + } + } + } + + return new LocalizedUrlHelper(HttpContext.Request, true); + } [NonAction] protected CurrencySelectorModel PrepareCurrencySelectorModel() { var availableCurrencies = _services.Cache.Get(string.Format(ModelCacheEventConsumer.AVAILABLE_CURRENCIES_MODEL_KEY, _services.WorkContext.WorkingLanguage.Id, _services.StoreContext.CurrentStore.Id), () => { - var result = _currencyService + var result = _currencyService.Value .GetAllCurrencies(storeId: _services.StoreContext.CurrentStore.Id) .Select(x => new CurrencyModel() { Id = x.Id, Name = x.GetLocalized(y => y.Name), ISOCode = x.CurrencyCode, - Symbol = GetCurrencySymbol(x.DisplayLocale) ?? x.CurrencyCode + Symbol = LocalizationHelper.GetCurrencySymbol(x.DisplayLocale) ?? x.CurrencyCode }) .ToList(); return result; @@ -236,28 +271,6 @@ protected CurrencySelectorModel PrepareCurrencySelectorModel() return model; } - // TODO: Zentral auslagern - private static string GetCurrencySymbol(string locale) - { - try - { - if (!string.IsNullOrEmpty(locale)) - { - var info = new RegionInfo(locale); - if (info == null) - { - return null; - } - return info.CurrencySymbol; - } - return null; - } - catch - { - return null; - } - } - [NonAction] protected TaxTypeSelectorModel PrepareTaxTypeSelectorModel() { @@ -276,7 +289,7 @@ protected int GetUnreadPrivateMessages() var customer = _services.WorkContext.CurrentCustomer; if (_forumSettings.AllowPrivateMessages && !customer.IsGuest()) { - var privateMessages = _forumservice.GetAllPrivateMessages(_services.StoreContext.CurrentStore.Id, 0, customer.Id, false, null, false, string.Empty, 0, 1); + var privateMessages = _forumservice.Value.GetAllPrivateMessages(_services.StoreContext.CurrentStore.Id, 0, customer.Id, false, null, false, string.Empty, 0, 1); if (privateMessages.TotalCount > 0) { @@ -350,23 +363,11 @@ public ActionResult Header() public ActionResult SetLanguage(int langid, string returnUrl = "") { - var language = _languageService.GetLanguageById(langid); + var language = _languageService.Value.GetLanguageById(langid); if (language != null && language.Published) { _services.WorkContext.WorkingLanguage = language; } - - // url referrer - if (String.IsNullOrEmpty(returnUrl)) - { - returnUrl = _services.WebHelper.GetUrlReferrer(); - } - - // home page - if (String.IsNullOrEmpty(returnUrl)) - { - returnUrl = Url.RouteUrl("HomePage"); - } if (_localizationSettings.SeoFriendlyUrlsForLanguagesEnabled) { @@ -375,10 +376,9 @@ public ActionResult SetLanguage(int langid, string returnUrl = "") returnUrl = helper.GetAbsolutePath(); } - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } - //currency [ChildActionOnly] public ActionResult CurrencySelector() { @@ -389,40 +389,31 @@ public ActionResult CurrencySelector() return PartialView(model); } + public ActionResult CurrencySelected(int customerCurrency, string returnUrl = "") { - var currency = _currencyService.GetCurrencyById(customerCurrency); + var currency = _currencyService.Value.GetCurrencyById(customerCurrency); if (currency != null) + { _services.WorkContext.WorkingCurrency = currency; + } - //url referrer - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = _services.WebHelper.GetUrlReferrer(); - //home page - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = Url.RouteUrl("HomePage"); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } - //tax type [ChildActionOnly] public ActionResult TaxTypeSelector() { var model = PrepareTaxTypeSelectorModel(); return PartialView(model); } + public ActionResult TaxTypeSelected(int customerTaxType, string returnUrl = "") { var taxDisplayType = (TaxDisplayType)Enum.ToObject(typeof(TaxDisplayType), customerTaxType); _services.WorkContext.TaxDisplayType = taxDisplayType; - //url referrer - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = _services.WebHelper.GetUrlReferrer(); - //home page - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = Url.RouteUrl("HomePage"); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } //Configuration page (used on mobile devices) @@ -446,7 +437,7 @@ public ActionResult Settings() return View(); } - //footer + // footer [ChildActionOnly] public ActionResult JavaScriptDisabledWarning() { @@ -456,7 +447,7 @@ public ActionResult JavaScriptDisabledWarning() return PartialView(); } - //header links + // header links [ChildActionOnly] public ActionResult HeaderLinks() { @@ -474,12 +465,12 @@ public ActionResult HeaderLinks() if (_forumSettings.ShowAlertForPM && !customer.GetAttribute(SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, _services.StoreContext.CurrentStore.Id)) { - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, true, _services.StoreContext.CurrentStore.Id); + _genericAttributeService.Value.SaveAttribute(customer, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, true, _services.StoreContext.CurrentStore.Id); alertMessage = T("PrivateMessages.YouHaveUnreadPM", unreadMessageCount); } } - var model = new HeaderLinksModel() + var model = new HeaderLinksModel { IsAuthenticated = customer.IsRegistered(), CustomerEmailUsername = customer.IsRegistered() ? (_customerSettings.UsernamesEnabled ? customer.Username : customer.Email) : "", @@ -502,7 +493,18 @@ public ActionResult ShopBar() { var customer = _services.WorkContext.CurrentCustomer; - var unreadMessageCount = GetUnreadPrivateMessages(); + var isAdmin = customer.IsAdmin(); + var isRegistered = isAdmin || customer.IsRegistered(); + + if (_storeInfoSettings.StoreClosed) + { + if (!isAdmin || !_storeInfoSettings.StoreClosedAllowForAdmins) + { + return Content(""); + } + } + + var unreadMessageCount = GetUnreadPrivateMessages(); var unreadMessage = string.Empty; var alertMessage = string.Empty; if (unreadMessageCount > 0) @@ -513,7 +515,7 @@ public ActionResult ShopBar() if (_forumSettings.ShowAlertForPM && !customer.GetAttribute(SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, _services.StoreContext.CurrentStore.Id)) { - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, true, _services.StoreContext.CurrentStore.Id); + _genericAttributeService.Value.SaveAttribute(customer, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, true, _services.StoreContext.CurrentStore.Id); alertMessage = T("PrivateMessages.YouHaveUnreadPM", unreadMessageCount); } } @@ -534,12 +536,12 @@ public ActionResult ShopBar() out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscount, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); subtotalBase = subTotalWithoutDiscountBase; - subtotal = _currencyService.ConvertFromPrimaryStoreCurrency(subtotalBase, _services.WorkContext.WorkingCurrency); + subtotal = _currencyService.Value.ConvertFromPrimaryStoreCurrency(subtotalBase, _services.WorkContext.WorkingCurrency); } - var model = new ShopBarModel() + var model = new ShopBarModel { - IsAuthenticated = customer.IsRegistered(), - CustomerEmailUsername = customer.IsRegistered() ? (_customerSettings.UsernamesEnabled ? customer.Username : customer.Email) : "", + IsAuthenticated = isRegistered, + CustomerEmailUsername = isRegistered ? (_customerSettings.UsernamesEnabled ? customer.Username : customer.Email) : "", IsCustomerImpersonated = _services.WorkContext.OriginalCustomerIfImpersonated != null, DisplayAdminLink = _services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel), ShoppingCartEnabled = _services.Permissions.Authorize(StandardPermissionProvider.EnableShoppingCart), @@ -551,7 +553,7 @@ public ActionResult ShopBar() CompareProductsEnabled = _catalogSettings.CompareProductsEnabled }; - if (model.ShoppingCartEnabled || model.WishlistEnabled) + if (model.ShoppingCartEnabled || model.WishlistEnabled) { if (model.ShoppingCartEnabled) model.ShoppingCartItems = cart.GetTotalProducts(); @@ -562,7 +564,7 @@ public ActionResult ShopBar() if (_catalogSettings.CompareProductsEnabled) { - model.CompareItems = EngineContext.Current.Resolve().GetComparedProductsCount(); + model.CompareItems = _compareProductsService.Value.GetComparedProductsCount(); } return PartialView(model); @@ -571,14 +573,13 @@ public ActionResult ShopBar() [ChildActionOnly] public ActionResult Footer() { - string taxInfo = (_services.WorkContext.GetTaxDisplayTypeFor(_services.WorkContext.CurrentCustomer, _services.StoreContext.CurrentStore.Id) == TaxDisplayType.IncludingTax) - ? T("Tax.InclVAT") - : T("Tax.ExclVAT"); - - string shippingInfoLink = Url.RouteUrl("Topic", new { SystemName = "shippinginfo" }); var store = _services.StoreContext.CurrentStore; + var allTopics = _topicService.GetAllTopics(store.Id); + var taxDisplayType = _services.WorkContext.GetTaxDisplayTypeFor(_services.WorkContext.CurrentCustomer, store.Id); + + var taxInfo = T(taxDisplayType == TaxDisplayType.IncludingTax ? "Tax.InclVAT" : "Tax.ExclVAT"); - var AvailableStoreThemes = _themeRegistry.GetThemeManifests() + var availableStoreThemes = !_themeSettings.AllowCustomerToSelectTheme ? new List() : _themeRegistry.Value.GetThemeManifests() .Where(x => !x.MobileTheme) .Select(x => { @@ -590,17 +591,36 @@ public ActionResult Footer() }) .ToList(); - var model = new FooterModel() + var model = new FooterModel { StoreName = store.Name, - LegalInfo = T("Tax.LegalInfoFooter", taxInfo, shippingInfoLink), ShowLegalInfo = _taxSettings.ShowLegalHintsInFooter, - ShowThemeSelector = _themeSettings.AllowCustomerToSelectTheme && AvailableStoreThemes.Count > 1, + ShowThemeSelector = availableStoreThemes.Count > 1, BlogEnabled = _blogSettings.Enabled, ForumEnabled = _forumSettings.ForumsEnabled, HideNewsletterBlock = _customerSettings.HideNewsletterBlock, }; + model.TopicPageUrls = allTopics + .Where(x => !x.RenderAsWidget) + .GroupBy(x => x.SystemName) + .ToDictionary(x => x.Key.EmptyNull().ToLower(), x => + { + if (x.Key.IsCaseInsensitiveEqual("contactus")) + return Url.RouteUrl("ContactUs"); + + return Url.RouteUrl("Topic", new { SystemName = x.Key }); + }); + + if (model.TopicPageUrls.ContainsKey("shippinginfo")) + { + model.LegalInfo = T("Tax.LegalInfoFooter", taxInfo, model.TopicPageUrls["shippinginfo"]); + } + else + { + model.LegalInfo = T("Tax.LegalInfoFooter2", taxInfo); + } + var hint = _services.Settings.GetSettingByKey("Rnd_SmCopyrightHint", string.Empty, store.Id); if (hint.IsEmpty()) { @@ -608,30 +628,15 @@ public ActionResult Footer() _services.Settings.SetSetting("Rnd_SmCopyrightHint", hint, store.Id); } - var topics = new string[] { "paymentinfo", "imprint", "disclaimer" }; - foreach (var t in topics) - { - //load by store - var topic = _topicService.GetTopicBySystemName(t, store.Id); - if (topic == null) - //not found. let's find topic assigned to all stores - topic = _topicService.GetTopicBySystemName(t, 0); - - if (topic != null) - { - model.Topics.Add(t, topic.Title); - } - } - - var socialSettings = EngineContext.Current.Resolve(); + model.ShowSocialLinks = _socialSettings.Value.ShowSocialLinksInFooter; + model.FacebookLink = _socialSettings.Value.FacebookLink; + model.GooglePlusLink = _socialSettings.Value.GooglePlusLink; + model.TwitterLink = _socialSettings.Value.TwitterLink; + model.PinterestLink = _socialSettings.Value.PinterestLink; + model.YoutubeLink = _socialSettings.Value.YoutubeLink; - model.ShowSocialLinks = socialSettings.ShowSocialLinksInFooter; - model.FacebookLink = socialSettings.FacebookLink; - model.GooglePlusLink = socialSettings.GooglePlusLink; - model.TwitterLink = socialSettings.TwitterLink; - model.PinterestLink = socialSettings.PinterestLink; - model.YoutubeLink = socialSettings.YoutubeLink; - model.SmartStoreHint = "{0} by SmartStore AG © {1}".FormatCurrent(hint, DateTime.Now.Year); + model.SmartStoreHint = "{0} by SmartStore AG © {1}" + .FormatCurrent(hint, DateTime.Now.Year); return PartialView(model); } @@ -639,11 +644,13 @@ public ActionResult Footer() [ChildActionOnly] public ActionResult Menu() { + var store = _services.StoreContext.CurrentStore; var customer = _services.WorkContext.CurrentCustomer; - var model = new MenuModel() + var model = new MenuModel { RecentlyAddedProductsEnabled = _catalogSettings.RecentlyAddedProductsEnabled, + NewsEnabled = _newsSettings.Enabled, BlogEnabled = _blogSettings.Enabled, ForumEnabled = _forumSettings.ForumsEnabled, AllowPrivateMessages = _forumSettings.AllowPrivateMessages && customer.IsRegistered(), @@ -652,7 +659,10 @@ public ActionResult Menu() IsCustomerImpersonated = _services.WorkContext.OriginalCustomerIfImpersonated != null, IsAuthenticated = customer.IsRegistered(), DisplayAdminLink = _services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel), - }; + HasContactUsPage = (_topicService.GetTopicBySystemName("ContactUs", store.Id) != null) + }; + + model.DisplayLoginLink = _storeInfoSettings.StoreClosed && !model.DisplayAdminLink; return PartialView(model); } @@ -661,7 +671,10 @@ public ActionResult Menu() [ChildActionOnly] public ActionResult InfoBlock() { - var model = new InfoBlockModel() + var store = _services.StoreContext.CurrentStore; + var allTopics = _topicService.GetAllTopics(store.Id); + + var model = new InfoBlockModel { RecentlyAddedProductsEnabled = _catalogSettings.RecentlyAddedProductsEnabled, RecentlyViewedProductsEnabled = _catalogSettings.RecentlyViewedProductsEnabled, @@ -672,7 +685,18 @@ public ActionResult InfoBlock() AllowPrivateMessages = _forumSettings.AllowPrivateMessages, }; - return PartialView(model); + model.TopicPageUrls = allTopics + .Where(x => !x.RenderAsWidget) + .GroupBy(x => x.SystemName) + .ToDictionary(x => x.Key.EmptyNull().ToLower(), x => + { + if (x.Key.IsCaseInsensitiveEqual("contactus")) + return Url.RouteUrl("ContactUs"); + + return Url.RouteUrl("Topic", new { SystemName = x.Key }); + }); + + return PartialView(model); } [ChildActionOnly] @@ -682,13 +706,13 @@ public ActionResult StoreThemeSelector() return Content(""); var model = new StoreThemeSelectorModel(); - var currentTheme = _themeRegistry.GetThemeManifest(_themeContext.WorkingDesktopTheme); + var currentTheme = _themeRegistry.Value.GetThemeManifest(_themeContext.WorkingDesktopTheme); model.CurrentStoreTheme = new StoreThemeModel() { Name = currentTheme.ThemeName, Title = currentTheme.ThemeTitle }; - model.AvailableStoreThemes = _themeRegistry.GetThemeManifests() + model.AvailableStoreThemes = _themeRegistry.Value.GetThemeManifests() //do not display themes for mobile devices .Where(x => !x.MobileTheme) .Select(x => @@ -703,33 +727,23 @@ public ActionResult StoreThemeSelector() return PartialView(model); } - public ActionResult StoreThemeSelected(string themeName) + public ActionResult ChangeTheme(string themeName, string returnUrl = null) { - _themeContext.WorkingDesktopTheme = themeName; + if (!_themeSettings.AllowCustomerToSelectTheme || (themeName.HasValue() && !_themeRegistry.Value.ThemeManifestExists(themeName))) + { + return HttpNotFound(); + } - var model = new StoreThemeSelectorModel(); - var currentTheme = _themeRegistry.GetThemeManifest(_themeContext.WorkingDesktopTheme); - model.CurrentStoreTheme = new StoreThemeModel() - { - Name = currentTheme.ThemeName, - Title = currentTheme.ThemeTitle - }; - model.AvailableStoreThemes = _themeRegistry.GetThemeManifests() - //do not display themes for mobile devices - .Where(x => !x.MobileTheme) - .Select(x => - { - return new StoreThemeModel() - { - Name = x.ThemeName, - Title = x.ThemeTitle - }; - }) - .ToList(); - return PartialView("StoreThemeSelector", model); + _themeContext.WorkingDesktopTheme = themeName; + + if (HttpContext.Request.IsAjaxRequest()) + { + return Json(new { Success = true }); + } + + return RedirectToReferrer(returnUrl); } - //favicon [ChildActionOnly] [OutputCache(Duration=3600, VaryByCustom="Theme_Store")] public ActionResult Favicon() @@ -770,25 +784,26 @@ public ActionResult Favicon() ///
    /// True - use desktop version; false - use version for mobile devices /// Action result + [HttpPost] public ActionResult ChangeDevice(bool dontUseMobileVersion) { - _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, - SystemCustomerAttributeNames.DontUseMobileVersion, dontUseMobileVersion, _services.StoreContext.CurrentStore.Id); + _genericAttributeService.Value.SaveAttribute( + _services.WorkContext.CurrentCustomer, + SystemCustomerAttributeNames.DontUseMobileVersion, + dontUseMobileVersion, + _services.StoreContext.CurrentStore.Id); - string returnurl = _services.WebHelper.GetUrlReferrer(); - if (String.IsNullOrEmpty(returnurl)) - returnurl = Url.RouteUrl("HomePage"); - return Redirect(returnurl); + return RedirectToReferrer(); } [ChildActionOnly] public ActionResult ChangeDeviceBlock() { - if (!_mobileDeviceHelper.MobileDevicesSupported()) + if (!_mobileDeviceHelper.Value.MobileDevicesSupported()) //mobile devices support is disabled return Content(""); - if (!_mobileDeviceHelper.IsMobileDevice()) + if (!_mobileDeviceHelper.Value.IsMobileDevice()) //request is made by a desktop computer return Content(""); @@ -801,55 +816,56 @@ public ActionResult RobotsTextFile() { "/bin/", "/Content/files/", - "/Content/files/exportimport/", - "/country/getstatesbycountryid", - "/install", - "/product/setreviewhelpfulness", + "/Content/files/ExportImport/", + "/Exchange/", + "/Country/GetStatesByCountryId", + "/Install", + "/Product/SetReviewHelpfulness", }; var localizableDisallowPaths = new List() { - "/boards/forumwatch", - "/boards/postedit", - "/boards/postdelete", - "/boards/postcreate", - "/boards/topicedit", - "/boards/topicdelete", - "/boards/topiccreate", - "/boards/topicmove", - "/boards/topicwatch", - "/cart", - "/checkout", - "/product/clearcomparelist", - "/compareproducts", - "/customer/avatar", - "/customer/activation", - "/customer/addresses", - "/customer/backinstocksubscriptions", - "/customer/changepassword", - "/customer/checkusernameavailability", - "/customer/downloadableproducts", - "/customer/forumsubscriptions", - "/customer/deleteforumsubscriptions", - "/customer/info", - "/customer/orders", - "/customer/returnrequests", - "/customer/rewardpoints", - "/privatemessages", - "/newsletter/subscriptionactivation", - "/onepagecheckout", - "/order", - "/passwordrecovery", - "/poll/vote", - "/privatemessages", - "/returnrequest", - "/newsletter/subscribe", - "/topic/authenticate", - "/wishlist", - "/product/askquestion", - "/product/emailafriend", - "/search", - "/config", - "/settings" + "/Boards/ForumWatch", + "/Boards/PostEdit", + "/Boards/PostDelete", + "/Boards/PostCreate", + "/Boards/TopicEdit", + "/Boards/TopicDelete", + "/Boards/TopicCreate", + "/Boards/TopicMove", + "/Boards/TopicWatch", + "/Cart", + "/Checkout", + "/Product/ClearCompareList", + "/CompareProducts", + "/Customer/Avatar", + "/Customer/Activation", + "/Customer/Addresses", + "/Customer/BackInStockSubscriptions", + "/Customer/ChangePassword", + "/Customer/CheckUsernameAvailability", + "/Customer/DownloadableProducts", + "/Customer/ForumSubscriptions", + "/Customer/DeleteForumSubscriptions", + "/Customer/Info", + "/Customer/Orders", + "/Customer/ReturnRequests", + "/Customer/RewardPoints", + "/PrivateMessages", + "/Newsletter/SubscriptionActivation", + "/Order", + "/PasswordRecovery", + "/Poll/Vote", + "/ReturnRequest", + "/Newsletter/Subscribe", + "/Topic/Authenticate", + "/Wishlist", + "/Product/AskQuestion", + "/Product/EmailAFriend", + "/Search", + "/Config", + "/Settings", + "/Login", + "/Register" }; @@ -857,12 +873,15 @@ public ActionResult RobotsTextFile() var sb = new StringBuilder(); sb.Append("User-agent: *"); sb.Append(newLine); + sb.AppendFormat("Sitemap: {0}", Url.RouteUrl("SitemapSEO", (object)null, _securitySettings.Value.ForceSslForAllPages ? "https" : "http")); + sb.AppendLine(); + + var disallows = disallowPaths.Concat(localizableDisallowPaths); - var disallows = disallowPaths.Concat(localizableDisallowPaths); if (_localizationSettings.SeoFriendlyUrlsForLanguagesEnabled) { // URLs are localizable. Append SEO code - foreach (var language in _languageService.GetAllLanguages(storeId: _services.StoreContext.CurrentStore.Id)) + foreach (var language in _languageService.Value.GetAllLanguages(storeId: _services.StoreContext.CurrentStore.Id)) { disallows = disallows.Concat(localizableDisallowPaths.Select(x => "/{0}{1}".FormatInvariant(language.UniqueSeoCode, x))); } @@ -873,6 +892,9 @@ public ActionResult RobotsTextFile() // append extra disallows disallows = disallows.Concat(seoSettings.ExtraRobotsDisallows.Select(x => x.Trim())); + // Append all lowercase variants (at least Google is case sensitive) + disallows = disallows.Concat(GetLowerCaseVariants(disallows)); + foreach (var disallow in disallows) { sb.AppendFormat("Disallow: {0}", disallow); @@ -884,6 +906,21 @@ public ActionResult RobotsTextFile() return null; } + private IEnumerable GetLowerCaseVariants(IEnumerable disallows) + { + var other = new List(); + foreach (var item in disallows) + { + var lower = item.ToLower(); + if (lower != item) + { + other.Add(lower); + } + } + + return other; + } + public ActionResult GenericUrl() { // seems that no entity was found @@ -906,7 +943,7 @@ public ActionResult AccountDropdown() if (_forumSettings.ShowAlertForPM && !customer.GetAttribute(SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, _services.StoreContext.CurrentStore.Id)) { - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, true, _services.StoreContext.CurrentStore.Id); + _genericAttributeService.Value.SaveAttribute(customer, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, true, _services.StoreContext.CurrentStore.Id); alertMessage = T("PrivateMessages.YouHaveUnreadPM", unreadMessageCount); } } @@ -927,6 +964,7 @@ public ActionResult AccountDropdown() return PartialView(model); } + [OverrideActionFilters, OverrideAuthorization] public ActionResult PdfReceiptHeader(PdfHeaderFooterVariables vars, int storeId = 0, bool isPartial = false) { var model = PreparePdfReceiptHeaderFooterModel(storeId); @@ -939,6 +977,7 @@ public ActionResult PdfReceiptHeader(PdfHeaderFooterVariables vars, int storeId return View(model); } + [OverrideActionFilters, OverrideAuthorization] public ActionResult PdfReceiptFooter(PdfHeaderFooterVariables vars, int storeId = 0, bool isPartial = false) { var model = PreparePdfReceiptHeaderFooterModel(storeId); @@ -956,7 +995,6 @@ protected PdfReceiptHeaderFooterModel PreparePdfReceiptHeaderFooterModel(int sto return _services.Cache.Get("PdfReceiptHeaderFooterModel-{0}".FormatInvariant(storeId), () => { var model = new PdfReceiptHeaderFooterModel { StoreId = storeId }; - var store = _services.StoreService.GetStoreById(model.StoreId) ?? _services.StoreContext.CurrentStore; var companyInfoSettings = _services.Settings.LoadSetting(store.Id); @@ -986,6 +1024,153 @@ protected PdfReceiptHeaderFooterModel PreparePdfReceiptHeaderFooterModel(int sto }, 1 /* 1 min. (just for the duration of pdf processing) */); } - #endregion - } + #endregion + + #region Entity Picker + + public ActionResult EntityPicker(EntityPickerModel model) + { + model.PageSize = 48; // _commonSettings.EntityPickerPageSize; + model.AllString = T("Admin.Common.All"); + + if (model.Entity.IsCaseInsensitiveEqual("product")) + { + var allCategories = _categoryService.Value.GetAllCategories(showHidden: true); + var mappedCategories = allCategories.ToDictionary(x => x.Id); + + model.AvailableCategories = allCategories + .Select(x => new SelectListItem { Text = x.GetCategoryNameWithPrefix(_categoryService.Value, mappedCategories), Value = x.Id.ToString() }) + .ToList(); + + model.AvailableManufacturers = _manufacturerService.Value.GetAllManufacturers(true) + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.AvailableStores = _services.StoreService.GetAllStores() + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); + } + + return PartialView(model); + } + + [HttpPost] + public ActionResult EntityPicker(EntityPickerModel model, FormCollection form) + { + model.PageSize = 48; // _commonSettings.EntityPickerPageSize; + model.PublishedString = T("Common.Published"); + model.UnpublishedString = T("Common.Unpublished"); + + try + { + var disableIf = model.DisableIf.SplitSafe(",").Select(x => x.ToLower().Trim()).ToList(); + var disableIds = model.DisableIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); + + using (var scope = new DbContextScope(_services.DbContext, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true)) + { + if (model.Entity.IsCaseInsensitiveEqual("product")) + { + #region Product + + model.SearchTerm = model.ProductName.TrimSafe(); + + var hasPermission = _services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog); + var storeLocation = _services.WebHelper.GetStoreLocation(false); + var disableIfNotSimpleProduct = disableIf.Contains("notsimpleproduct"); + var labelTextGrouped = T("Admin.Catalog.Products.ProductType.GroupedProduct.Label").Text; + var labelTextBundled = T("Admin.Catalog.Products.ProductType.BundledProduct.Label").Text; + var sku = T("Products.Sku").Text; + + var searchContext = new ProductSearchContext + { + CategoryIds = (model.CategoryId == 0 ? null : new List { model.CategoryId }), + ManufacturerId = model.ManufacturerId, + StoreId = model.StoreId, + Keywords = model.SearchTerm, + ProductType = model.ProductTypeId > 0 ? (ProductType?)model.ProductTypeId : null, + SearchSku = !_catalogSettings.SuppressSkuSearch, + ShowHidden = hasPermission + }; + + var query = _productService.Value.PrepareProductSearchQuery(searchContext, x => new { x.Id, x.Sku, x.Name, x.Published, x.ProductTypeId }); + + query = from x in query + group x by x.Id into grp + orderby grp.Key + select grp.FirstOrDefault(); + + var products = query + .OrderBy(x => x.Name) + .Skip(model.PageIndex * model.PageSize) + .Take(model.PageSize) + .ToList(); + + var productIds = products.Select(x => x.Id).ToArray(); + var pictures = _productService.Value.GetProductPicturesByProductIds(productIds, true); + + model.SearchResult = products + .Select(x => + { + var item = new EntityPickerModel.SearchResultModel + { + Id = x.Id, + ReturnValue = (model.ReturnField.IsCaseInsensitiveEqual("sku") ? x.Sku : x.Id.ToString()), + Title = x.Name, + Summary = x.Sku, + SummaryTitle = "{0}: {1}".FormatInvariant(sku, x.Sku.NaIfEmpty()), + Published = (hasPermission ? x.Published : (bool?)null) + }; + + if (disableIfNotSimpleProduct) + { + item.Disable = (x.ProductTypeId != (int)ProductType.SimpleProduct); + } + + if (!item.Disable && disableIds.Contains(x.Id)) + { + item.Disable = true; + } + + if (x.ProductTypeId == (int)ProductType.GroupedProduct) + { + item.LabelText = labelTextGrouped; + item.LabelClassName = "label-success"; + } + else if (x.ProductTypeId == (int)ProductType.BundledProduct) + { + item.LabelText = labelTextBundled; + item.LabelClassName = "label-info"; + } + + var productPicture = pictures.FirstOrDefault(y => y.Key == x.Id); + if (productPicture.Value != null) + { + var picture = productPicture.Value.FirstOrDefault(); + if (picture != null) + { + item.ImageUrl = _pictureService.Value.GetPictureUrl(picture.Picture, _mediaSettings.Value.ProductThumbPictureSizeOnProductDetailsPage, + !_catalogSettings.HideProductDefaultPictures, storeLocation); + } + } + + return item; + }) + .ToList(); + + #endregion + } + } + } + catch (Exception exception) + { + NotifyError(exception.ToAllMessages()); + } + + return PartialView("EntityPickerList", model); + } + + #endregion + } } diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index d9e4d85eb7..a72b4fc872 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -4,15 +4,16 @@ using System.Web; using System.Web.Mvc; using SmartStore.Core; +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.Orders; using SmartStore.Core.Domain.Tax; +using SmartStore.Core.Logging; using SmartStore.Services.Authentication; using SmartStore.Services.Authentication.External; using SmartStore.Services.Catalog; @@ -27,18 +28,18 @@ using SmartStore.Services.Orders; using SmartStore.Services.Seo; using SmartStore.Services.Tax; +using SmartStore.Utilities; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Plugins; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.UI.Captcha; using SmartStore.Web.Models.Common; using SmartStore.Web.Models.Customer; -using SmartStore.Core.Logging; -using SmartStore.Web.Framework.Plugins; -using SmartStore.Utilities; namespace SmartStore.Web.Controllers { - public partial class CustomerController : PublicControllerBase + public partial class CustomerController : PublicControllerBase { #region Fields @@ -75,6 +76,7 @@ public partial class CustomerController : PublicControllerBase private readonly IDownloadService _downloadService; private readonly IWebHelper _webHelper; private readonly ICustomerActivityService _customerActivityService; + private readonly IProductAttributeParser _productAttributeParser; private readonly MediaSettings _mediaSettings; private readonly IWorkflowMessageService _workflowMessageService; @@ -107,7 +109,9 @@ public CustomerController(IAuthenticationService authenticationService, IOpenAuthenticationService openAuthenticationService, IBackInStockSubscriptionService backInStockSubscriptionService, IDownloadService downloadService, IWebHelper webHelper, - ICustomerActivityService customerActivityService, MediaSettings mediaSettings, + ICustomerActivityService customerActivityService, + IProductAttributeParser productAttributeParser, + MediaSettings mediaSettings, IWorkflowMessageService workflowMessageService, LocalizationSettings localizationSettings, CaptchaSettings captchaSettings, ExternalAuthenticationSettings externalAuthenticationSettings, PluginMediator pluginMediator) @@ -145,6 +149,7 @@ public CustomerController(IAuthenticationService authenticationService, this._downloadService = downloadService; this._webHelper = webHelper; this._customerActivityService = customerActivityService; + this._productAttributeParser = productAttributeParser; this._mediaSettings = mediaSettings; this._workflowMessageService = workflowMessageService; @@ -241,6 +246,7 @@ protected void PrepareCustomerInfoModel(CustomerInfoModel model, Customer custom model.StateProvinceId = customer.GetAttribute(SystemCustomerAttributeNames.StateProvinceId); model.Phone = customer.GetAttribute(SystemCustomerAttributeNames.Phone); model.Fax = customer.GetAttribute(SystemCustomerAttributeNames.Fax); + model.CustomerNumber = customer.GetAttribute(SystemCustomerAttributeNames.CustomerNumber); //newsletter var newsletter = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(customer.Email, _storeContext.CurrentStore.Id); @@ -311,6 +317,19 @@ protected void PrepareCustomerInfoModel(CustomerInfoModel model, Customer custom model.AllowUsersToChangeUsernames = _customerSettings.AllowUsersToChangeUsernames; model.CheckUsernameAvailabilityEnabled = _customerSettings.CheckUsernameAvailabilityEnabled; model.SignatureEnabled = _forumSettings.ForumsEnabled && _forumSettings.SignaturesEnabled; + model.DisplayCustomerNumber = _customerSettings.CustomerNumberMethod != CustomerNumberMethod.Disabled + && _customerSettings.CustomerNumberVisibility != CustomerNumberVisibility.None; + + if (_customerSettings.CustomerNumberMethod != CustomerNumberMethod.Disabled + && (_customerSettings.CustomerNumberVisibility == CustomerNumberVisibility.Editable + || (_customerSettings.CustomerNumberVisibility == CustomerNumberVisibility.EditableIfEmpty && String.IsNullOrEmpty(model.CustomerNumber)))) + { + model.CustomerNumberEnabled = true; + } + else + { + model.CustomerNumberEnabled = false; + } //external authentication foreach (var ear in _openAuthenticationService.GetExternalIdentifiersFor(customer)) @@ -333,37 +352,46 @@ protected void PrepareCustomerInfoModel(CustomerInfoModel model, Customer custom } [NonAction] - protected CustomerOrderListModel PrepareCustomerOrderListModel(Customer customer) + protected CustomerOrderListModel PrepareCustomerOrderListModel(Customer customer, int pageIndex) { if (customer == null) throw new ArgumentNullException("customer"); - var model = new CustomerOrderListModel(); + var storeScope = (_orderSettings.DisplayOrdersOfAllStores ? 0 : _storeContext.CurrentStore.Id); + + var model = new CustomerOrderListModel(); model.NavigationModel = GetCustomerNavigationModel(customer); model.NavigationModel.SelectedTab = CustomerNavigationEnum.Orders; - var orders = _orderService.SearchOrders(_storeContext.CurrentStore.Id, customer.Id, - null, null, null, null, null, null, null, null, 0, int.MaxValue); - foreach (var order in orders) - { - var orderModel = new CustomerOrderListModel.OrderDetailsModel() - { - Id = order.Id, - OrderNumber = order.GetOrderNumber(), - CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc), - OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), - IsReturnRequestAllowed = _orderProcessingService.IsReturnRequestAllowed(order) - }; - var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); - orderModel.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); - model.Orders.Add(orderModel); - } + var orders = _orderService.SearchOrders(storeScope, customer.Id, null, null, null, null, null, null, null, null, pageIndex, _orderSettings.OrderListPageSize); + + var orderModels = orders + .Select(x => + { + var orderModel = new CustomerOrderListModel.OrderDetailsModel + { + Id = x.Id, + OrderNumber = x.GetOrderNumber(), + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), + OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), + IsReturnRequestAllowed = _orderProcessingService.IsReturnRequestAllowed(x) + }; + + var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(x.OrderTotal, x.CurrencyRate); + orderModel.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, x.CustomerCurrencyCode, false, _workContext.WorkingLanguage); + + return orderModel; + }) + .ToList(); + + model.Orders = new PagedList(orderModels, orders.PageIndex, orders.PageSize, orders.TotalCount); + + + var recurringPayments = _orderService.SearchRecurringPayments(_storeContext.CurrentStore.Id, customer.Id, 0, null); - var recurringPayments = _orderService.SearchRecurringPayments(_storeContext.CurrentStore.Id, - customer.Id, 0, null); foreach (var recurringPayment in recurringPayments) { - var recurringPaymentModel = new CustomerOrderListModel.RecurringOrderModel() + var recurringPaymentModel = new CustomerOrderListModel.RecurringOrderModel { Id = recurringPayment.Id, StartDate = _dateTimeHelper.ConvertToUserTime(recurringPayment.StartDateUtc, DateTimeKind.Utc).ToString(), @@ -393,7 +421,6 @@ public ActionResult Login(bool? checkoutAsGuest) model.CheckoutAsGuest = checkoutAsGuest.HasValue ? checkoutAsGuest.Value : false; model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnLoginPage; - // codehint: sm-add if (_customerSettings.PrefillLoginUsername.HasValue()) { if (model.UsernamesEnabled) @@ -440,10 +467,7 @@ public ActionResult Login(LoginModel model, string returnUrl, bool captchaValid) //activity log _customerActivityService.InsertActivity("PublicStore.Login", _localizationService.GetResource("ActivityLog.PublicStore.Login"), customer); - if (!String.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) - return Redirect(returnUrl); - else - return RedirectToRoute("HomePage"); + return RedirectToReferrer(returnUrl); } else { @@ -469,6 +493,7 @@ public ActionResult Register() foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) model.AvailableTimeZones.Add(new SelectListItem() { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == _dateTimeHelper.DefaultStoreTimeZone.Id) }); model.DisplayVatNumber = _taxSettings.EuVatEnabled; + model.VatRequired = _taxSettings.VatRequired; //form fields model.GenderEnabled = _customerSettings.GenderEnabled; model.DateOfBirthEnabled = _customerSettings.DateOfBirthEnabled; @@ -529,15 +554,15 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha if (_workContext.CurrentCustomer.IsRegistered()) { - //Already registered customer. + // Already registered customer. _authenticationService.SignOut(); - //Save a new record + // Save a new record _workContext.CurrentCustomer = _customerService.InsertGuestCustomer(); } var customer = _workContext.CurrentCustomer; - //validate CAPTCHA + // validate CAPTCHA if (_captchaSettings.Enabled && _captchaSettings.ShowOnRegistrationPage && !captchaValid) { ModelState.AddModelError("", _localizationService.GetResource("Common.WrongCaptcha")); @@ -556,12 +581,12 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha var registrationResult = _customerRegistrationService.RegisterCustomer(registrationRequest); if (registrationResult.Success) { - //properties + // properties if (_dateTimeSettings.AllowCustomersToSetTimeZone) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.TimeZoneId, model.TimeZoneId); } - //VAT number + // VAT number if (_taxSettings.EuVatEnabled) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.VatNumber, model.VatNumber); @@ -572,12 +597,12 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.VatNumberStatusId, (int)vatNumberStatus); - //send VAT number admin notification + // send VAT number admin notification if (!String.IsNullOrEmpty(model.VatNumber) && _taxSettings.EuVatEmailAdminWhenNewVatSubmitted) _workflowMessageService.SendNewVatSubmittedStoreOwnerNotification(customer, model.VatNumber, vatAddress, _localizationSettings.DefaultAdminLanguageId); } - //form fields + // form fields if (_customerSettings.GenderEnabled) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, model.FirstName); @@ -610,11 +635,13 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Phone, model.Phone); if (_customerSettings.FaxEnabled) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Fax, model.Fax); + if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.AutomaticallySet && String.IsNullOrEmpty(customer.GetAttribute(SystemCustomerAttributeNames.CustomerNumber))) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CustomerNumber, customer.Id); - //newsletter + // newsletter if (_customerSettings.NewsletterEnabled) { - //save newsletter value + // save newsletter value var newsletter = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(model.Email, _storeContext.CurrentStore.Id); if (newsletter != null) { @@ -633,7 +660,7 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha { if (model.Newsletter) { - _newsLetterSubscriptionService.InsertNewsLetterSubscription(new NewsLetterSubscription() + _newsLetterSubscriptionService.InsertNewsLetterSubscription(new NewsLetterSubscription { NewsLetterSubscriptionGuid = Guid.NewGuid(), Email = model.Email, @@ -693,11 +720,10 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha { case UserRegistrationType.EmailValidation: { - //email validation message + // email validation message _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.AccountActivationToken, Guid.NewGuid().ToString()); _workflowMessageService.SendCustomerEmailValidationMessage(customer, _workContext.WorkingLanguage.Id); - //result return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.EmailValidation }); } case UserRegistrationType.AdminApproval: @@ -706,8 +732,8 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha } case UserRegistrationType.Standard: { - //send customer welcome message - _workflowMessageService.SendCustomerWelcomeMessage(customer, _workContext.WorkingLanguage.Id); + // send customer welcome message + _workflowMessageService.SendCustomerWelcomeMessage(customer, _workContext.WorkingLanguage.Id); var redirectUrl = Url.RouteUrl("RegisterResult", new { resultId = (int)UserRegistrationType.Standard }); if (!String.IsNullOrEmpty(returnUrl)) @@ -732,6 +758,7 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) model.AvailableTimeZones.Add(new SelectListItem() { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == _dateTimeHelper.DefaultStoreTimeZone.Id) }); model.DisplayVatNumber = _taxSettings.EuVatEnabled; + model.VatRequired = _taxSettings.VatRequired; //form fields model.GenderEnabled = _customerSettings.GenderEnabled; model.DateOfBirthEnabled = _customerSettings.DateOfBirthEnabled; @@ -802,10 +829,8 @@ public ActionResult RegisterResult(int resultId) default: break; } - var model = new RegisterResultModel() - { - Result = resultText - }; + + var model = new RegisterResultModel { Result = resultText }; return View(model); } @@ -875,14 +900,14 @@ public ActionResult AccountActivation(string token, string email) { var customer = _customerService.GetCustomerByEmail(email); if (customer == null) - return RedirectToRoute("HomePage"); + return RedirectToHomePageWithError("Email"); var cToken = customer.GetAttribute(SystemCustomerAttributeNames.AccountActivationToken); if (String.IsNullOrEmpty(cToken)) - return RedirectToRoute("HomePage"); + return RedirectToHomePageWithError("Token"); if (!cToken.Equals(token, StringComparison.InvariantCultureIgnoreCase)) - return RedirectToRoute("HomePage"); + return RedirectToHomePageWithError("Token"); //activate user account customer.Active = true; @@ -890,8 +915,8 @@ public ActionResult AccountActivation(string token, string email) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.AccountActivationToken, ""); //send welcome message _workflowMessageService.SendCustomerWelcomeMessage(customer, _workContext.WorkingLanguage.Id); - - var model = new AccountActivationModel(); + + var model = new AccountActivationModel(); model.Result = _localizationService.GetResource("Account.AccountActivation.Activated"); return View(model); } @@ -953,8 +978,7 @@ public ActionResult Info(CustomerInfoModel model) if (ModelState.IsValid) { //username - if (_customerSettings.UsernamesEnabled && - this._customerSettings.AllowUsersToChangeUsernames) + if (_customerSettings.UsernamesEnabled && _customerSettings.AllowUsersToChangeUsernames) { if (!customer.Username.Equals(model.Username.Trim(), StringComparison.InvariantCultureIgnoreCase)) { @@ -1003,9 +1027,28 @@ public ActionResult Info(CustomerInfoModel model) //form fields if (_customerSettings.GenderEnabled) + { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); + } + + if (_customerSettings.CustomerNumberMethod != CustomerNumberMethod.Disabled) + { + var customerNumbers = _genericAttributeService.GetAttributes(SystemCustomerAttributeNames.CustomerNumber, "customer"); + var currentCustomerNumber = customer.GetAttribute(SystemCustomerAttributeNames.CustomerNumber); + + if (model.CustomerNumber != currentCustomerNumber && customerNumbers.Where(x => x.Value == model.CustomerNumber).Any()) + { + NotifyError("Common.CustomerNumberAlreadyExists"); + } + else + { + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CustomerNumber, model.CustomerNumber); + } + } + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, model.FirstName); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.LastName, model.LastName); + if (_customerSettings.DateOfBirthEnabled) { DateTime? dateOfBirth = null; @@ -1038,38 +1081,13 @@ public ActionResult Info(CustomerInfoModel model) //newsletter if (_customerSettings.NewsletterEnabled) { - //save newsletter value - var newsletter = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(customer.Email, _storeContext.CurrentStore.Id); - if (newsletter != null) - { - if (model.Newsletter) - { - newsletter.Active = true; - _newsLetterSubscriptionService.UpdateNewsLetterSubscription(newsletter); - } - else - { - _newsLetterSubscriptionService.DeleteNewsLetterSubscription(newsletter); - } - } - else - { - if (model.Newsletter) - { - _newsLetterSubscriptionService.InsertNewsLetterSubscription(new NewsLetterSubscription() - { - NewsLetterSubscriptionGuid = Guid.NewGuid(), - Email = customer.Email, - Active = true, - CreatedOnUtc = DateTime.UtcNow, - StoreId = _storeContext.CurrentStore.Id - }); - } - } + _newsLetterSubscriptionService.AddNewsLetterSubscriptionFor(model.Newsletter, customer.Email, _storeContext.CurrentStore.Id); } - if (_forumSettings.ForumsEnabled && _forumSettings.SignaturesEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Signature, model.Signature); + if (_forumSettings.ForumsEnabled && _forumSettings.SignaturesEnabled) + { + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Signature, model.Signature); + } return RedirectToAction("Info"); } @@ -1243,13 +1261,13 @@ public ActionResult AddressEdit(CustomerAddressEditModel model, int id) #region Orders [RequireHttpsByConfigAttribute(SslRequirement.Yes)] - public ActionResult Orders() + public ActionResult Orders(int? page) { if (!IsCurrentUserRegistered()) return new HttpUnauthorizedResult(); - var customer = _workContext.CurrentCustomer; - var model = PrepareCustomerOrderListModel(customer); + var model = PrepareCustomerOrderListModel(_workContext.CurrentCustomer, Math.Max((page ?? 0) - 1, 0)); + return View(model); } @@ -1262,9 +1280,13 @@ public ActionResult CancelRecurringPayment(FormCollection form) //get recurring payment identifier int recurringPaymentId = 0; - foreach (var formValue in form.AllKeys) - if (formValue.StartsWith("cancelRecurringPayment", StringComparison.InvariantCultureIgnoreCase)) - recurringPaymentId = Convert.ToInt32(formValue.Substring("cancelRecurringPayment".Length)); + foreach (var formValue in form.AllKeys) + { + if (formValue.StartsWith("cancelRecurringPayment", StringComparison.InvariantCultureIgnoreCase)) + { + recurringPaymentId = Convert.ToInt32(formValue.Substring("cancelRecurringPayment".Length)); + } + } var recurringPayment = _orderService.GetRecurringPaymentById(recurringPaymentId); if (recurringPayment == null) @@ -1277,16 +1299,14 @@ public ActionResult CancelRecurringPayment(FormCollection form) { var errors = _orderProcessingService.CancelRecurringPayment(recurringPayment); - var model = PrepareCustomerOrderListModel(customer); + var model = PrepareCustomerOrderListModel(customer, 0); model.CancelRecurringPaymentErrors = errors; return View(model); } - else - { - return RedirectToAction("Orders"); - } - } + + return RedirectToAction("Orders"); + } #endregion @@ -1312,8 +1332,9 @@ public ActionResult ReturnRequests() if (orderItem != null) { var product = orderItem.Product; + var attributeQueryData = new List>(); - var itemModel = new CustomerReturnRequestsModel.ReturnRequestModel() + var itemModel = new CustomerReturnRequestsModel.ReturnRequestModel { Id = returnRequest.Id, ReturnRequestStatus = returnRequest.ReturnRequestStatus.GetLocalizedEnum(_localizationService, _workContext), @@ -1324,8 +1345,22 @@ public ActionResult ReturnRequests() ReturnAction = returnRequest.RequestedAction, ReturnReason = returnRequest.ReasonForReturn, Comments = returnRequest.CustomerComments, - CreatedOn = _dateTimeHelper.ConvertToUserTime(returnRequest.CreatedOnUtc, DateTimeKind.Utc), + CreatedOn = _dateTimeHelper.ConvertToUserTime(returnRequest.CreatedOnUtc, DateTimeKind.Utc) }; + + if (orderItem.Product.ProductType != ProductType.BundledProduct) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, orderItem.AttributesXml, orderItem.ProductId); + } + else if (orderItem.Product.BundlePerItemPricing && orderItem.BundleData.HasValue()) + { + var bundleData = orderItem.GetBundleData(); + + bundleData.ForEach(x => _productAttributeParser.DeserializeQueryData(attributeQueryData, x.AttributesXml, x.ProductId, x.BundleItemId)); + } + + itemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(attributeQueryData, itemModel.ProductSeName); + model.Items.Add(itemModel); } } @@ -1348,11 +1383,12 @@ public ActionResult DownloadableProducts() var model = new CustomerDownloadableProductsModel(); model.NavigationModel = GetCustomerNavigationModel(customer); model.NavigationModel.SelectedTab = CustomerNavigationEnum.DownloadableProducts; - var items = _orderService.GetAllOrderItems(null, customer.Id, null, null, - null, null, null, true); + + var items = _orderService.GetAllOrderItems(null, customer.Id, null, null, null, null, null, true); + foreach (var item in items) { - var itemModel = new CustomerDownloadableProductsModel.DownloadableProductsModel() + var itemModel = new CustomerDownloadableProductsModel.DownloadableProductsModel { OrderItemGuid = item.OrderItemGuid, OrderId = item.OrderId, @@ -1362,6 +1398,9 @@ public ActionResult DownloadableProducts() ProductAttributes = item.AttributeDescription, ProductId = item.ProductId }; + + itemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(item.AttributesXml, item.ProductId, itemModel.ProductSeName); + model.Items.Add(itemModel); if (_downloadService.IsDownloadAllowed(item)) @@ -1381,11 +1420,11 @@ public ActionResult UserAgreement(Guid id /* orderItemId */) var orderItem = _orderService.GetOrderItemByGuid(id); if (orderItem == null) - return RedirectToRoute("HomePage"); + return RedirectToHomePageWithError("Guid"); var product = orderItem.Product; if (product == null || !product.HasUserAgreement) - return RedirectToRoute("HomePage"); + return RedirectToHomePageWithError("Product"); var model = new UserAgreementModel(); model.UserAgreementText = product.UserAgreementText; @@ -1529,30 +1568,34 @@ public ActionResult UploadAvatar(CustomerAvatarModel model, HttpPostedFileBase u try { var customerAvatar = _pictureService.GetPictureById(customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId)); - if ((uploadedFile != null) && (!String.IsNullOrEmpty(uploadedFile.FileName))) - { - int avatarMaxSize = _customerSettings.AvatarMaximumSizeBytes; - if (uploadedFile.ContentLength > avatarMaxSize) - throw new SmartException(string.Format(_localizationService.GetResource("Account.Avatar.MaximumUploadedFileSize"), Prettifier.BytesToString(avatarMaxSize))); - byte[] customerPictureBinary = uploadedFile.GetPictureBits(); - if (customerAvatar != null) - customerAvatar = _pictureService.UpdatePicture(customerAvatar.Id, customerPictureBinary, uploadedFile.ContentType, null, true); - else - customerAvatar = _pictureService.InsertPicture(customerPictureBinary, uploadedFile.ContentType, null, true); - } + if ((uploadedFile != null) && (!String.IsNullOrEmpty(uploadedFile.FileName))) + { + var avatarMaxSize = _customerSettings.AvatarMaximumSizeBytes; + + if (uploadedFile.ContentLength > avatarMaxSize) + throw new SmartException(T("Account.Avatar.MaximumUploadedFileSize", Prettifier.BytesToString(avatarMaxSize))); + + byte[] customerPictureBinary = uploadedFile.InputStream.ToByteArray(); - int customerAvatarId = 0; - if (customerAvatar != null) - customerAvatarId = customerAvatar.Id; + if (customerAvatar != null) + customerAvatar = _pictureService.UpdatePicture(customerAvatar.Id, customerPictureBinary, uploadedFile.ContentType, null, true); + else + customerAvatar = _pictureService.InsertPicture(customerPictureBinary, uploadedFile.ContentType, null, true, false); + } + else if (customerAvatar != null) + { + _pictureService.DeletePicture(customerAvatar); + customerAvatar = null; + } + + var customerAvatarId = (customerAvatar != null ? customerAvatar.Id : 0); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.AvatarPictureId, customerAvatarId); - model.AvatarUrl = _pictureService.GetPictureUrl( - customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId), - _mediaSettings.AvatarPictureSize, - false); - return View(model); + model.AvatarUrl = _pictureService.GetPictureUrl(customerAvatarId, _mediaSettings.AvatarPictureSize, false); + + return View(model); } catch (Exception exc) { @@ -1560,12 +1603,8 @@ public ActionResult UploadAvatar(CustomerAvatarModel model, HttpPostedFileBase u } } - //If we got this far, something failed, redisplay form - model.AvatarUrl = _pictureService.GetPictureUrl( - customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId), - _mediaSettings.AvatarPictureSize, - false); + model.AvatarUrl = _pictureService.GetPictureUrl(customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId), _mediaSettings.AvatarPictureSize, false); return View(model); } @@ -1588,6 +1627,7 @@ public ActionResult RemoveAvatar(CustomerAvatarModel model, HttpPostedFileBase u var customerAvatar = _pictureService.GetPictureById(customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId)); if (customerAvatar != null) _pictureService.DeletePicture(customerAvatar); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.AvatarPictureId, 0); return RedirectToAction("Avatar"); @@ -1637,14 +1677,14 @@ public ActionResult PasswordRecoveryConfirm(string token, string email) { var customer = _customerService.GetCustomerByEmail(email); if (customer == null ) - return RedirectToRoute("HomePage"); + return RedirectToHomePageWithError("Email"); var cPrt = customer.GetAttribute(SystemCustomerAttributeNames.PasswordRecoveryToken); if (String.IsNullOrEmpty(cPrt)) - return RedirectToRoute("HomePage"); + return RedirectToHomePageWithError("Token"); if (!cPrt.Equals(token, StringComparison.InvariantCultureIgnoreCase)) - return RedirectToRoute("HomePage"); + return RedirectToHomePageWithError("Token"); var model = new PasswordRecoveryConfirmModel(); return View(model); @@ -1656,14 +1696,14 @@ public ActionResult PasswordRecoveryConfirmPOST(string token, string email, Pass { var customer = _customerService.GetCustomerByEmail(email); if (customer == null) - return RedirectToRoute("HomePage"); + return RedirectToHomePageWithError("Email"); var cPrt = customer.GetAttribute(SystemCustomerAttributeNames.PasswordRecoveryToken); if (String.IsNullOrEmpty(cPrt)) - return RedirectToRoute("HomePage"); + return RedirectToHomePageWithError("Token"); if (!cPrt.Equals(token, StringComparison.InvariantCultureIgnoreCase)) - return RedirectToRoute("HomePage"); + return RedirectToHomePageWithError("Token"); if (ModelState.IsValid) { @@ -1738,12 +1778,12 @@ public ActionResult ForumSubscriptions(int? page) var forum = _forumService.GetForumById(forumId); if (forum != null) { - title = forum.Name; + title = forum.GetLocalized(x => x.Name); slug = forum.GetSeName(); } } - model.ForumSubscriptions.Add(new ForumSubscriptionModel() + model.ForumSubscriptions.Add(new ForumSubscriptionModel { Id = forumSubscription.Id, ForumTopicId = forumTopicId, diff --git a/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs b/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs index 99cc481d18..857b877aa3 100644 --- a/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs @@ -1,7 +1,11 @@ using System; +using System.Web; using System.Web.Mvc; using SmartStore.Core; +using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Media; +using SmartStore.Core.Html; using SmartStore.Services.Catalog; using SmartStore.Services.Media; using SmartStore.Services.Orders; @@ -9,7 +13,7 @@ namespace SmartStore.Web.Controllers { - public partial class DownloadController : PublicControllerBase + public partial class DownloadController : PublicControllerBase { private readonly IDownloadService _downloadService; private readonly IProductService _productService; @@ -18,8 +22,12 @@ public partial class DownloadController : PublicControllerBase private readonly CustomerSettings _customerSettings; - public DownloadController(IDownloadService downloadService, IProductService productService, - IOrderService orderService, IWorkContext workContext, CustomerSettings customerSettings) + public DownloadController( + IDownloadService downloadService, + IProductService productService, + IOrderService orderService, + IWorkContext workContext, + CustomerSettings customerSettings) { this._downloadService = downloadService; this._productService = productService; @@ -28,32 +36,40 @@ public DownloadController(IDownloadService downloadService, IProductService prod this._customerSettings = customerSettings; } + private ActionResult GetFileContentResultFor(Download download, Product product) + { + if (download.DownloadBinary == null) + { + return Content(T("Common.Download.NoDataAvailable")); + } + + var id = (product != null ? product.Id : download.Id); + var fileName = !String.IsNullOrWhiteSpace(download.Filename) ? download.Filename : id.ToString(); + var contentType = !String.IsNullOrWhiteSpace(download.ContentType) ? download.ContentType : "application/octet-stream"; + + return new FileContentResult(download.DownloadBinary, contentType) + { + FileDownloadName = fileName + download.Extension + }; + } + public ActionResult Sample(int id /* productId */) { var product = _productService.GetProductById(id); if (product == null) return HttpNotFound(); - if (!product.HasSampleDownload) - return Content("Product variant doesn't have a sample download."); + if (!product.HasSampleDownload) + return Content(T("Common.Download.HasNoSample")); var download = _downloadService.GetDownloadById(product.SampleDownloadId.GetValueOrDefault()); if (download == null) - return Content("Sample download is not available any more."); + return Content(T("Common.Download.SampleNotAvailable")); if (download.UseDownloadUrl) - { return new RedirectResult(download.DownloadUrl); - } - else - { - if (download.DownloadBinary == null) - return Content("Download data is not available any more."); - string fileName = !String.IsNullOrWhiteSpace(download.Filename) ? download.Filename : product.Id.ToString(); - string contentType = !String.IsNullOrWhiteSpace(download.ContentType) ? download.ContentType : "application/octet-stream"; - return new FileContentResult(download.DownloadBinary, contentType) { FileDownloadName = fileName + download.Extension }; - } + return GetFileContentResultFor(download, product); } public ActionResult GetDownload(Guid id /* orderItemId */, bool agree = false) @@ -68,7 +84,7 @@ public ActionResult GetDownload(Guid id /* orderItemId */, bool agree = false) var order = orderItem.Order; var product = orderItem.Product; if (!_downloadService.IsDownloadAllowed(orderItem)) - return Content("Downloads are not allowed"); + return Content(T("Common.Download.NotAllowed")); if (_customerSettings.DownloadableProductsValidateUser) { @@ -76,46 +92,35 @@ public ActionResult GetDownload(Guid id /* orderItemId */, bool agree = false) return new HttpUnauthorizedResult(); if (order.CustomerId != _workContext.CurrentCustomer.Id) - return Content("This is not your order"); + return Content(T("Account.CustomerOrders.NotYourOrder")); } var download = _downloadService.GetDownloadById(product.DownloadId); if (download == null) - return Content("Download is not available any more."); - - if (product.HasUserAgreement) - { - if (!agree) - return RedirectToAction("UserAgreement", "Customer", new { id = id }); - } + return Content(T("Common.Download.NoDataAvailable")); + if (product.HasUserAgreement && !agree) + return RedirectToAction("UserAgreement", "Customer", new { id = id }); if (!product.UnlimitedDownloads && orderItem.DownloadCount >= product.MaxNumberOfDownloads) - return Content(string.Format("You have reached maximum number of downloads {0}", product.MaxNumberOfDownloads)); + return Content(T("Common.Download.MaxNumberReached", product.MaxNumberOfDownloads)); - if (download.UseDownloadUrl) { - //increase download orderItem.DownloadCount++; _orderService.UpdateOrder(order); - //return result return new RedirectResult(download.DownloadUrl); } else { if (download.DownloadBinary == null) - return Content("Download data is not available any more."); + return Content(T("Common.Download.NoDataAvailable")); - //increase download orderItem.DownloadCount++; _orderService.UpdateOrder(order); - //return result - string fileName = !String.IsNullOrWhiteSpace(download.Filename) ? download.Filename : product.Id.ToString(); - string contentType = !String.IsNullOrWhiteSpace(download.ContentType) ? download.ContentType : "application/octet-stream"; - return new FileContentResult(download.DownloadBinary, contentType) { FileDownloadName = fileName + download.Extension }; + return GetFileContentResultFor(download, product); } } @@ -130,8 +135,9 @@ public ActionResult GetLicense(Guid id /* orderItemId */) var order = orderItem.Order; var product = orderItem.Product; + if (!_downloadService.IsLicenseDownloadAllowed(orderItem)) - return Content("Downloads are not allowed"); + return Content(T("Common.Download.NotAllowed")); if (_customerSettings.DownloadableProductsValidateUser) { @@ -139,52 +145,49 @@ public ActionResult GetLicense(Guid id /* orderItemId */) return new HttpUnauthorizedResult(); if (order.CustomerId != _workContext.CurrentCustomer.Id) - return Content("This is not your order"); + return Content(T("Account.CustomerOrders.NotYourOrder")); } var download = _downloadService.GetDownloadById(orderItem.LicenseDownloadId.HasValue ? orderItem.LicenseDownloadId.Value : 0); if (download == null) - return Content("Download is not available any more."); + return Content(T("Common.Download.NotAvailable")); if (download.UseDownloadUrl) - { - //return result return new RedirectResult(download.DownloadUrl); - } - else - { - if (download.DownloadBinary == null) - return Content("Download data is not available any more."); - - //return result - string fileName = !String.IsNullOrWhiteSpace(download.Filename) ? download.Filename : product.Id.ToString(); - string contentType = !String.IsNullOrWhiteSpace(download.ContentType) ? download.ContentType : "application/octet-stream"; - return new FileContentResult(download.DownloadBinary, contentType) { FileDownloadName = fileName + download.Extension }; - } - } + + return GetFileContentResultFor(download, product); + } public ActionResult GetFileUpload(Guid downloadId) { var download = _downloadService.GetDownloadByGuid(downloadId); if (download == null) - return Content("Download is not available any more."); + return Content(T("Common.Download.NotAvailable")); if (download.UseDownloadUrl) - { - //return result return new RedirectResult(download.DownloadUrl); - } - else - { - if (download.DownloadBinary == null) - return Content("Download data is not available any more."); - //return result - string fileName = !String.IsNullOrWhiteSpace(download.Filename) ? download.Filename : downloadId.ToString(); - string contentType = !String.IsNullOrWhiteSpace(download.ContentType) ? download.ContentType : "application/octet-stream"; - return new FileContentResult(download.DownloadBinary, contentType) { FileDownloadName = fileName + download.Extension }; - } - } + return GetFileContentResultFor(download, null); + } + + public ActionResult GetUserAgreement(int productId, bool? asPlainText) + { + var product = _productService.GetProductById(productId); + if (product == null) + return Content(T("Products.NotFound", productId)); + + if (!product.IsDownload || !product.HasUserAgreement || product.UserAgreementText.IsEmpty()) + return Content(T("DownloadableProducts.HasNoUserAgreement")); + + if (asPlainText ?? false) + { + var agreement = HtmlUtils.ConvertHtmlToPlainText(product.UserAgreementText); + agreement = HtmlUtils.StripTags(HttpUtility.HtmlDecode(agreement)); + + return Content(agreement); + } - } + return Content(product.UserAgreementText); + } + } } diff --git a/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs b/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs index f95f0ef4d2..3fc35cd8db 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs @@ -31,10 +31,10 @@ public ExternalAuthenticationController(IOpenAuthenticationService openAuthentic #region Methods - public RedirectResult RemoveParameterAssociation(string returnUrl) + public ActionResult RemoveParameterAssociation(string returnUrl) { ExternalAuthorizerHelper.RemoveParameters(); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [ChildActionOnly] diff --git a/src/Presentation/SmartStore.Web/Controllers/FilterController.cs b/src/Presentation/SmartStore.Web/Controllers/FilterController.cs index 01a4528ad5..e122492826 100644 --- a/src/Presentation/SmartStore.Web/Controllers/FilterController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/FilterController.cs @@ -18,6 +18,14 @@ public FilterController(IFilterService filterService, CatalogSettings catalogSet _catalogSettings = catalogSettings; } + private bool IsShowAllText(ICollection criteriaGroup) + { + if (criteriaGroup.Any(c => c.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutPrice))) + return false; + + return (criteriaGroup.Count >= _catalogSettings.MaxFilterItemsToDisplay || criteriaGroup.Any(c => !c.IsInactive)); + } + public ActionResult Products(string filter, int categoryID, string path, int? pagesize, int? orderby, string viewmode) { var context = _filterService.CreateFilterProductContext(filter, categoryID, path, pagesize, orderby, viewmode); @@ -39,7 +47,7 @@ public ActionResult Products(string active, string inactive, int categoryID, int // TODO: needed later for ajax based filtering... see example below //System.Threading.Thread.Sleep(3000); - var context = new FilterProductContext() + var context = new FilterProductContext { ParentCategoryID = categoryID, CategoryIds = new List { categoryID }, @@ -74,20 +82,13 @@ public ActionResult ProductsMultiSelect(string filter, int categoryID, string pa _filterService.ProductFilterableMultiSelect(context, filterMultiSelect); - return PartialView(new ProductFilterModel { + return PartialView(new ProductFilterModel + { Context = context, IsShowAllText = IsShowAllText(context.Criteria), MaxFilterItemsToDisplay = _catalogSettings.MaxFilterItemsToDisplay, ExpandAllFilterGroups = _catalogSettings.ExpandAllFilterCriteria }); } - - private bool IsShowAllText(ICollection criteriaGroup) - { - if (criteriaGroup.Any(c => c.Entity == FilterService.ShortcutPrice)) - return false; - - return (criteriaGroup.Count >= _catalogSettings.MaxFilterItemsToDisplay || criteriaGroup.Any(c => !c.IsInactive)); - } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/HomeController.cs b/src/Presentation/SmartStore.Web/Controllers/HomeController.cs index 579bb68d52..58e6eb4ace 100644 --- a/src/Presentation/SmartStore.Web/Controllers/HomeController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/HomeController.cs @@ -6,6 +6,7 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Cms; using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Infrastructure; using SmartStore.Core.Localization; @@ -41,6 +42,7 @@ public partial class HomeController : PublicControllerBase private readonly Lazy _sitemapGenerator; private readonly Lazy _captchaSettings; private readonly Lazy _commonSettings; + private readonly Lazy _customerSettings; #endregion @@ -56,7 +58,8 @@ public HomeController( Lazy emailAccountService, Lazy sitemapGenerator, Lazy captchaSettings, - Lazy commonSettings) + Lazy commonSettings, + Lazy customerSettings) { this._services = services; this._categoryService = categoryService; @@ -68,39 +71,34 @@ public HomeController( this._sitemapGenerator = sitemapGenerator; this._captchaSettings = captchaSettings; this._commonSettings = commonSettings; - - T = NullLocalizer.Instance; + this._customerSettings = customerSettings; } #endregion - public Localizer T { get; set; } - [RequireHttpsByConfigAttribute(SslRequirement.No)] public ActionResult Index() { - return View(); + return View(); } [ChildActionOnly] public ActionResult ContentSlider() { - var workContext = EngineContext.Current.Resolve(); - var storeContext = EngineContext.Current.Resolve(); - var pictureService = EngineContext.Current.Resolve(); - var model = EngineContext.Current.Resolve(); + var pictureService = EngineContext.Current.Resolve(); + var settings = _services.Settings.LoadSetting(); - model.BackgroundPictureUrl = pictureService.GetPictureUrl(model.BackgroundPictureId, 0, false); - - var slides = model.Slides + settings.BackgroundPictureUrl = pictureService.GetPictureUrl(settings.BackgroundPictureId, 0, false); + + var slides = settings.Slides .Where(s => - s.LanguageCulture == workContext.WorkingLanguage.LanguageCulture && - (!s.LimitedToStores || (s.SelectedStoreIds != null && s.SelectedStoreIds.Contains(storeContext.CurrentStore.Id))) + s.LanguageCulture == _services.WorkContext.WorkingLanguage.LanguageCulture && + (!s.LimitedToStores || (s.SelectedStoreIds != null && s.SelectedStoreIds.Contains(_services.StoreContext.CurrentStore.Id))) ) .OrderBy(s => s.DisplayOrder); - foreach (ContentSliderSlideSettings slide in slides) + foreach (var slide in slides) { slide.PictureUrl = pictureService.GetPictureUrl(slide.PictureId, 0, false); slide.Button1.Url = CheckButtonUrl(slide.Button1.Url); @@ -108,9 +106,9 @@ public ActionResult ContentSlider() slide.Button3.Url = CheckButtonUrl(slide.Button3.Url); } - model.Slides = slides.ToList(); + settings.Slides = slides.ToList(); - return PartialView(model); + return PartialView(settings); } public ActionResult StoreClosed() @@ -125,7 +123,9 @@ public ActionResult ContactUs() { Email = _services.WorkContext.CurrentCustomer.Email, FullName = _services.WorkContext.CurrentCustomer.GetFullName(), - DisplayCaptcha = _captchaSettings.Value.Enabled && _captchaSettings.Value.ShowOnContactUsPage + DisplayCaptcha = _captchaSettings.Value.Enabled && _captchaSettings.Value.ShowOnContactUsPage, + DisplayPrivacyAgreement = _customerSettings.Value.DisplayPrivacyAgreementOnContactUs + }; return View(model); @@ -147,9 +147,7 @@ public ActionResult ContactUsSend(ContactUsModel model, bool captchaValid) string fullName = model.FullName; string subject = T("ContactUs.EmailSubject", _services.StoreContext.CurrentStore.Name); - var emailAccount = _emailAccountService.Value.GetEmailAccountById(EngineContext.Current.Resolve().DefaultEmailAccountId); - if (emailAccount == null) - emailAccount = _emailAccountService.Value.GetAllEmailAccounts().FirstOrDefault(); + var emailAccount = _emailAccountService.Value.GetDefaultEmailAccount(); string from = null; string fromName = null; @@ -168,7 +166,7 @@ public ActionResult ContactUsSend(ContactUsModel model, bool captchaValid) from = email; fromName = fullName; } - _queuedEmailService.Value.InsertQueuedEmail(new QueuedEmail() + _queuedEmailService.Value.InsertQueuedEmail(new QueuedEmail { From = from, FromName = fromName, @@ -256,7 +254,6 @@ public ActionResult Sitemap() Id = product.Id, Name = product.GetLocalized(x => x.Name).EmptyNull(), ShortDescription = product.GetLocalized(x => x.ShortDescription), - FullDescription = product.GetLocalized(x => x.FullDescription), SeName = product.GetSeName(), }).ToList(); } diff --git a/src/Presentation/SmartStore.Web/Controllers/InstallController.cs b/src/Presentation/SmartStore.Web/Controllers/InstallController.cs index fa3a1499a0..7c5a919eb0 100644 --- a/src/Presentation/SmartStore.Web/Controllers/InstallController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/InstallController.cs @@ -1,32 +1,28 @@ using System; using System.Collections.Generic; +using System.Data.Entity; using System.Data.SqlClient; using System.Linq; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; -using System.Web; -using System.Web.SessionState; -using System.Web.Caching; using System.Web.Hosting; using System.Web.Mvc; -using System.ComponentModel.Composition; +using System.Web.SessionState; +using Autofac; using SmartStore.Core; -using SmartStore.Core.Caching; +using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Infrastructure; using SmartStore.Core.Plugins; +using SmartStore.Data; +using SmartStore.Data.Setup; using SmartStore.Services.Security; +using SmartStore.Utilities; using SmartStore.Web.Framework.Security; using SmartStore.Web.Infrastructure.Installation; using SmartStore.Web.Models.Install; -using SmartStore.Core.Async; -using System.Data.Entity; -using SmartStore.Data; -using SmartStore.Data.Setup; -using System.Configuration; -using SmartStore.Utilities; namespace SmartStore.Web.Controllers { @@ -145,7 +141,7 @@ protected string CreateDatabase(string connectionString, string collation) protected string CreateConnectionString( bool trustedConnection, string serverName, string databaseName, - string userName, string password, int timeout = 15 /* codehint: sm-edit (was 0) */) + string userName, string password, int timeout = 15) { var builder = new SqlConnectionStringBuilder(); builder.IntegratedSecurity = trustedConnection; @@ -159,7 +155,6 @@ protected string CreateConnectionString( builder.PersistSecurityInfo = false; //builder.MultipleActiveResultSets = true; - // codehint: sm-add builder.UserInstance = false; builder.Pooling = true; builder.MinPoolSize = 1; @@ -182,10 +177,10 @@ public ActionResult Index() if (DataSettings.DatabaseIsInstalled()) return RedirectToRoute("HomePage"); - //set page timeout to 5 minutes - this.Server.ScriptTimeout = 300; + // set page timeout to 10 minutes + this.Server.ScriptTimeout = 600; - var model = new InstallModel() + var model = new InstallModel { AdminEmail = _locService.GetResource("AdminEmailValue"), //AdminPassword = "admin", @@ -200,26 +195,34 @@ public ActionResult Index() Collation = "SQL_Latin1_General_CP1_CI_AS", }; - foreach (var lang in _locService.GetAvailableLanguages()) + var curLanguage = _locService.GetCurrentLanguage(); + var availableLanguages = _locService.GetAvailableLanguages(); + + foreach (var lang in availableLanguages) { - model.AvailableLanguages.Add(new SelectListItem() + model.AvailableLanguages.Add(new SelectListItem { Value = Url.Action("ChangeLanguage", "Install", new { language = lang.Code}), Text = lang.Name, - Selected = _locService.GetCurrentLanguage().Code == lang.Code, + Selected = curLanguage.Code == lang.Code, }); } foreach (var lang in _locService.GetAvailableAppLanguages()) { - model.AvailableAppLanguages.Add(new SelectListItem() + model.AvailableAppLanguages.Add(new SelectListItem { Value = lang.Culture, Text = lang.Name, - Selected = lang.Culture == Thread.CurrentThread.CurrentCulture.IetfLanguageTag // TODO (?) + Selected = lang.UniqueSeoCode.IsCaseInsensitiveEqual(curLanguage.Code) }); } + if (!model.AvailableAppLanguages.Any(x => x.Selected)) + { + model.AvailableAppLanguages.FirstOrDefault(x => x.Value.IsCaseInsensitiveEqual("en")).Selected = true; + } + model.AvailableMediaStorages.Add(new SelectListItem { Value = "db", Text = _locService.GetResource("MediaStorage.DB"), Selected = true }); model.AvailableMediaStorages.Add(new SelectListItem { Value = "fs", Text = _locService.GetResource("MediaStorage.FS") }); @@ -235,18 +238,20 @@ public JsonResult Progress() [HttpPost] public async Task Install(InstallModel model) { - var result = await InstallCore(model); - return Json(result); + var t = AsyncRunner.Run( + (c, ct, state) => InstallCore(c, (InstallModel)state), + model, + CancellationToken.None, + TaskCreationOptions.LongRunning, + TaskScheduler.Default); + + return Json(await t); } [NonAction] - protected virtual Task InstallCore(InstallModel model) + protected virtual InstallationResult InstallCore(ILifetimeScope scope, InstallModel model) { - var tcs = new TaskCompletionSource(); - var t = tcs.Task; - var scope = EngineContext.Current.ContainerManager.Scope(); - UpdateResult(x => { x.ProgressMessage = _locService.GetResource("Progress.CheckingRequirements"); @@ -255,12 +260,11 @@ protected virtual Task InstallCore(InstallModel model) if (DataSettings.DatabaseIsInstalled()) { - tcs.SetResult(UpdateResult(x => + return UpdateResult(x => { x.Success = true; x.RedirectUrl = Url.Action("Index", "Home"); - })); - return t; + }); } //set page timeout to 5 minutes @@ -329,7 +333,7 @@ protected virtual Task InstallCore(InstallModel model) //and the configured application pool identity on IIS 7.5) that is used if the application is not impersonating. //If the application is impersonating via , //the identity will be the anonymous user (typically IUSR_MACHINENAME) or the authenticated request user. - var webHelper = EngineContext.Current.ContainerManager.Resolve(scope: scope); + var webHelper = scope.Resolve(); //validate permissions var dirsToCheck = FilePermissionHelper.GetDirectoriesWrite(webHelper); foreach (string dir in dirsToCheck) @@ -351,13 +355,12 @@ protected virtual Task InstallCore(InstallModel model) if (GetInstallResult().HasErrors) { - tcs.SetResult(UpdateResult(x => + return UpdateResult(x => { x.Completed = true; x.Success = false; x.RedirectUrl = null; - })); - return t; + }); } else { @@ -399,14 +402,13 @@ protected virtual Task InstallCore(InstallModel model) var errorCreatingDatabase = CreateDatabase(connectionString, collation); if (errorCreatingDatabase.HasValue()) { - tcs.SetResult(UpdateResult(x => + return UpdateResult(x => { x.Errors.Add(errorCreatingDatabase); x.Completed = true; x.Success = false; x.RedirectUrl = null; - })); - return t; + }); } else { @@ -423,14 +425,13 @@ protected virtual Task InstallCore(InstallModel model) //check whether database exists if (!SqlServerDatabaseExists(connectionString)) { - tcs.SetResult(UpdateResult(x => + return UpdateResult(x => { x.Errors.Add(_locService.GetResource("DatabaseNotExists")); x.Completed = true; x.Success = false; x.RedirectUrl = null; - })); - return t; + }); } } } @@ -460,7 +461,7 @@ protected virtual Task InstallCore(InstallModel model) settings.Save(); // init data provider - var dataProviderInstance = EngineContext.Current.ContainerManager.Resolve(scope: scope); + var dataProviderInstance = scope.Resolve(); // Although obsolete we have no other chance than using this here. // Delegating this to DbConfiguration is not possible during installation. @@ -472,14 +473,13 @@ protected virtual Task InstallCore(InstallModel model) var lazyLanguage = _locService.GetAppLanguage(model.PrimaryLanguage); if (lazyLanguage == null) { - tcs.SetResult(UpdateResult(x => + return UpdateResult(x => { - x.Errors.Add(string.Format("The install language '{0}' is not registered", model.PrimaryLanguage)); + x.Errors.Add(_locService.GetResource("Install.LanguageNotRegistered").FormatInvariant(model.PrimaryLanguage)); x.Completed = true; x.Success = false; x.RedirectUrl = null; - })); - return t; + }); } // create the DataContext @@ -517,7 +517,7 @@ protected virtual Task InstallCore(InstallModel model) // install plugins PluginManager.MarkAllPluginsAsUninstalled(); - var pluginFinder = EngineContext.Current.ContainerManager.Resolve(scope: scope); + var pluginFinder = scope.Resolve(); var plugins = pluginFinder.GetPlugins(false) //.ToList() .OrderBy(x => x.PluginDescriptor.Group) @@ -540,7 +540,7 @@ protected virtual Task InstallCore(InstallModel model) var pluginsCount = plugins.Count; var idx = 0; - using (var dbScope = new DbContextScope(autoDetectChanges: false)) { + using (var dbScope = new DbContextScope(autoDetectChanges: false, hooksEnabled: false)) { foreach (var plugin in plugins) { try @@ -568,17 +568,16 @@ protected virtual Task InstallCore(InstallModel model) foreach (var providerType in permissionProviders) { dynamic provider = Activator.CreateInstance(providerType); - EngineContext.Current.ContainerManager.Resolve(scope: scope).InstallPermissions(provider); + scope.Resolve().InstallPermissions(provider); } // SUCCESS: Redirect to home page - tcs.SetResult(UpdateResult(x => + return UpdateResult(x => { x.Completed = true; x.Success = true; x.RedirectUrl = Url.Action("Index", "Home"); - })); - return t; + }); } catch (Exception exception) { @@ -607,14 +606,13 @@ protected virtual Task InstallCore(InstallModel model) msg += " (" + realException.Message + ")"; } - tcs.SetResult(UpdateResult(x => + return UpdateResult(x => { x.Errors.Add(string.Format(_locService.GetResource("SetupFailed"), msg)); x.Success = false; x.Completed = true; x.RedirectUrl = null; - })); - return t; + }); } finally { diff --git a/src/Presentation/SmartStore.Web/Controllers/KeepAliveController.cs b/src/Presentation/SmartStore.Web/Controllers/KeepAliveController.cs deleted file mode 100644 index 3093193eb9..0000000000 --- a/src/Presentation/SmartStore.Web/Controllers/KeepAliveController.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Web.Mvc; - -namespace SmartStore.Web.Controllers -{ - public partial class KeepAliveController : Controller - { - public ActionResult Index() - { - return Content("I am alive!"); - } - } -} diff --git a/src/Presentation/SmartStore.Web/Controllers/NewsController.cs b/src/Presentation/SmartStore.Web/Controllers/NewsController.cs index ccc30b1c10..b285065191 100644 --- a/src/Presentation/SmartStore.Web/Controllers/NewsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/NewsController.cs @@ -5,23 +5,24 @@ using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Caching; -using SmartStore.Core.Domain; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.News; +using SmartStore.Core.Logging; using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; -using SmartStore.Core.Logging; using SmartStore.Services.Media; using SmartStore.Services.Messages; using SmartStore.Services.News; using SmartStore.Services.Seo; using SmartStore.Services.Stores; -using SmartStore.Web.Framework; +using SmartStore.Utilities; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.UI.Captcha; using SmartStore.Web.Infrastructure.Cache; @@ -46,6 +47,7 @@ public partial class NewsController : PublicControllerBase private readonly ICacheManager _cacheManager; private readonly ICustomerActivityService _customerActivityService; private readonly IStoreMappingService _storeMappingService; + private readonly ILanguageService _languageService; private readonly MediaSettings _mediaSettings; private readonly NewsSettings _newsSettings; @@ -64,6 +66,7 @@ public NewsController(INewsService newsService, IWorkflowMessageService workflowMessageService, IWebHelper webHelper, ICacheManager cacheManager, ICustomerActivityService customerActivityService, IStoreMappingService storeMappingService, + ILanguageService languageService, MediaSettings mediaSettings, NewsSettings newsSettings, LocalizationSettings localizationSettings, CustomerSettings customerSettings, CaptchaSettings captchaSettings) @@ -80,6 +83,7 @@ public NewsController(INewsService newsService, this._cacheManager = cacheManager; this._customerActivityService = customerActivityService; this._storeMappingService = storeMappingService; + this._languageService = languageService; this._mediaSettings = mediaSettings; this._newsSettings = newsSettings; @@ -110,7 +114,7 @@ protected void PrepareNewsItemModel(NewsItemModel model, NewsItem newsItem, bool model.Short = newsItem.Short; model.Full = newsItem.Full; model.AllowComments = newsItem.AllowComments; - model.AvatarPictureSize = _mediaSettings.AvatarPictureSize; // codehint: sm-add + model.AvatarPictureSize = _mediaSettings.AvatarPictureSize; model.CreatedOn = _dateTimeHelper.ConvertToUserTime(newsItem.CreatedOnUtc, DateTimeKind.Utc); model.NumberOfComments = newsItem.ApprovedCommentCount; model.AddNewComment.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnNewsCommentPage; @@ -208,28 +212,43 @@ public ActionResult List(NewsPagingFilteringModel command) return View(model); } - [ActionName("rss")] + [ActionName("rss"), Compress] public ActionResult ListRss(int languageId) { - var feed = new SyndicationFeed( - string.Format("{0}: News", _storeContext.CurrentStore.Name), - "News", - new Uri(_webHelper.GetStoreLocation(false)), - "NewsRSS", - DateTime.UtcNow); + DateTime? maxAge = null; + var protocol = _webHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var selfLink = Url.Action("rss", "News", new { languageId = languageId }, protocol); + var newsLink = Url.RouteUrl("NewsArchive", null, protocol); - if (!_newsSettings.Enabled) - return new RssActionResult() { Feed = feed }; + var title = "{0} - News".FormatInvariant(_storeContext.CurrentStore.Name); - var items = new List(); - var newsItems = _newsService.GetAllNews(languageId, _storeContext.CurrentStore.Id, 0, int.MaxValue); - foreach (var n in newsItems) - { - string newsUrl = Url.RouteUrl("NewsItem", new { SeName = n.GetSeName(n.LanguageId, ensureTwoPublishedLanguages: false) }, "http"); - items.Add(new SyndicationItem(n.Title, n.Short, new Uri(newsUrl), String.Format("Blog:{0}", n.Id), n.CreatedOnUtc)); - } - feed.Items = items; - return new RssActionResult() { Feed = feed }; + if (_newsSettings.MaxAgeInDays > 0) + maxAge = DateTime.UtcNow.Subtract(new TimeSpan(_newsSettings.MaxAgeInDays, 0, 0, 0)); + + var language = _languageService.GetLanguageById(languageId); + var feed = new SmartSyndicationFeed(new Uri(newsLink), title); + + feed.AddNamespaces(true); + feed.Init(selfLink, language); + + if (!_newsSettings.Enabled) + return new RssActionResult { Feed = feed }; + + var items = new List(); + var newsItems = _newsService.GetAllNews(languageId, _storeContext.CurrentStore.Id, 0, int.MaxValue, false, maxAge); + + foreach (var news in newsItems) + { + var newsUrl = Url.RouteUrl("NewsItem", new { SeName = news.GetSeName(news.LanguageId, ensureTwoPublishedLanguages: false) }, "http"); + + var item = feed.CreateItem(news.Title, news.Short, newsUrl, news.CreatedOnUtc, news.Full); + + items.Add(item); + } + + feed.Items = items; + + return new RssActionResult { Feed = feed }; } public ActionResult NewsItem(int newsItemId) @@ -300,13 +319,11 @@ public ActionResult NewsCommentAdd(int newsItemId, NewsItemModel model, bool cap //activity log _customerActivityService.InsertActivity("PublicStore.AddNewsComment", _localizationService.GetResource("ActivityLog.PublicStore.AddNewsComment")); - //The text boxes should be cleared after a comment has been posted - //That' why we reload the page - TempData["sm.news.addcomment.result"] = _localizationService.GetResource("News.Comments.SuccessfullyAdded"); + NotifySuccess(T("News.Comments.SuccessfullyAdded")); + return RedirectToRoute("NewsItem", new { SeName = newsItem.GetSeName(newsItem.LanguageId, ensureTwoPublishedLanguages: false) }); } - //If we got this far, something failed, redisplay form PrepareNewsItemModel(model, newsItem, true); return View(model); @@ -319,7 +336,8 @@ public ActionResult RssHeaderLink() return Content(""); string link = string.Format("", - Url.Action("rss", null, new { languageId = _workContext.WorkingLanguage.Id }, _webHelper.IsCurrentConnectionSecured() ? "https" : "http"), _storeContext.CurrentStore.Name); + Url.Action("rss", null, new { languageId = _workContext.WorkingLanguage.Id }, _webHelper.IsCurrentConnectionSecured() ? "https" : "http"), + _storeContext.CurrentStore.Name); return Content(link); } diff --git a/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs b/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs index ad148aeb33..d41e2a40b0 100644 --- a/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs @@ -3,16 +3,14 @@ using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Messages; -using SmartStore.Services.Localization; using SmartStore.Services.Messages; -using SmartStore.Web.Models.Newsletter; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Models.Newsletter; namespace SmartStore.Web.Controllers { - public partial class NewsletterController : PublicControllerBase + public partial class NewsletterController : PublicControllerBase { - private readonly ILocalizationService _localizationService; private readonly IWorkContext _workContext; private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; private readonly IWorkflowMessageService _workflowMessageService; @@ -20,12 +18,13 @@ public partial class NewsletterController : PublicControllerBase private readonly CustomerSettings _customerSettings; - public NewsletterController(ILocalizationService localizationService, - IWorkContext workContext, INewsLetterSubscriptionService newsLetterSubscriptionService, - IWorkflowMessageService workflowMessageService, CustomerSettings customerSettings, + public NewsletterController( + IWorkContext workContext, + INewsLetterSubscriptionService newsLetterSubscriptionService, + IWorkflowMessageService workflowMessageService, + CustomerSettings customerSettings, IStoreContext storeContext) { - this._localizationService = localizationService; this._workContext = workContext; this._newsLetterSubscriptionService = newsLetterSubscriptionService; this._workflowMessageService = workflowMessageService; @@ -47,56 +46,59 @@ public ActionResult NewsletterBox() public ActionResult Subscribe(bool subscribe, string email) { string result; - bool success = false; + var success = false; if (!email.IsEmail()) - result = _localizationService.GetResource("Newsletter.Email.Wrong"); - else - { - //subscribe/unsubscribe - email = email.Trim(); - - var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(email, _storeContext.CurrentStore.Id); - if (subscription != null) - { - if (subscribe) - { - if (!subscription.Active) - { - _workflowMessageService.SendNewsLetterSubscriptionActivationMessage(subscription, _workContext.WorkingLanguage.Id); - } - result = _localizationService.GetResource("Newsletter.SubscribeEmailSent"); - } - else - { - if (subscription.Active) - { - _workflowMessageService.SendNewsLetterSubscriptionDeactivationMessage(subscription, _workContext.WorkingLanguage.Id); - } - result = _localizationService.GetResource("Newsletter.UnsubscribeEmailSent"); - } - } - else if (subscribe) - { - subscription = new NewsLetterSubscription() - { - NewsLetterSubscriptionGuid = Guid.NewGuid(), - Email = email, - Active = false, - CreatedOnUtc = DateTime.UtcNow, + { + result = T("Newsletter.Email.Wrong"); + } + else + { + //subscribe/unsubscribe + email = email.Trim(); + + var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(email, _storeContext.CurrentStore.Id); + if (subscription != null) + { + if (subscribe) + { + if (!subscription.Active) + { + _workflowMessageService.SendNewsLetterSubscriptionActivationMessage(subscription, _workContext.WorkingLanguage.Id); + } + result = T("Newsletter.SubscribeEmailSent"); + } + else + { + if (subscription.Active) + { + _workflowMessageService.SendNewsLetterSubscriptionDeactivationMessage(subscription, _workContext.WorkingLanguage.Id); + } + result = T("Newsletter.UnsubscribeEmailSent"); + } + } + else if (subscribe) + { + subscription = new NewsLetterSubscription + { + NewsLetterSubscriptionGuid = Guid.NewGuid(), + Email = email, + Active = false, + CreatedOnUtc = DateTime.UtcNow, StoreId = _storeContext.CurrentStore.Id - }; - _newsLetterSubscriptionService.InsertNewsLetterSubscription(subscription); - _workflowMessageService.SendNewsLetterSubscriptionActivationMessage(subscription, _workContext.WorkingLanguage.Id); - - result = _localizationService.GetResource("Newsletter.SubscribeEmailSent"); - } - else - { - result = _localizationService.GetResource("Newsletter.UnsubscribeEmailSent"); - } - success = true; - } + }; + + _newsLetterSubscriptionService.InsertNewsLetterSubscription(subscription); + _workflowMessageService.SendNewsLetterSubscriptionActivationMessage(subscription, _workContext.WorkingLanguage.Id); + + result = T("Newsletter.SubscribeEmailSent"); + } + else + { + result = T("Newsletter.UnsubscribeEmailSent"); + } + success = true; + } return Json(new { @@ -108,25 +110,26 @@ public ActionResult Subscribe(bool subscribe, string email) public ActionResult SubscriptionActivation(Guid token, bool active) { var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByGuid(token); - if (subscription == null) + if (subscription == null) + { return HttpNotFound(); + } var model = new SubscriptionActivationModel(); - if (active) - { - subscription.Active = active; - _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscription); - } - else - _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); - - if (active) - model.Result = _localizationService.GetResource("Newsletter.ResultActivated"); - else - model.Result = _localizationService.GetResource("Newsletter.ResultDeactivated"); - - return View(model); + if (active) + { + subscription.Active = active; + _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscription); + } + else + { + _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); + } + + model.Result = T(active ? "Newsletter.ResultActivated" : "Newsletter.ResultDeactivated"); + + return View(model); } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs index f55c87c7a5..448a1983d3 100644 --- a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs @@ -10,7 +10,6 @@ using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Html; -using SmartStore.Core.Localization; using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Directory; @@ -25,6 +24,7 @@ using SmartStore.Services.Shipping; using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Pdf; using SmartStore.Web.Framework.Plugins; using SmartStore.Web.Framework.Security; @@ -48,6 +48,7 @@ public partial class OrderController : PublicControllerBase private readonly ICountryService _countryService; private readonly IProductService _productService; private readonly IProductAttributeFormatter _productAttributeFormatter; + private readonly IProductAttributeParser _productAttributeParser; private readonly IStoreService _storeService; private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter; private readonly PluginMediator _pluginMediator; @@ -73,6 +74,7 @@ public OrderController( IStoreService storeService, IProductService productService, IProductAttributeFormatter productAttributeFormatter, + IProductAttributeParser productAttributeParser, Lazy pictureService, PluginMediator pluginMediator, ICommonServices services, @@ -90,16 +92,14 @@ public OrderController( this._countryService = countryService; this._productService = productService; this._productAttributeFormatter = productAttributeFormatter; + this._productAttributeParser = productAttributeParser; this._storeService = storeService; this._checkoutAttributeFormatter = checkoutAttributeFormatter; this._pluginMediator = pluginMediator; this._services = services; this._quantityUnitService = quantityUnitService; - T = NullLocalizer.Instance; } - public Localizer T { get; set; } - #endregion #region Utilities @@ -111,6 +111,7 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) throw new ArgumentNullException("order"); var store = _storeService.GetStoreById(order.StoreId) ?? _services.StoreContext.CurrentStore; + var language = _services.WorkContext.WorkingLanguage; var orderSettings = _services.Settings.LoadSetting(store.Id); var catalogSettings = _services.Settings.LoadSetting(store.Id); @@ -145,7 +146,7 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) var shipments = order.Shipments.Where(x => x.ShippedDateUtc.HasValue).OrderBy(x => x.CreatedOnUtc).ToList(); foreach (var shipment in shipments) { - var shipmentModel = new OrderDetailsModel.ShipmentBriefModel() + var shipmentModel = new OrderDetailsModel.ShipmentBriefModel { Id = shipment.Id, TrackingNumber = shipment.TrackingNumber, @@ -171,7 +172,7 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) model.CanRePostProcessPayment = _paymentService.CanRePostProcessPayment(order); //purchase order number (we have to find a better to inject this information because it's related to a certain plugin) - if (paymentMethod != null && paymentMethod.Metadata.SystemName.Equals("Payments.PurchaseOrder", StringComparison.InvariantCultureIgnoreCase)) + if (paymentMethod != null && paymentMethod.Metadata.SystemName.Equals("SmartStore.PurchaseOrderNumber", StringComparison.InvariantCultureIgnoreCase)) { model.DisplayPurchaseOrderNumber = true; model.PurchaseOrderNumber = order.PurchaseOrderNumber; @@ -184,56 +185,62 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) case TaxDisplayType.ExcludingTax: { //order subtotal - var orderSubtotalExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalExclTax, order.CurrencyRate); - model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, false, false); + var orderSubtotalExclTax = _currencyService.ConvertCurrency(order.OrderSubtotalExclTax, order.CurrencyRate); + model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalExclTax, true, order.CustomerCurrencyCode, language, false, false); //discount (applied to order subtotal) - var orderSubTotalDiscountExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountExclTax, order.CurrencyRate); - if (orderSubTotalDiscountExclTaxInCustomerCurrency > decimal.Zero) + var orderSubTotalDiscountExclTax = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountExclTax, order.CurrencyRate); + if (orderSubTotalDiscountExclTax > decimal.Zero) { - model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountExclTaxInCustomerCurrency, true, - order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, false, false); + model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountExclTax, true, order.CustomerCurrencyCode, language, false, false); } //order shipping - var orderShippingExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingExclTax, order.CurrencyRate); - model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, false, false); + var orderShippingExclTax = _currencyService.ConvertCurrency(order.OrderShippingExclTax, order.CurrencyRate); + model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingExclTax, true, order.CustomerCurrencyCode, language, false, false); + //payment method additional fee - var paymentMethodAdditionalFeeExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeExclTax, order.CurrencyRate); - if (paymentMethodAdditionalFeeExclTaxInCustomerCurrency != decimal.Zero) - model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, false, false); + var paymentMethodAdditionalFeeExclTax = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeExclTax, order.CurrencyRate); + if (paymentMethodAdditionalFeeExclTax != decimal.Zero) + { + model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTax, true, order.CustomerCurrencyCode, + language, false, false); + } } break; case TaxDisplayType.IncludingTax: { //order subtotal - var orderSubtotalInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalInclTax, order.CurrencyRate); - model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, true, false); + var orderSubtotalInclTax = _currencyService.ConvertCurrency(order.OrderSubtotalInclTax, order.CurrencyRate); + model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalInclTax, true, order.CustomerCurrencyCode, language, true, false); //discount (applied to order subtotal) - var orderSubTotalDiscountInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountInclTax, order.CurrencyRate); - if (orderSubTotalDiscountInclTaxInCustomerCurrency > decimal.Zero) + var orderSubTotalDiscountInclTax = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountInclTax, order.CurrencyRate); + if (orderSubTotalDiscountInclTax > decimal.Zero) { - model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountInclTaxInCustomerCurrency, true, - order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, true, false); + model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountInclTax, true, order.CustomerCurrencyCode, language, true, false); } //order shipping - var orderShippingInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingInclTax, order.CurrencyRate); - model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, true, false); + var orderShippingInclTax = _currencyService.ConvertCurrency(order.OrderShippingInclTax, order.CurrencyRate); + model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingInclTax, true, order.CustomerCurrencyCode, language, true, false); //payment method additional fee - var paymentMethodAdditionalFeeInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeInclTax, order.CurrencyRate); - if (paymentMethodAdditionalFeeInclTaxInCustomerCurrency != decimal.Zero) - model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, true, false); + var paymentMethodAdditionalFeeInclTax = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeInclTax, order.CurrencyRate); + if (paymentMethodAdditionalFeeInclTax != decimal.Zero) + { + model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeInclTax, true, order.CustomerCurrencyCode, + language, true, false); + } } break; } //tax - bool displayTax = true; - bool displayTaxRates = true; + var displayTax = true; + var displayTaxRates = true; + if (taxSettings.HideTaxInOrderSummary && order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) { displayTax = false; @@ -252,52 +259,62 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) displayTax = !displayTaxRates; var orderTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTax, order.CurrencyRate); - //TODO pass languageId to _priceFormatter.FormatPrice - model.Tax = _priceFormatter.FormatPrice(orderTaxInCustomerCurrency, true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage); + + model.Tax = _priceFormatter.FormatPrice(orderTaxInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); foreach (var tr in order.TaxRatesDictionary) { var rate = _priceFormatter.FormatTaxRate(tr.Key); - var labelKey = "ShoppingCart.Totals.TaxRateLine" + (_services.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax ? "Incl" : "Excl"); - model.TaxRates.Add(new OrderDetailsModel.TaxRate() + //var labelKey = "ShoppingCart.Totals.TaxRateLine" + (_services.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax ? "Incl" : "Excl"); + var labelKey = (_services.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax ? "ShoppingCart.Totals.TaxRateLineIncl" : "ShoppingCart.Totals.TaxRateLineExcl"); + + model.TaxRates.Add(new OrderDetailsModel.TaxRate { Rate = rate, Label = T(labelKey).Text.FormatCurrent(rate), - //TODO pass languageId to _priceFormatter.FormatPrice - Value = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(tr.Value, order.CurrencyRate), true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage), + Value = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(tr.Value, order.CurrencyRate), true, order.CustomerCurrencyCode, false, language), }); } } } + model.DisplayTaxRates = displayTaxRates; model.DisplayTax = displayTax; //discount (applied to order total) var orderDiscountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderDiscount, order.CurrencyRate); - if (orderDiscountInCustomerCurrency > decimal.Zero) - model.OrderTotalDiscount = _priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage); - + if (orderDiscountInCustomerCurrency > decimal.Zero) + { + model.OrderTotalDiscount = _priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); + } //gift cards foreach (var gcuh in order.GiftCardUsageHistory) { - model.GiftCards.Add(new OrderDetailsModel.GiftCard() - { - CouponCode = gcuh.GiftCard.GiftCardCouponCode, - Amount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage), - }); - } + var remainingAmountBase = gcuh.GiftCard.GetGiftCardRemainingAmount(); + var remainingAmount = _currencyService.ConvertCurrency(remainingAmountBase, order.CurrencyRate); + + var gcModel = new OrderDetailsModel.GiftCard + { + CouponCode = gcuh.GiftCard.GiftCardCouponCode, + Amount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, language), + Remaining = _priceFormatter.FormatPrice(remainingAmount, true, false) + }; + + model.GiftCards.Add(gcModel); + } //reward points if (order.RedeemedRewardPointsEntry != null) { model.RedeemedRewardPoints = -order.RedeemedRewardPointsEntry.Points; - model.RedeemedRewardPointsAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(order.RedeemedRewardPointsEntry.UsedAmount, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage); + model.RedeemedRewardPointsAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(order.RedeemedRewardPointsEntry.UsedAmount, order.CurrencyRate)), + true, order.CustomerCurrencyCode, false, language); } //total var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); - model.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage); + model.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); //checkout attributes model.CheckoutAttributeInfo = HtmlUtils.ConvertPlainTextToTable(HtmlUtils.ConvertHtmlToPlainText(order.CheckoutAttributeDescription)); @@ -308,7 +325,7 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) .OrderByDescending(on => on.CreatedOnUtc) .ToList()) { - model.OrderNotes.Add(new OrderDetailsModel.OrderNote() + model.OrderNotes.Add(new OrderDetailsModel.OrderNote { Note = orderNote.FormatOrderNoteText(), CreatedOn = _dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc) @@ -338,23 +355,26 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) var order = shipment.Order; if (order == null) - throw new Exception("order cannot be loaded"); + throw new SmartException(T("Order.NotFound", shipment.OrderId)); var store = _storeService.GetStoreById(order.StoreId) ?? _services.StoreContext.CurrentStore; var catalogSettings = _services.Settings.LoadSetting(store.Id); var shippingSettings = _services.Settings.LoadSetting(store.Id); - var model = new ShipmentDetailsModel(); - - model.Id = shipment.Id; + var model = new ShipmentDetailsModel + { + Id = shipment.Id, + TrackingNumber = shipment.TrackingNumber + }; + if (shipment.ShippedDateUtc.HasValue) model.ShippedDate = _dateTimeHelper.ConvertToUserTime(shipment.ShippedDateUtc.Value, DateTimeKind.Utc); + if (shipment.DeliveryDateUtc.HasValue) model.DeliveryDate = _dateTimeHelper.ConvertToUserTime(shipment.DeliveryDateUtc.Value, DateTimeKind.Utc); - //tracking number and shipment information - model.TrackingNumber = shipment.TrackingNumber; var srcm = _shippingService.LoadShippingRateComputationMethodBySystemName(order.ShippingRateComputationMethodSystemName); + if (srcm != null && srcm.IsShippingRateComputationMethodActive(shippingSettings)) { var shipmentTracker = srcm.Value.ShipmentTracker; @@ -364,25 +384,30 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) if (shippingSettings.DisplayShipmentEventsToCustomers) { var shipmentEvents = shipmentTracker.GetShipmentEvents(shipment.TrackingNumber); - if (shipmentEvents != null) - foreach (var shipmentEvent in shipmentEvents) - { - var shipmentStatusEventModel = new ShipmentDetailsModel.ShipmentStatusEventModel(); - var shipmentEventCountry = _countryService.GetCountryByTwoLetterIsoCode(shipmentEvent.CountryCode); - shipmentStatusEventModel.Country = shipmentEventCountry != null - ? shipmentEventCountry.GetLocalized(x => x.Name) - : shipmentEvent.CountryCode; - shipmentStatusEventModel.Date = shipmentEvent.Date; - shipmentStatusEventModel.EventName = shipmentEvent.EventName; - shipmentStatusEventModel.Location = shipmentEvent.Location; - model.ShipmentStatusEvents.Add(shipmentStatusEventModel); - } + if (shipmentEvents != null) + { + foreach (var shipmentEvent in shipmentEvents) + { + var shipmentEventCountry = _countryService.GetCountryByTwoLetterIsoCode(shipmentEvent.CountryCode); + + var shipmentStatusEventModel = new ShipmentDetailsModel.ShipmentStatusEventModel + { + Country = (shipmentEventCountry != null ? shipmentEventCountry.GetLocalized(x => x.Name) : shipmentEvent.CountryCode), + Date = shipmentEvent.Date, + EventName = shipmentEvent.EventName, + Location = shipmentEvent.Location + }; + + model.ShipmentStatusEvents.Add(shipmentStatusEventModel); + } + } } } } //products in this shipment model.ShowSku = catalogSettings.ShowProductSku; + foreach (var shipmentItem in shipment.ShipmentItems) { var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); @@ -390,7 +415,10 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) continue; orderItem.Product.MergeWithCombination(orderItem.AttributesXml); - var shipmentItemModel = new ShipmentDetailsModel.ShipmentItemModel() + + var attributeQueryData = new List>(); + + var shipmentItemModel = new ShipmentDetailsModel.ShipmentItemModel { Id = shipmentItem.Id, Sku = orderItem.Product.Sku, @@ -399,12 +427,25 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) ProductSeName = orderItem.Product.GetSeName(), AttributeInfo = orderItem.AttributeDescription, QuantityOrdered = orderItem.Quantity, - QuantityShipped = shipmentItem.Quantity, + QuantityShipped = shipmentItem.Quantity }; + + if (orderItem.Product.ProductType != ProductType.BundledProduct) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, orderItem.AttributesXml, orderItem.ProductId); + } + else if (orderItem.Product.BundlePerItemPricing && orderItem.BundleData.HasValue()) + { + var bundleData = orderItem.GetBundleData(); + + bundleData.ForEach(x => _productAttributeParser.DeserializeQueryData(attributeQueryData, x.AttributesXml, x.ProductId, x.BundleItemId)); + } + + shipmentItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(attributeQueryData, shipmentItemModel.ProductSeName); + model.Items.Add(shipmentItemModel); } - //order details model model.Order = PrepareOrderDetailsModel(order); return model; @@ -412,9 +453,11 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, OrderItem orderItem) { + var attributeQueryData = new List>(); + orderItem.Product.MergeWithCombination(orderItem.AttributesXml); - var model = new OrderDetailsModel.OrderItemModel() + var model = new OrderDetailsModel.OrderItemModel { Id = orderItem.Id, Sku = orderItem.Product.Sku, @@ -426,8 +469,13 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde AttributeInfo = orderItem.AttributeDescription }; + if (orderItem.Product.ProductType != ProductType.BundledProduct) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, orderItem.AttributesXml, orderItem.ProductId); + } + var quantityUnit = _quantityUnitService.GetQuantityUnitById(orderItem.Product.QuantityUnitId); - model.QuantityUnit = quantityUnit == null ? "" : quantityUnit.GetLocalized(x => x.Name); + model.QuantityUnit = (quantityUnit == null ? "" : quantityUnit.GetLocalized(x => x.Name)); if (orderItem.Product.ProductType == ProductType.BundledProduct && orderItem.BundleData.HasValue()) { @@ -438,7 +486,7 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde foreach (var bundleItem in bundleData) { - var bundleItemModel = new OrderDetailsModel.BundleItemModel() + var bundleItemModel = new OrderDetailsModel.BundleItemModel { Sku = bundleItem.Sku, ProductName = bundleItem.ProductName, @@ -449,6 +497,13 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde AttributeInfo = bundleItem.AttributesInfo }; + bundleItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(bundleItem.AttributesXml, bundleItem.ProductId, bundleItemModel.ProductSeName); + + if (orderItem.Product.BundlePerItemPricing) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, bundleItem.AttributesXml, bundleItem.ProductId, bundleItem.BundleItemId); + } + if (model.BundlePerItemShoppingCart) { decimal priceWithDiscount = _currencyService.ConvertCurrency(bundleItem.PriceWithDiscount, order.CurrencyRate); @@ -471,6 +526,7 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde model.SubTotal = _priceFormatter.FormatPrice(priceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, false, false); } break; + case TaxDisplayType.IncludingTax: { var unitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceInclTax, order.CurrencyRate); @@ -481,6 +537,9 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde } break; } + + model.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(attributeQueryData, model.ProductSeName); + return model; } @@ -516,8 +575,9 @@ public ActionResult Print(int id, bool pdf = false) return new HttpUnauthorizedResult(); var model = PrepareOrderDetailsModel(order); + var fileName = T("Order.PdfInvoiceFileName", order.Id); - return PrintCore(new List { model }, pdf, "order-{0}.pdf".FormatWith(order.Id)); + return PrintCore(new List { model }, pdf, fileName); } [AdminAuthorize] @@ -526,28 +586,36 @@ public ActionResult PrintMany(string ids = null, bool pdf = false) if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageOrders)) return new HttpUnauthorizedResult(); - IList orders; + const int maxOrders = 500; + IList orders = null; + int totalCount = 0; using (var scope = new DbContextScope(_services.DbContext, autoDetectChanges: false, forceNoTracking: true)) { if (ids != null) { - int[] intIds = ids.ToIntArray(); - orders = _orderService.GetOrdersByIds(intIds); + orders = _orderService.GetOrdersByIds(ids.ToIntArray()); + totalCount = orders.Count; } else { - orders = _orderService.SearchOrders(0, 0, null, null, null, null, null, null, null, null, 0, int.MaxValue); + var pagedOrders = _orderService.SearchOrders(0, 0, null, null, null, null, null, null, null, null, 0, 1); + totalCount = pagedOrders.TotalCount; + + if (totalCount > 0 && totalCount <= maxOrders) + { + orders = _orderService.SearchOrders(0, 0, null, null, null, null, null, null, null, null, 0, int.MaxValue); + } } } - if (orders.Count == 0) + if (totalCount == 0) { NotifyInfo(T("Admin.Common.ExportNoData")); return RedirectToReferrer(); } - if (orders.Count > 500) + if (totalCount > maxOrders) { NotifyWarning(T("Admin.Common.ExportToPdf.TooManyItems")); return RedirectToReferrer(); @@ -612,27 +680,30 @@ public ActionResult RePostPayment(int id) if (IsUnauthorizedOrder(order)) return new HttpUnauthorizedResult(); - if (!_paymentService.CanRePostProcessPayment(order)) - return RedirectToAction("Details", "Order", new { id = order.Id }); + try + { + if (_paymentService.CanRePostProcessPayment(order)) + { + var postProcessPaymentRequest = new PostProcessPaymentRequest + { + Order = order, + IsRePostProcessPayment = true + }; - var postProcessPaymentRequest = new PostProcessPaymentRequest() - { - Order = order, - IsRePostProcessPayment = true - }; - _paymentService.PostProcessPayment(postProcessPaymentRequest); + _paymentService.PostProcessPayment(postProcessPaymentRequest); - if (_services.WebHelper.IsRequestBeingRedirected || _services.WebHelper.IsPostBeingDone) - { - //redirection or POST has been done in PostProcessPayment - return Content("Redirected"); - } - else - { - //if no redirection has been done (to a third-party payment page) - //theoretically it's not possible - return RedirectToAction("Details", "Order", new { id = order.Id }); - } + if (postProcessPaymentRequest.RedirectUrl.HasValue()) + { + return Redirect(postProcessPaymentRequest.RedirectUrl); + } + } + } + catch (Exception exception) + { + NotifyError(exception); + } + + return RedirectToAction("Details", "Order", new { id = order.Id }); } [RequireHttpsByConfigAttribute(SslRequirement.Yes)] diff --git a/src/Presentation/SmartStore.Web/Controllers/PollController.cs b/src/Presentation/SmartStore.Web/Controllers/PollController.cs index 6b51058139..1919207834 100644 --- a/src/Presentation/SmartStore.Web/Controllers/PollController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/PollController.cs @@ -6,7 +6,6 @@ using SmartStore.Core.Caching; using SmartStore.Core.Domain.Polls; using SmartStore.Services.Customers; -using SmartStore.Services.Localization; using SmartStore.Services.Polls; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Infrastructure.Cache; @@ -14,11 +13,10 @@ namespace SmartStore.Web.Controllers { - public partial class PollController : PublicControllerBase + public partial class PollController : PublicControllerBase { #region Fields - private readonly ILocalizationService _localizationService; private readonly IWorkContext _workContext; private readonly IPollService _pollService; private readonly IWebHelper _webHelper; @@ -29,12 +27,13 @@ public partial class PollController : PublicControllerBase #region Constructors - public PollController(ILocalizationService localizationService, - IWorkContext workContext, IPollService pollService, - IWebHelper webHelper, ICacheManager cacheManager, + public PollController( + IWorkContext workContext, + IPollService pollService, + IWebHelper webHelper, + ICacheManager cacheManager, IStoreContext storeContext) { - this._localizationService = localizationService; this._workContext = workContext; this._pollService = pollService; this._webHelper = webHelper; @@ -49,18 +48,23 @@ public PollController(ILocalizationService localizationService, [NonAction] protected PollModel PreparePollModel(Poll poll, bool setAlreadyVotedProperty) { - var model = new PollModel() + var model = new PollModel { Id = poll.Id, AlreadyVoted = setAlreadyVotedProperty && _pollService.AlreadyVoted(poll.Id, _workContext.CurrentCustomer.Id), Name = poll.Name }; + var answers = poll.PollAnswers.OrderBy(x => x.DisplayOrder); - foreach (var answer in answers) - model.TotalVotes += answer.NumberOfVotes; + + foreach (var answer in answers) + { + model.TotalVotes += answer.NumberOfVotes; + } + foreach (var pa in answers) { - model.Answers.Add(new PollAnswerModel() + model.Answers.Add(new PollAnswerModel { Id = pa.Id, Name = pa.Name, @@ -88,10 +92,11 @@ public ActionResult PollBlock(string systemKeyword) var poll = _pollService.GetPollBySystemKeyword(systemKeyword, _workContext.WorkingLanguage.Id); if (poll == null) - return new PollModel() { Id = 0 }; //we do not cache nulls. that's why let's return an empty record (ID = 0) + return new PollModel { Id = 0 }; //we do not cache nulls. that's why let's return an empty record (ID = 0) return PreparePollModel(poll, false); }); + if (cachedModel == null || cachedModel.Id == 0) return Content(""); @@ -108,30 +113,29 @@ public ActionResult PollBlock(string systemKeyword) public ActionResult Vote(int pollAnswerId) { var pollAnswer = _pollService.GetPollAnswerById(pollAnswerId); - if (pollAnswer == null) - return Json(new - { - error = "No poll answer found with the specified id", - }); + + if (pollAnswer == null) + { + return Json(new { error = T("Polls.AnswerNotFound", pollAnswerId).Text }); + } var poll = pollAnswer.Poll; - if (!poll.Published) - return Json(new - { - error = "Poll is not available", - }); - if (_workContext.CurrentCustomer.IsGuest() && !poll.AllowGuestsToVote) - return Json(new - { - error = _localizationService.GetResource("Polls.OnlyRegisteredUsersVote"), - }); + if (!poll.Published) + { + return Json(new { error = T("Polls.NotAvailable").Text }); + } + + if (_workContext.CurrentCustomer.IsGuest() && !poll.AllowGuestsToVote) + { + return Json(new { error = T("Polls.OnlyRegisteredUsersVote").Text }); + } bool alreadyVoted = _pollService.AlreadyVoted(poll.Id, _workContext.CurrentCustomer.Id); if (!alreadyVoted) { //vote - pollAnswer.PollVotingRecords.Add(new PollVotingRecord() + pollAnswer.PollVotingRecords.Add(new PollVotingRecord { PollAnswerId = pollAnswer.Id, CustomerId = _workContext.CurrentCustomer.Id, @@ -140,8 +144,10 @@ public ActionResult Vote(int pollAnswerId) CreatedOnUtc = DateTime.UtcNow, UpdatedOnUtc = DateTime.UtcNow, }); + //update totals pollAnswer.NumberOfVotes = pollAnswer.PollVotingRecords.Count; + _pollService.UpdatePoll(poll); } @@ -161,12 +167,14 @@ public ActionResult HomePagePolls() .Select(x => PreparePollModel(x, false)) .ToList(); }); + //"AlreadyVoted" property of "PollModel" object depends on the current customer. Let's update it. //But first we need to clone the cached model (the updated one should not be cached) var model = new List(); + foreach (var p in cachedModel) { - var pollModel = (PollModel) p.Clone(); + var pollModel = (PollModel)p.Clone(); pollModel.AlreadyVoted = _pollService.AlreadyVoted(pollModel.Id, _workContext.CurrentCustomer.Id); model.Add(pollModel); } @@ -178,6 +186,5 @@ public ActionResult HomePagePolls() } #endregion - } } diff --git a/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs b/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs index b79fbee8b9..8171bf6d4f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/PrivateMessagesController.cs @@ -4,19 +4,19 @@ using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Logging; using SmartStore.Services.Customers; using SmartStore.Services.Forums; using SmartStore.Services.Helpers; -using SmartStore.Services.Localization; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Security; using SmartStore.Web.Models.Common; using SmartStore.Web.Models.PrivateMessages; -using SmartStore.Core.Logging; namespace SmartStore.Web.Controllers { - [RequireHttpsByConfigAttribute(SslRequirement.Yes)] + [RequireHttpsByConfigAttribute(SslRequirement.Yes)] public partial class PrivateMessagesController : PublicControllerBase { #region Fields @@ -24,7 +24,6 @@ public partial class PrivateMessagesController : PublicControllerBase private readonly IForumService _forumService; private readonly ICustomerService _customerService; private readonly ICustomerActivityService _customerActivityService; - private readonly ILocalizationService _localizationService; private readonly IWorkContext _workContext; private readonly IStoreContext _storeContext; private readonly IDateTimeHelper _dateTimeHelper; @@ -36,16 +35,17 @@ public partial class PrivateMessagesController : PublicControllerBase #region Constructors public PrivateMessagesController(IForumService forumService, - ICustomerService customerService, ICustomerActivityService customerActivityService, - ILocalizationService localizationService, - IWorkContext workContext, IStoreContext storeContext, + ICustomerService customerService, + ICustomerActivityService customerActivityService, + IWorkContext workContext, + IStoreContext storeContext, IDateTimeHelper dateTimeHelper, - ForumSettings forumSettings, CustomerSettings customerSettings) + ForumSettings forumSettings, + CustomerSettings customerSettings) { this._forumService = forumService; this._customerService = customerService; this._customerActivityService = customerActivityService; - this._localizationService = localizationService; this._workContext = workContext; this._storeContext = storeContext; this._dateTimeHelper = dateTimeHelper; @@ -102,7 +102,7 @@ public ActionResult Index(int? page, string tab) break; } - var model = new PrivateMessageIndexModel() + var model = new PrivateMessageIndexModel { InboxPage = inboxPage, SentItemsPage = sentItemsPage, @@ -130,7 +130,7 @@ public ActionResult Inbox(int page, string tab) foreach (var pm in list) { - inbox.Add(new PrivateMessageModel() + inbox.Add(new PrivateMessageModel { Id = pm.Id, FromCustomerId = pm.FromCustomer.Id, @@ -146,13 +146,11 @@ public ActionResult Inbox(int page, string tab) }); } - // codehint: sm-edit ViewData["PagerRouteValues"] = new PrivateMessageRouteValues { page = page, tab = tab }; var model = new PrivateMessageListModel(list) { Messages = inbox - // codehint: sm-delete }; return PartialView(model); @@ -176,7 +174,7 @@ public ActionResult SentItems(int page, string tab) foreach (var pm in list) { - sentItems.Add(new PrivateMessageModel() + sentItems.Add(new PrivateMessageModel { Id = pm.Id, FromCustomerId = pm.FromCustomer.Id, @@ -192,13 +190,11 @@ public ActionResult SentItems(int page, string tab) }); } - // codehint: sm-edit ViewData["PagerRouteValues"] = new PrivateMessageRouteValues { page = page, tab = tab }; var model = new PrivateMessageListModel(list) { Messages = sentItems - // codehint: sm-delete }; return PartialView(model); @@ -230,8 +226,8 @@ public ActionResult DeleteInboxPM(FormCollection formCollection) } } } - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } [HttpPost, FormValueRequired("mark-unread"), ActionName("InboxUpdate")] public ActionResult MarkUnread(FormCollection formCollection) @@ -259,7 +255,7 @@ public ActionResult MarkUnread(FormCollection formCollection) } } } - return RedirectToRoute("PrivateMessages"); + return RedirectToAction("Index"); } //updates sent items (deletes PrivateMessages) @@ -309,8 +305,8 @@ public ActionResult Send(int id /* toCustomerId */, int? replyToMessageId) if (customerTo == null || customerTo.IsGuest()) { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } var model = new SendPrivateMessageModel(); model.ToCustomerId = customerTo.Id; @@ -322,8 +318,8 @@ public ActionResult Send(int id /* toCustomerId */, int? replyToMessageId) var replyToPM = _forumService.GetPrivateMessageById(replyToMessageId.Value); if (replyToPM == null) { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } if (replyToPM.ToCustomerId == _workContext.CurrentCustomer.Id || replyToPM.FromCustomerId == _workContext.CurrentCustomer.Id) { @@ -332,10 +328,10 @@ public ActionResult Send(int id /* toCustomerId */, int? replyToMessageId) } else { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } } - return View(model); + return View(model); } [HttpPost] @@ -361,8 +357,8 @@ public ActionResult Send(SendPrivateMessageModel model) } else { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } } else { @@ -371,8 +367,8 @@ public ActionResult Send(SendPrivateMessageModel model) if (toCustomer == null || toCustomer.IsGuest()) { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } model.ToCustomerId = toCustomer.Id; model.CustomerToName = toCustomer.FormatUserName(); model.AllowViewingToProfile = _customerSettings.AllowViewingProfiles && !toCustomer.IsGuest(); @@ -411,9 +407,9 @@ public ActionResult Send(SendPrivateMessageModel model) _forumService.InsertPrivateMessage(privateMessage); //activity log - _customerActivityService.InsertActivity("PublicStore.SendPM", _localizationService.GetResource("ActivityLog.PublicStore.SendPM"), toCustomer.Email); + _customerActivityService.InsertActivity("PublicStore.SendPM", T("ActivityLog.PublicStore.SendPM", toCustomer.Email)); - return RedirectToRoute("PrivateMessages", new { tab = "sent" }); + return RedirectToAction("Index", new { tab = "sent" }); } catch (Exception ex) { @@ -441,8 +437,8 @@ public ActionResult View(int id /* privateMessageId */) { if (pm.ToCustomerId != _workContext.CurrentCustomer.Id && pm.FromCustomerId != _workContext.CurrentCustomer.Id) { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } if (!pm.IsRead && pm.ToCustomerId == _workContext.CurrentCustomer.Id) { @@ -452,10 +448,10 @@ public ActionResult View(int id /* privateMessageId */) } else { - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } - var model = new PrivateMessageModel() + var model = new PrivateMessageModel { Id = pm.Id, FromCustomerId = pm.FromCustomer.Id, @@ -500,8 +496,8 @@ public ActionResult Delete(int id /* privateMessageId */) _forumService.UpdatePrivateMessage(pm); } } - return RedirectToRoute("PrivateMessages"); - } + return RedirectToAction("Index"); + } #endregion } diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index 9b4a15b599..79497418a9 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Web.Mvc; using SmartStore.Core.Domain.Catalog; @@ -7,7 +8,6 @@ using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Localization; using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Common; @@ -22,6 +22,7 @@ using SmartStore.Services.Stores; using SmartStore.Services.Tax; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.UI.Captcha; using SmartStore.Web.Infrastructure.Cache; @@ -29,7 +30,7 @@ namespace SmartStore.Web.Controllers { - public partial class ProductController : PublicControllerBase + public partial class ProductController : PublicControllerBase { #region Fields @@ -59,6 +60,8 @@ public partial class ProductController : PublicControllerBase private readonly LocalizationSettings _localizationSettings; private readonly CaptchaSettings _captchaSettings; private readonly CatalogHelper _helper; + private readonly IDownloadService _downloadService; + private readonly ILocalizationService _localizationService; #endregion @@ -90,7 +93,9 @@ public ProductController( ShoppingCartSettings shoppingCartSettings, LocalizationSettings localizationSettings, CaptchaSettings captchaSettings, - CatalogHelper helper) + CatalogHelper helper, + IDownloadService downloadService, + ILocalizationService localizationService) { this._services = services; this._manufacturerService = manufacturerService; @@ -118,15 +123,12 @@ public ProductController( this._localizationSettings = localizationSettings; this._captchaSettings = captchaSettings; this._helper = helper; - - T = NullLocalizer.Instance; + this._downloadService = downloadService; + this._localizationService = localizationService; } #endregion - public Localizer T { get; set; } - - #region Products [RequireHttpsByConfigAttribute(SslRequirement.No)] @@ -165,11 +167,16 @@ public ActionResult ProductDetails(int productId, string attributes) } } - //prepare the model - var selectedAttributes = new FormCollection(); - selectedAttributes.ConvertAttributeQueryData(_productAttributeParser.DeserializeQueryData(attributes), product.Id); + var selectedAttributes = new NameValueCollection(); + + // get selected attributes from query string + selectedAttributes.GetSelectedAttributes( + Request.QueryString, + _productAttributeParser.DeserializeQueryData(attributes), + product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing ? 0 : product.Id); - var model = _helper.PrepareProductDetailsPageModel(product, selectedAttributes: selectedAttributes); + // prepare the view model + var model = _helper.PrepareProductDetailsPageModel(product, selectedAttributes: selectedAttributes, queryData: Request.QueryString); //save as recently viewed _recentlyViewedProductsService.AddProductToRecentlyViewedList(product.Id); @@ -231,7 +238,7 @@ public ActionResult AddProductToCart(int productId, FormCollection form) foreach (string formKey in form.AllKeys) { - if (formKey.Equals(string.Format("addtocart_{0}.EnteredQuantity", productId), StringComparison.InvariantCultureIgnoreCase)) + if (formKey.Equals(string.Format("addtocart_{0}.AddToCart.EnteredQuantity", productId), StringComparison.InvariantCultureIgnoreCase)) { int.TryParse(form[formKey], out quantity); break; @@ -300,9 +307,10 @@ public ActionResult AddProductToCart(int productId, FormCollection form) else { //Errors - foreach (string error in addToCartContext.Warnings) - ModelState.AddModelError("", error); - + foreach (string error in addToCartContext.Warnings) + { + this.NotifyError(error); + } //If we got this far, something failed, redisplay form var model = _helper.PrepareProductDetailsPageModel(parentProduct); @@ -313,7 +321,12 @@ public ActionResult AddProductToCart(int productId, FormCollection form) [ChildActionOnly] public ActionResult ProductManufacturers(int productId, bool preparePictureModel = false) { - string cacheKey = string.Format(ModelCacheEventConsumer.PRODUCT_MANUFACTURERS_MODEL_KEY, productId, _services.WorkContext.WorkingLanguage.Id, _services.StoreContext.CurrentStore.Id); + var cacheKey = string.Format(ModelCacheEventConsumer.PRODUCT_MANUFACTURERS_MODEL_KEY, + productId, + !_catalogSettings.HideManufacturerDefaultPictures, + _services.WorkContext.WorkingLanguage.Id, + _services.StoreContext.CurrentStore.Id); + var cacheModel = _services.Cache.Get(cacheKey, () => { var model = _manufacturerService.GetProductManufacturersByProductId(productId) @@ -322,7 +335,8 @@ public ActionResult ProductManufacturers(int productId, bool preparePictureModel var m = x.Manufacturer.ToModel(); if (preparePictureModel) { - m.PictureModel.ImageUrl = _pictureService.GetPictureUrl(x.Manufacturer.PictureId.GetValueOrDefault()); + m.PictureModel.ImageUrl = _pictureService.GetPictureUrl(x.Manufacturer.PictureId.GetValueOrDefault(), 0, !_catalogSettings.HideManufacturerDefaultPictures); + var picture = _pictureService.GetPictureUrl(x.Manufacturer.PictureId.GetValueOrDefault()); if (picture != null) { @@ -348,7 +362,7 @@ public ActionResult ReviewOverview(int id) { var product = _productService.GetProductById(id); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", id)); var model = new ProductReviewOverviewModel() { @@ -365,7 +379,7 @@ public ActionResult ProductSpecifications(int productId) { var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); var model = _helper.PrepareProductSpecificationModel(product); @@ -396,7 +410,7 @@ public ActionResult ProductTierPrices(int productId) var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); if (!product.HasTierPrices) return Content(""); //no tier prices @@ -439,7 +453,7 @@ public ActionResult RelatedProducts(int productId, int? productThumbPictureSize) if (products.Count == 0) return Content(""); - var model = _helper.PrepareProductOverviewModels(products, true, true, productThumbPictureSize).ToList(); + var model = _helper.PrepareProductOverviewModels(products, true, true, productThumbPictureSize, false, false, false, false, true).ToList(); return PartialView(model); } @@ -450,22 +464,23 @@ public ActionResult ProductsAlsoPurchased(int productId, int? productThumbPictur if (!_catalogSettings.ProductsAlsoPurchasedEnabled) return Content(""); - //load and cache report - var productIds = _services.Cache.Get(string.Format(ModelCacheEventConsumer.PRODUCTS_ALSO_PURCHASED_IDS_KEY, productId, _services.StoreContext.CurrentStore.Id), () => - _orderReportService - .GetAlsoPurchasedProductsIds(_services.StoreContext.CurrentStore.Id, productId, _catalogSettings.ProductsAlsoPurchasedNumber) - ); + // load and cache report + var productIds = _services.Cache.Get(string.Format(ModelCacheEventConsumer.PRODUCTS_ALSO_PURCHASED_IDS_KEY, productId, _services.StoreContext.CurrentStore.Id), () => + { + return _orderReportService.GetAlsoPurchasedProductsIds(_services.StoreContext.CurrentStore.Id, productId, _catalogSettings.ProductsAlsoPurchasedNumber); + }); - //load products + // load products var products = _productService.GetProductsByIds(productIds); - //ACL and store mapping + + // ACL and store mapping products = products.Where(p => _aclService.Authorize(p) && _storeMappingService.Authorize(p)).ToList(); if (products.Count == 0) return Content(""); - //prepare model - var model = _helper.PrepareProductOverviewModels(products, true, true, productThumbPictureSize).ToList(); + // prepare model + var model = _helper.PrepareProductOverviewModels(products, true, true, productThumbPictureSize, false, false, false, false, true).ToList(); return PartialView(model); } @@ -514,7 +529,7 @@ public ActionResult BackInStockSubscribePopup(int id /* productId */) { var product = _productService.GetProductById(id); if (product == null || product.Deleted) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", id)); var model = new BackInStockSubscribeModel(); model.ProductId = product.Id; @@ -543,7 +558,7 @@ public ActionResult BackInStockSubscribePopupPOST(int id /* productId */) { var product = _productService.GetProductById(id); if (product == null || product.Deleted) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", id)); if (!_services.WorkContext.CurrentCustomer.IsRegistered()) return Content(T("BackInStockSubscriptions.OnlyRegistered")); @@ -603,6 +618,14 @@ public ActionResult UpdateProductDetails(int productId, string itemType, int bun IList bundleItems = null; ProductBundleItemData bundleItem = (bItem == null ? null : new ProductBundleItemData(bItem)); + var warnings = new List(); + var attributes = _productAttributeService.GetProductVariantAttributesByProductId(productId); + + string attributeXml = form.CreateSelectedAttributesXml(productId, attributes, _productAttributeParser, + _localizationService, _downloadService, _catalogSettings, this.Request, warnings, true); + + var areAllAttributesForCombinationSelected = _shoppingCartService.AreAllAttributesForCombinationSelected(attributeXml, product); + // quantity required for tier prices string quantityKey = form.AllKeys.FirstOrDefault(k => k.EndsWith("EnteredQuantity")); if (quantityKey.HasValue()) @@ -613,9 +636,10 @@ public ActionResult UpdateProductDetails(int productId, string itemType, int bun bundleItems = _productService.GetBundleItems(product.Id); if (form.Count > 0) { + // may add elements to form if they are preselected by bundle item filter foreach (var itemData in bundleItems) { - var tempModel = _helper.PrepareProductDetailsPageModel(itemData.Item.Product, false, itemData, null, form); + var unused = _helper.PrepareProductDetailsPageModel(itemData.Item.Product, false, itemData, null, form); } } } @@ -647,76 +671,89 @@ public ActionResult UpdateProductDetails(int productId, string itemType, int bun } else { - var allCombinationImageIds = new List(); - - _productAttributeService - .GetAllProductVariantAttributeCombinations(product.Id) - .GetAllCombinationImageIds(allCombinationImageIds); + var allCombinationPictureIds = _productAttributeService.GetAllProductVariantAttributeCombinationPictureIds(product.Id); - _helper.PrepareProductDetailsPictureModel(pictureModel, pictures, product.GetLocalized(x => x.Name), allCombinationImageIds, - false, bundleItem, m.CombinationSelected); + _helper.PrepareProductDetailsPictureModel( + pictureModel, + pictures, + product.GetLocalized(x => x.Name), + allCombinationPictureIds, + false, + bundleItem, + m.SelectedCombination); galleryStartIndex = pictureModel.GalleryStartIndex; galleryHtml = this.RenderPartialViewToString("_PictureGallery", pictureModel); } } - + #region data object - object data = new - { - Delivery = new - { - Id = 0, - Name = m.DeliveryTimeName, - Color = m.DeliveryTimeHexValue, - DisplayAccordingToStock = m.DisplayDeliveryTimeAccordingToStock - }, - Measure = new - { - Weight = new { Value = m.WeightValue, Text = m.Weight }, - Height = new { Value = product.Height, Text = m.Height }, - Width = new { Value = product.Width, Text = m.Width }, - Length = new { Value = product.Length, Text = m.Length } - }, - Number = new - { - Sku = new { Value = m.Sku, Show = m.ShowSku }, - Gtin = new { Value = m.Gtin, Show = m.ShowGtin }, - Mpn = new { Value = m.ManufacturerPartNumber, Show = m.ShowManufacturerPartNumber } - }, - Price = new - { - Base = new - { - Enabled = m.IsBasePriceEnabled, - Info = m.BasePriceInfo - }, - Old = new - { - Value = decimal.Zero, - Text = m.ProductPrice.OldPrice - }, - WithoutDiscount = new - { - Value = m.ProductPrice.PriceValue, - Text = m.ProductPrice.Price - }, - WithDiscount = new - { - Value = m.ProductPrice.PriceWithDiscountValue, - Text = m.ProductPrice.PriceWithDiscount + + object data = new + { + Delivery = new + { + Id = 0, + Name = m.DeliveryTimeName, + Color = m.DeliveryTimeHexValue, + DisplayAccordingToStock = m.DisplayDeliveryTimeAccordingToStock + }, + Measure = new + { + Weight = new { Value = m.WeightValue, Text = m.Weight }, + Height = new { Value = product.Height, Text = m.Height }, + Width = new { Value = product.Width, Text = m.Width }, + Length = new { Value = product.Length, Text = m.Length } + }, + Number = new + { + Sku = new { Value = m.Sku, Show = m.ShowSku }, + Gtin = new { Value = m.Gtin, Show = m.ShowGtin }, + Mpn = new { Value = m.ManufacturerPartNumber, Show = m.ShowManufacturerPartNumber } + }, + Price = new + { + Base = new + { + Enabled = m.IsBasePriceEnabled, + Info = m.BasePriceInfo + }, + Old = new + { + Value = decimal.Zero, + Text = m.ProductPrice.OldPrice + }, + WithoutDiscount = new + { + Value = m.ProductPrice.PriceValue, + Text = m.ProductPrice.Price + }, + WithDiscount = new + { + Value = m.ProductPrice.PriceWithDiscountValue, + Text = m.ProductPrice.PriceWithDiscount + } + }, + Stock = new + { + Quantity = new + { + Value = product.StockQuantity, + Show = areAllAttributesForCombinationSelected ? product.DisplayStockQuantity : false + }, + Availability = new + { + Text = m.StockAvailability, + Show = areAllAttributesForCombinationSelected ? product.DisplayStockAvailability : false, + Available = m.IsAvailable } - }, - Stock = new - { - Quantity = new { Value = product.StockQuantity, Show = product.DisplayStockQuantity }, - Availability = new { Text = m.StockAvailability, Show = product.DisplayStockAvailability, Available = m.IsAvailable } - }, + }, + + DynamicThumblUrl = dynamicThumbUrl, + GalleryStartIndex = galleryStartIndex, + GalleryHtml = galleryHtml + }; - DynamicThumblUrl = dynamicThumbUrl, - GalleryStartIndex = galleryStartIndex, - GalleryHtml = galleryHtml - }; #endregion return new JsonResult { Data = data }; @@ -732,7 +769,7 @@ public ActionResult ProductTags(int productId) { var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); var cacheKey = string.Format(ModelCacheEventConsumer.PRODUCTTAG_BY_PRODUCT_MODEL_KEY, product.Id, _services.WorkContext.WorkingLanguage.Id, _services.StoreContext.CurrentStore.Id); var cacheModel = _services.Cache.Get(cacheKey, () => @@ -863,7 +900,7 @@ public ActionResult SetReviewHelpfulness(int productReviewId, bool washelpful) { var productReview = _customerContentService.GetCustomerContentById(productReviewId) as ProductReview; if (productReview == null) - throw new ArgumentException("No product review found with the specified id"); + throw new ArgumentException(T("Reviews.NotFound", productReviewId)); if (_services.WorkContext.CurrentCustomer.IsGuest() && !_catalogSettings.AllowAnonymousUsersToReviewProduct) { @@ -1002,7 +1039,7 @@ public ActionResult AskQuestionSend(ProductAskQuestionModel model, bool captchaV } else { - ModelState.AddModelError("", "Fehler beim Versenden der Email. Bitte versuchen Sie es später erneut."); + ModelState.AddModelError("", T("Common.Error.SendMail")); } } diff --git a/src/Presentation/SmartStore.Web/Controllers/ProfileController.cs b/src/Presentation/SmartStore.Web/Controllers/ProfileController.cs index e9ab34b7b0..6f79a4ebcc 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProfileController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProfileController.cs @@ -240,12 +240,10 @@ public ActionResult Posts(int customerProfileId, int page) }); } - // codehint: sm-edit ViewData["PagerRouteValues"] = new RouteValues { page = page, id = customerProfileId }; var model = new ProfilePostsModel(list) { - // codehint: sm-delete Posts = latestPosts, }; diff --git a/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs b/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs index 95d0aa59af..d7b5e454d6 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Core; +using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Tax; @@ -11,9 +13,9 @@ using SmartStore.Services.Messages; using SmartStore.Services.Orders; using SmartStore.Services.Seo; +using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; using SmartStore.Web.Models.Order; -using SmartStore.Web.Framework.Controllers; namespace SmartStore.Web.Controllers { @@ -30,6 +32,7 @@ public partial class ReturnRequestController : PublicControllerBase private readonly ILocalizationService _localizationService; private readonly ICustomerService _customerService; private readonly IWorkflowMessageService _workflowMessageService; + private readonly IProductAttributeParser _productAttributeParser; private readonly LocalizationSettings _localizationSettings; private readonly OrderSettings _orderSettings; @@ -46,6 +49,7 @@ public ReturnRequestController( ILocalizationService localizationService, ICustomerService customerService, IWorkflowMessageService workflowMessageService, + IProductAttributeParser productAttributeParser, LocalizationSettings localizationSettings, OrderSettings orderSettings) { @@ -58,6 +62,7 @@ public ReturnRequestController( this._localizationService = localizationService; this._customerService = customerService; this._workflowMessageService = workflowMessageService; + this._productAttributeParser = productAttributeParser; this._localizationSettings = localizationSettings; this._orderSettings = orderSettings; @@ -95,18 +100,33 @@ protected SubmitReturnRequestModel PrepareReturnRequestModel(SubmitReturnRequest //products var orderItems = _orderService.GetAllOrderItems(order.Id, null, null, null, null, null, null); + foreach (var orderItem in orderItems) { - var orderItemModel = new SubmitReturnRequestModel.OrderItemModel() + var attributeQueryData = new List>(); + + var orderItemModel = new SubmitReturnRequestModel.OrderItemModel { Id = orderItem.Id, ProductId = orderItem.Product.Id, ProductName = orderItem.Product.GetLocalized(x => x.Name), - ProductSeName = orderItem.Product.GetSeName(), + ProductSeName = orderItem.Product.GetSeName(), AttributeInfo = orderItem.AttributeDescription, Quantity = orderItem.Quantity }; - model.Items.Add(orderItemModel); + + if (orderItem.Product.ProductType != ProductType.BundledProduct) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, orderItem.AttributesXml, orderItem.ProductId); + } + else if (orderItem.Product.BundlePerItemPricing && orderItem.BundleData.HasValue()) + { + var bundleData = orderItem.GetBundleData(); + + bundleData.ForEach(x => _productAttributeParser.DeserializeQueryData(attributeQueryData, x.AttributesXml, x.ProductId, x.BundleItemId)); + } + + orderItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(attributeQueryData, orderItemModel.ProductSeName); //unit price switch (order.CustomerTaxDisplayType) @@ -124,6 +144,8 @@ protected SubmitReturnRequestModel PrepareReturnRequestModel(SubmitReturnRequest } break; } + + model.Items.Add(orderItemModel); } return model; diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index d4d1010330..e4ff2cb6a7 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Web; using System.Web.Mvc; @@ -33,7 +32,9 @@ using SmartStore.Services.Seo; using SmartStore.Services.Shipping; using SmartStore.Services.Tax; +using SmartStore.Services.Topics; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Plugins; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.UI.Captcha; @@ -91,12 +92,15 @@ public partial class ShoppingCartController : PublicControllerBase private readonly AddressSettings _addressSettings; private readonly PluginMediator _pluginMediator; private readonly IQuantityUnitService _quantityUnitService; + private readonly Lazy _topicService; + private readonly IMeasureService _measureService; + private readonly MeasureSettings _measureSettings; - #endregion + #endregion - #region Constructors + #region Constructors - public ShoppingCartController(IProductService productService, + public ShoppingCartController(IProductService productService, IWorkContext workContext, IStoreContext storeContext, IShoppingCartService shoppingCartService, IPictureService pictureService, ILocalizationService localizationService, @@ -121,7 +125,9 @@ public ShoppingCartController(IProductService productService, ShippingSettings shippingSettings, TaxSettings taxSettings, CaptchaSettings captchaSettings, AddressSettings addressSettings, HttpContextBase httpContext, PluginMediator pluginMediator, - IQuantityUnitService quantityUnitService) + IQuantityUnitService quantityUnitService, + Lazy topicService, + IMeasureService measureService, MeasureSettings measureSettings) { this._productService = productService; this._workContext = workContext; @@ -167,6 +173,9 @@ public ShoppingCartController(IProductService productService, this._addressSettings = addressSettings; this._pluginMediator = pluginMediator; this._quantityUnitService = quantityUnitService; + this._topicService = topicService; + this._measureService = measureService; + this._measureSettings = measureSettings; } #endregion @@ -174,7 +183,7 @@ public ShoppingCartController(IProductService productService, #region Utilities [NonAction] - protected PictureModel PrepareCartItemPictureModel(Product product, int pictureSize, bool showDefaultPicture, string productName, string attributesXml) + protected PictureModel PrepareCartItemPictureModel(Product product, int pictureSize, string productName, string attributesXml) { if (product == null) throw new ArgumentNullException("product"); @@ -205,10 +214,10 @@ protected PictureModel PrepareCartItemPictureModel(Product product, int pictureS picture = _pictureService.GetPicturesByProductId(product.ParentGroupedProductId, 1).FirstOrDefault(); } - return new PictureModel() + return new PictureModel { PictureId = picture != null ? picture.Id : 0, - ImageUrl = _pictureService.GetPictureUrl(picture, pictureSize, showDefaultPicture), + ImageUrl = _pictureService.GetPictureUrl(picture, pictureSize, !_catalogSettings.HideProductDefaultPictures), Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), productName), AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), productName), }; @@ -223,7 +232,7 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org product.MergeWithCombination(item.AttributesXml); - var model = new ShoppingCartModel.ShoppingCartItemModel() + var model = new ShoppingCartModel.ShoppingCartItemModel { Id = item.Id, Sku = product.Sku, @@ -235,17 +244,22 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org IsShipEnabled = product.IsShipEnabled, ShortDesc = product.GetLocalized(x => x.ShortDescription), ProductType = product.ProductType, - BasePrice = product.GetBasePriceInfo(_localizationService, _priceFormatter), - Weight = product.Weight + BasePrice = product.GetBasePriceInfo(_localizationService, _priceFormatter, _currencyService, _taxService, _priceCalculationService, _workContext.WorkingCurrency), + Weight = product.Weight, + IsDownload = product.IsDownload, + HasUserAgreement = product.HasUserAgreement, + IsEsd = product.IsEsd }; + model.ProductUrl = GetProductUrlWithAttributes(sci, model.ProductSeName); + if (item.BundleItem != null) { model.BundlePerItemPricing = item.BundleItem.BundleProduct.BundlePerItemPricing; model.BundlePerItemShoppingCart = item.BundleItem.BundleProduct.BundlePerItemShoppingCart; model.AttributeInfo = _productAttributeFormatter.FormatAttributes(product, item.AttributesXml, _workContext.CurrentCustomer, - renderPrices: false, renderGiftCardAttributes: true, allowHyperlinks: false); + renderPrices: false, renderGiftCardAttributes: true, allowHyperlinks: true); string bundleItemName = item.BundleItem.GetLocalized(x => x.Name); if (bundleItemName.HasValue()) @@ -271,6 +285,13 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org else { model.AttributeInfo = _productAttributeFormatter.FormatAttributes(product, item.AttributesXml); + + var selectedAttributeValues = _productAttributeParser.ParseProductVariantAttributeValues(item.AttributesXml).ToList(); + if (selectedAttributeValues != null) + { + foreach (var attributeValue in selectedAttributeValues) + model.Weight = decimal.Add(model.Weight, attributeValue.WeightAdjustment); + } } if (product.DisplayDeliveryTimeAccordingToStock(_catalogSettings)) @@ -360,23 +381,33 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org decimal shoppingCartItemDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartItemDiscountBase, _workContext.WorkingCurrency); model.Discount = _priceFormatter.FormatPrice(shoppingCartItemDiscount); } + + model.BasePrice = product.GetBasePriceInfo( + _localizationService, + _priceFormatter, + _currencyService, + _taxService, + _priceCalculationService, + _workContext.WorkingCurrency, + (product.Price - _priceCalculationService.GetUnitPrice(sci, true)) * (-1) + ); } //picture if (item.BundleItem != null) { if (_shoppingCartSettings.ShowProductBundleImagesOnShoppingCart) - model.Picture = PrepareCartItemPictureModel(product, _mediaSettings.CartThumbBundleItemPictureSize, true, model.ProductName, item.AttributesXml); + model.Picture = PrepareCartItemPictureModel(product, _mediaSettings.CartThumbBundleItemPictureSize, model.ProductName, item.AttributesXml); } else { if (_shoppingCartSettings.ShowProductImagesOnShoppingCart) - model.Picture = PrepareCartItemPictureModel(product, _mediaSettings.CartThumbPictureSize, true, model.ProductName, item.AttributesXml); + model.Picture = PrepareCartItemPictureModel(product, _mediaSettings.CartThumbPictureSize, model.ProductName, item.AttributesXml); } //item warnings var itemWarnings = _shoppingCartService.GetShoppingCartItemWarnings(_workContext.CurrentCustomer, item.ShoppingCartType, product, item.StoreId, - item.AttributesXml, item.CustomerEnteredPrice, item.Quantity, false, childItems: sci.ChildItems); + item.AttributesXml, item.CustomerEnteredPrice, item.Quantity, false, bundleItem: item.BundleItem, childItems: sci.ChildItems); foreach (var warning in itemWarnings) { @@ -395,6 +426,7 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org return model; } + private WishlistModel.ShoppingCartItemModel PrepareWishlistCartItemModel(OrganizedShoppingCartItem sci) { var item = sci.Item; @@ -402,7 +434,7 @@ private WishlistModel.ShoppingCartItemModel PrepareWishlistCartItemModel(Organiz product.MergeWithCombination(item.AttributesXml); - var model = new WishlistModel.ShoppingCartItemModel() + var model = new WishlistModel.ShoppingCartItemModel { Id = item.Id, Sku = product.Sku, @@ -415,6 +447,8 @@ private WishlistModel.ShoppingCartItemModel PrepareWishlistCartItemModel(Organiz VisibleIndividually = product.VisibleIndividually }; + model.ProductUrl = GetProductUrlWithAttributes(sci, model.ProductSeName); + if (item.BundleItem != null) { model.BundlePerItemPricing = item.BundleItem.BundleProduct.BundlePerItemPricing; @@ -452,7 +486,7 @@ private WishlistModel.ShoppingCartItemModel PrepareWishlistCartItemModel(Organiz var allowedQuantities = product.ParseAllowedQuatities(); foreach (var qty in allowedQuantities) { - model.AllowedQuantities.Add(new SelectListItem() + model.AllowedQuantities.Add(new SelectListItem { Text = qty.ToString(), Value = qty.ToString(), @@ -511,12 +545,12 @@ private WishlistModel.ShoppingCartItemModel PrepareWishlistCartItemModel(Organiz if (item.BundleItem != null) { if (_shoppingCartSettings.ShowProductBundleImagesOnShoppingCart) - model.Picture = PrepareCartItemPictureModel(product, _mediaSettings.CartThumbBundleItemPictureSize, true, model.ProductName, item.AttributesXml); + model.Picture = PrepareCartItemPictureModel(product, _mediaSettings.CartThumbBundleItemPictureSize, model.ProductName, item.AttributesXml); } else { if (_shoppingCartSettings.ShowProductImagesOnShoppingCart) - model.Picture = PrepareCartItemPictureModel(product, _mediaSettings.CartThumbPictureSize, true, model.ProductName, item.AttributesXml); + model.Picture = PrepareCartItemPictureModel(product, _mediaSettings.CartThumbPictureSize, model.ProductName, item.AttributesXml); } //item warnings @@ -545,9 +579,10 @@ private void PrepareButtonPaymentMethodModel(ButtonPaymentMethodModel model, ILi { model.Items.Clear(); + var paymentTypes = new PaymentMethodType[] { PaymentMethodType.Button, PaymentMethodType.StandardAndButton }; + var boundPaymentMethods = _paymentService - .LoadActivePaymentMethods(_workContext.CurrentCustomer.Id, _storeContext.CurrentStore.Id) - .Where(pm => pm.Value.PaymentMethodType == PaymentMethodType.Button || pm.Value.PaymentMethodType == PaymentMethodType.StandardAndButton) + .LoadActivePaymentMethods(_workContext.CurrentCustomer, cart, _storeContext.CurrentStore.Id, paymentTypes, false) .ToList(); foreach (var pm in boundPaymentMethods) @@ -560,7 +595,7 @@ private void PrepareButtonPaymentMethodModel(ButtonPaymentMethodModel model, ILi RouteValueDictionary routeValues; pm.Value.GetPaymentInfoRoute(out actionName, out controllerName, out routeValues); - model.Items.Add(new ButtonPaymentMethodModel.ButtonPaymentMethodItem() + model.Items.Add(new ButtonPaymentMethodModel.ButtonPaymentMethodItem { ActionName = actionName, ControllerName = controllerName, @@ -608,12 +643,21 @@ protected void PrepareShoppingCartModel(ShoppingCartModel model, model.ShowProductImages = _shoppingCartSettings.ShowProductImagesOnShoppingCart; model.ShowProductBundleImages = _shoppingCartSettings.ShowProductBundleImagesOnShoppingCart; model.ShowSku = _catalogSettings.ShowProductSku; + + var measure = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); + if(measure != null) + { + model.MeasureUnitName = measure.Name; + } + + var checkoutAttributesXml = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService); model.CheckoutAttributeInfo = HtmlUtils.ConvertPlainTextToTable(HtmlUtils.ConvertHtmlToPlainText( _checkoutAttributeFormatter.FormatAttributes(checkoutAttributesXml, _workContext.CurrentCustomer) )); //model.CheckoutAttributeInfo = _checkoutAttributeFormatter.FormatAttributes(_workContext.CurrentCustomer.CheckoutAttributes, _workContext.CurrentCustomer); //model.CheckoutAttributeInfo = _checkoutAttributeFormatter.FormatAttributes(_workContext.CurrentCustomer.CheckoutAttributes, _workContext.CurrentCustomer, "", false); + bool minOrderSubtotalAmountOk = _orderProcessingService.ValidateMinOrderSubtotalAmount(cart); if (!minOrderSubtotalAmountOk) { @@ -626,23 +670,37 @@ protected void PrepareShoppingCartModel(ShoppingCartModel model, model.DiscountBox.Display = _shoppingCartSettings.ShowDiscountBox; var discountCouponCode = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.DiscountCouponCode); var discount = _discountService.GetDiscountByCouponCode(discountCouponCode); - if (discount != null && - discount.RequiresCouponCode && - _discountService.IsDiscountValid(discount, _workContext.CurrentCustomer)) + if (discount != null && discount.RequiresCouponCode && _discountService.IsDiscountValid(discount, _workContext.CurrentCustomer)) + { model.DiscountBox.CurrentCode = discount.CouponCode; + } + model.GiftCardBox.Display = _shoppingCartSettings.ShowGiftCardBox; model.DisplayCommentBox = _shoppingCartSettings.ShowCommentBox; + model.NewsLetterSubscription = _shoppingCartSettings.NewsLetterSubscription; + model.ThirdPartyEmailHandOver = _shoppingCartSettings.ThirdPartyEmailHandOver; + model.DisplayEsdRevocationWaiverBox = _shoppingCartSettings.ShowEsdRevocationWaiverBox; + + if (_shoppingCartSettings.ThirdPartyEmailHandOver != CheckoutThirdPartyEmailHandOver.None) + { + model.ThirdPartyEmailHandOverLabel = _shoppingCartSettings.GetLocalized(x => x.ThirdPartyEmailHandOverLabel, _workContext.WorkingLanguage.Id, true, false); + + if (model.ThirdPartyEmailHandOverLabel.IsEmpty()) + model.ThirdPartyEmailHandOverLabel = T("Admin.Configuration.Settings.ShoppingCart.ThirdPartyEmailHandOverLabel.Default"); + } //cart warnings var cartWarnings = _shoppingCartService.GetShoppingCartWarnings(cart, checkoutAttributesXml, validateCheckoutAttributes); foreach (var warning in cartWarnings) + { model.Warnings.Add(warning); + } #endregion #region Checkout attributes - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(); + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id); if (!cart.RequiresShipping()) { //remove attributes which require shippable products @@ -865,6 +923,7 @@ protected void PrepareWishlistModel(WishlistModel model, IList x.Item.Id) .Take(_shoppingCartSettings.MiniShoppingCartProductNumber) .ToList()) { - var cartItemModel = new MiniShoppingCartModel.ShoppingCartItemModel() + var item = sci.Item; + var product = sci.Item.Product; + + var cartItemModel = new MiniShoppingCartModel.ShoppingCartItemModel { - Id = sci.Item.Id, - ProductId = sci.Item.Product.Id, - ProductName = sci.Item.Product.GetLocalized(x => x.Name), - ProductSeName = sci.Item.Product.GetSeName(), - Quantity = sci.Item.Quantity, + Id = item.Id, + ProductId = product.Id, + ProductName = product.GetLocalized(x => x.Name), + ProductSeName = product.GetSeName(), + Quantity = item.Quantity, AttributeInfo = _productAttributeFormatter.FormatAttributes( - sci.Item.Product, - sci.Item.AttributesXml, + product, + item.AttributesXml, null, serapator: ", ", renderPrices: false, @@ -958,35 +1019,40 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() allowHyperlinks: false) }; - if (sci.Item.Product.ProductType == ProductType.BundledProduct) - { - var bundleItems = _productService.GetBundleItems(sci.Item.Product.Id); - foreach (var bundleItem in bundleItems) - { - var bundleItemModel = new MiniShoppingCartModel.ShoppingCartItemBundleItem(); - bundleItemModel.ProductName = bundleItem.Item.Product.GetLocalized(x => x.Name); - var bundlePic = _pictureService.GetPicturesByProductId(bundleItem.Item.ProductId).FirstOrDefault(); - if(bundlePic != null) - bundleItemModel.PictureUrl = _pictureService.GetPictureUrl(bundlePic.Id, 32); + cartItemModel.ProductUrl = GetProductUrlWithAttributes(sci, cartItemModel.ProductSeName); - bundleItemModel.ProductSeName = bundleItem.Item.Product.GetSeName(); + if (sci.ChildItems != null && _shoppingCartSettings.ShowProductBundleImagesOnShoppingCart) + { + foreach (var childItem in sci.ChildItems.Where(x => x.Item.Id != item.Id && x.Item.BundleItem != null && !x.Item.BundleItem.HideThumbnail)) + { + var bundleItemModel = new MiniShoppingCartModel.ShoppingCartItemBundleItem + { + ProductName = childItem.Item.Product.GetLocalized(x => x.Name), + ProductSeName = childItem.Item.Product.GetSeName(), + }; - if (!bundleItem.Item.HideThumbnail) - cartItemModel.BundleItems.Add(bundleItemModel); - } - } + bundleItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes( + childItem.Item.AttributesXml, childItem.Item.ProductId, bundleItemModel.ProductSeName); + + var itemPicture = _pictureService.GetPicturesByProductId(childItem.Item.ProductId, 1).FirstOrDefault(); + if (itemPicture != null) + bundleItemModel.PictureUrl = _pictureService.GetPictureUrl(itemPicture.Id, 32); + + cartItemModel.BundleItems.Add(bundleItemModel); + } + } //unit prices - if (sci.Item.Product.CallForPrice) + if (product.CallForPrice) { cartItemModel.UnitPrice = _localizationService.GetResource("Products.CallForPrice"); } else { - sci.Item.Product.MergeWithCombination(sci.Item.AttributesXml); + product.MergeWithCombination(item.AttributesXml); decimal taxRate = decimal.Zero; - decimal shoppingCartUnitPriceWithDiscountBase = _taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate); + decimal shoppingCartUnitPriceWithDiscountBase = _taxService.GetProductPrice(product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate); decimal shoppingCartUnitPriceWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartUnitPriceWithDiscountBase, _workContext.WorkingCurrency); cartItemModel.UnitPrice = _priceFormatter.FormatPrice(shoppingCartUnitPriceWithDiscount); @@ -995,8 +1061,7 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() //picture if (_shoppingCartSettings.ShowProductImagesInMiniShoppingCart) { - cartItemModel.Picture = PrepareCartItemPictureModel(sci.Item.Product, - _mediaSettings.MiniCartThumbPictureSize, true, cartItemModel.ProductName, sci.Item.AttributesXml); + cartItemModel.Picture = PrepareCartItemPictureModel(product, _mediaSettings.MiniCartThumbPictureSize, cartItemModel.ProductName, item.AttributesXml); } model.Items.Add(cartItemModel); @@ -1010,15 +1075,18 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() protected void ParseAndSaveCheckoutAttributes(List cart, FormCollection form) { string selectedAttributes = ""; - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(); + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id); + if (!cart.RequiresShipping()) { //remove attributes which require shippable products checkoutAttributes = checkoutAttributes.RemoveShippableAttributes(); } + foreach (var attribute in checkoutAttributes) { string controlId = string.Format("checkout_attribute_{0}", attribute.Id); + switch (attribute.AttributeControlType) { case AttributeControlType.DropdownList: @@ -1028,13 +1096,13 @@ protected void ParseAndSaveCheckoutAttributes(List ca var rblAttributes = form[controlId]; if (!String.IsNullOrEmpty(rblAttributes)) { - int selectedAttributeId = int.Parse(rblAttributes); + var selectedAttributeId = rblAttributes.SplitSafe(",").SafeGet(0).ToInt(); if (selectedAttributeId > 0) - selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, - attribute, selectedAttributeId.ToString()); + selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, attribute, selectedAttributeId.ToString()); } } break; + case AttributeControlType.Checkboxes: { var cblAttributes = form[controlId]; @@ -1042,14 +1110,14 @@ protected void ParseAndSaveCheckoutAttributes(List ca { foreach (var item in cblAttributes.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { - int selectedAttributeId = int.Parse(item); - if (selectedAttributeId > 0) - selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, - attribute, selectedAttributeId.ToString()); + var selectedAttributeId = item.SplitSafe(",").SafeGet(0).ToInt(); + if (selectedAttributeId > 0) + selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, attribute, selectedAttributeId.ToString()); } } } break; + case AttributeControlType.TextBox: case AttributeControlType.MultilineTextbox: { @@ -1057,36 +1125,38 @@ protected void ParseAndSaveCheckoutAttributes(List ca if (!String.IsNullOrEmpty(txtAttribute)) { string enteredText = txtAttribute.Trim(); - selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, - attribute, enteredText); + selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, attribute, enteredText); } } break; + case AttributeControlType.Datepicker: { var date = form[controlId + "_day"]; var month = form[controlId + "_month"]; var year = form[controlId + "_year"]; DateTime? selectedDate = null; + try { selectedDate = new DateTime(Int32.Parse(year), Int32.Parse(month), Int32.Parse(date)); } catch { } + if (selectedDate.HasValue) { - selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, - attribute, selectedDate.Value.ToString("D")); + selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, attribute, selectedDate.Value.ToString("D")); } } break; + case AttributeControlType.FileUpload: { - var httpPostedFile = this.Request.Files[controlId]; - if ((httpPostedFile != null) && (!String.IsNullOrEmpty(httpPostedFile.FileName))) + var postedFile = this.Request.Files[controlId].ToPostedFileResult(); + if (postedFile != null && postedFile.FileName.HasValue()) { int fileMaxSize = _catalogSettings.FileUploadMaximumSizeBytes; - if (httpPostedFile.ContentLength > fileMaxSize) + if (postedFile.Size > fileMaxSize) { //TODO display warning //warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.MaximumUploadedFileSize"), (int)(fileMaxSize / 1024))); @@ -1094,25 +1164,25 @@ protected void ParseAndSaveCheckoutAttributes(List ca else { //save an uploaded file - var download = new Download() + var download = new Download { DownloadGuid = Guid.NewGuid(), UseDownloadUrl = false, DownloadUrl = "", - DownloadBinary = httpPostedFile.GetDownloadBits(), - ContentType = httpPostedFile.ContentType, - Filename = System.IO.Path.GetFileNameWithoutExtension(httpPostedFile.FileName), - Extension = System.IO.Path.GetExtension(httpPostedFile.FileName), + DownloadBinary = postedFile.Buffer, + ContentType = postedFile.ContentType, + Filename = postedFile.FileTitle, + Extension = postedFile.FileExtension, IsNew = true }; _downloadService.InsertDownload(download); //save attribute - selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, - attribute, download.DownloadGuid.ToString()); + selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, attribute, download.DownloadGuid.ToString()); } } } break; + default: break; } @@ -1122,6 +1192,27 @@ protected void ParseAndSaveCheckoutAttributes(List ca _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.CheckoutAttributes, selectedAttributes); } + private string GetProductUrlWithAttributes(OrganizedShoppingCartItem cartItem, string productSeName) + { + var attributeQueryData = new List>(); + var product = cartItem.Item.Product; + + if (product.ProductType != ProductType.BundledProduct) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, cartItem.Item.AttributesXml, product.Id); + } + else if (cartItem.ChildItems != null && product.BundlePerItemPricing) + { + foreach (var childItem in cartItem.ChildItems.Where(x => x.Item.Id != cartItem.Item.Id)) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, childItem.Item.AttributesXml, childItem.Item.ProductId, childItem.BundleItemData.Item.Id); + } + } + + var url = _productAttributeParser.GetProductUrlWithAttributes(attributeQueryData, productSeName); + return url; + } + #endregion #region Shopping cart @@ -1140,7 +1231,7 @@ public ActionResult AddProductSimple(int productId, bool forceredirection = fals return Json(new { success = false, - message = "No product found with the specified ID" + message = T("Products.NotFound", productId) }); } @@ -1367,86 +1458,62 @@ public ActionResult UploadFileProductAttribute(int productId, int productAttribu { success = false, downloadGuid = Guid.Empty, - }, "text/plain"); + }); } - //ensure that this attribute belong to this product and has "file upload" type + + // ensure that this attribute belong to this product and has "file upload" type var pva = _productAttributeService .GetProductVariantAttributesByProductId(productId) .Where(pa => pa.ProductAttributeId == productAttributeId) .FirstOrDefault(); + if (pva == null || pva.AttributeControlType != AttributeControlType.FileUpload) { return Json(new { success = false, downloadGuid = Guid.Empty, - }, "text/plain"); - } - - //we process it distinct ways based on a browser - //find more info here http://stackoverflow.com/questions/4884920/mvc3-valums-ajax-file-upload - Stream stream = null; - var fileName = ""; - var contentType = ""; - if (String.IsNullOrEmpty(Request["qqfile"])) - { - // IE - HttpPostedFileBase httpPostedFile = Request.Files[0]; - if (httpPostedFile == null) - throw new ArgumentException("No file uploaded"); - stream = httpPostedFile.InputStream; - fileName = Path.GetFileName(httpPostedFile.FileName); - contentType = httpPostedFile.ContentType; - } - else - { - //Webkit, Mozilla - stream = Request.InputStream; - fileName = Request["qqfile"]; + }); } - var fileBinary = new byte[stream.Length]; - stream.Read(fileBinary, 0, fileBinary.Length); - - var fileExtension = Path.GetExtension(fileName); - if (!String.IsNullOrEmpty(fileExtension)) - fileExtension = fileExtension.ToLowerInvariant(); + var postedFile = Request.ToPostedFileResult(); + if (postedFile == null) + { + throw new ArgumentException(T("Common.NoFileUploaded")); + } int fileMaxSize = _catalogSettings.FileUploadMaximumSizeBytes; - if (fileBinary.Length > fileMaxSize) + if (postedFile.Size > fileMaxSize) { - //when returning JSON the mime-type must be set to text/plain - //otherwise some browsers will pop-up a "Save As" dialog. return Json(new { success = false, message = string.Format(_localizationService.GetResource("ShoppingCart.MaximumUploadedFileSize"), (int)(fileMaxSize / 1024)), downloadGuid = Guid.Empty, - }, "text/plain"); + }); } - var download = new Download() + var download = new Download { DownloadGuid = Guid.NewGuid(), UseDownloadUrl = false, DownloadUrl = "", - DownloadBinary = fileBinary, - ContentType = contentType, - //we store filename without extension for downloads - Filename = Path.GetFileNameWithoutExtension(fileName), - Extension = fileExtension, - IsNew = true + DownloadBinary = postedFile.Buffer, + ContentType = postedFile.ContentType, + // we store filename without extension for downloads + Filename = postedFile.FileTitle, + Extension = postedFile.FileExtension, + IsNew = true, + IsTransient = true }; _downloadService.InsertDownload(download); - //when returning JSON the mime-type must be set to text/plain - //otherwise some browsers will pop-up a "Save As" dialog. return Json(new { success = true, message = _localizationService.GetResource("ShoppingCart.FileUploaded"), downloadGuid = download.DownloadGuid, - }, "text/plain"); + }); } @@ -1681,14 +1748,7 @@ public ActionResult DeleteCartItem(int cartItemId, bool? wishlistItem) public ActionResult ContinueShopping() { string returnUrl = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.LastContinueShoppingPage, _storeContext.CurrentStore.Id); - if (!String.IsNullOrEmpty(returnUrl)) - { - return Redirect(returnUrl); - } - else - { - return RedirectToRoute("HomePage"); - } + return RedirectToReferrer(returnUrl); } [ValidateInput(false)] @@ -1806,7 +1866,8 @@ public ActionResult ApplyGiftCard(string giftcardcouponcode, FormCollection form [FormValueRequired("estimateshipping")] public ActionResult GetEstimateShipping(EstimateShippingModel shippingModel, FormCollection form) { - var cart = _workContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, _storeContext.CurrentStore.Id); + var store = _storeContext.CurrentStore; + var cart = _workContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); //parse and save checkout attributes ParseAndSaveCheckoutAttributes(cart, form); @@ -1815,11 +1876,17 @@ public ActionResult GetEstimateShipping(EstimateShippingModel shippingModel, For model.EstimateShipping.CountryId = shippingModel.CountryId; model.EstimateShipping.StateProvinceId = shippingModel.StateProvinceId; model.EstimateShipping.ZipPostalCode = shippingModel.ZipPostalCode; + PrepareShoppingCartModel(model, cart, setEstimateShippingDefaultAddress: false); if (cart.RequiresShipping()) { - var address = new Address() + if (_topicService.Value.GetTopicBySystemName("ShippingInfo", store.Id) != null) + { + model.EstimateShipping.ShippingInfoUrl = Url.RouteUrl("Topic", new { SystemName = "shippinginfo" }); + } + + var address = new Address { CountryId = shippingModel.CountryId, Country = shippingModel.CountryId.HasValue ? _countryService.GetCountryById(shippingModel.CountryId.Value) : null, @@ -1827,12 +1894,15 @@ public ActionResult GetEstimateShipping(EstimateShippingModel shippingModel, For StateProvince = shippingModel.StateProvinceId.HasValue ? _stateProvinceService.GetStateProvinceById(shippingModel.StateProvinceId.Value) : null, ZipPostalCode = shippingModel.ZipPostalCode, }; - GetShippingOptionResponse getShippingOptionResponse = _shippingService - .GetShippingOptions(cart, address, "", _storeContext.CurrentStore.Id); + + var getShippingOptionResponse = _shippingService.GetShippingOptions(cart, address, "", store.Id); + if (!getShippingOptionResponse.Success) { - foreach (var error in getShippingOptionResponse.Errors) - model.EstimateShipping.Warnings.Add(error); + foreach (var error in getShippingOptionResponse.Errors) + { + model.EstimateShipping.Warnings.Add(error); + } } else { @@ -1842,12 +1912,13 @@ public ActionResult GetEstimateShipping(EstimateShippingModel shippingModel, For foreach (var shippingOption in getShippingOptionResponse.ShippingOptions) { - var soModel = new EstimateShippingModel.ShippingOptionModel() + var soModel = new EstimateShippingModel.ShippingOptionModel { + ShippingMethodId = shippingOption.ShippingMethodId, Name = shippingOption.Name, - Description = shippingOption.Description, - + Description = shippingOption.Description }; + //calculate discounted and taxed rate Discount appliedDiscount = null; decimal shippingTotal = _orderTotalCalculationService.AdjustShippingRate( @@ -1856,12 +1927,13 @@ public ActionResult GetEstimateShipping(EstimateShippingModel shippingModel, For decimal rateBase = _taxService.GetShippingPrice(shippingTotal, _workContext.CurrentCustomer); decimal rate = _currencyService.ConvertFromPrimaryStoreCurrency(rateBase, _workContext.WorkingCurrency); soModel.Price = _priceFormatter.FormatShippingPrice(rate, false /*true*/); + model.EstimateShipping.ShippingOptions.Add(soModel); } } else { - model.EstimateShipping.Warnings.Add(_localizationService.GetResource("Checkout.ShippingIsNotAllowed")); + model.EstimateShipping.Warnings.Add(T("Checkout.ShippingIsNotAllowed")); } } } @@ -2560,7 +2632,7 @@ public ActionResult FlyoutWishlist() { Customer customer = _workContext.CurrentCustomer; - var cart = customer.GetCartItems(ShoppingCartType.Wishlist, _storeContext.CurrentStore.Id, true); + var cart = customer.GetCartItems(ShoppingCartType.Wishlist, _storeContext.CurrentStore.Id); var model = new WishlistModel(); PrepareWishlistModel(model, cart, true); diff --git a/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs b/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs new file mode 100644 index 0000000000..68c94b3fe7 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using System.Web.SessionState; +using SmartStore.Core.Domain.Tasks; +using SmartStore.Services.Tasks; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Services.Security; +using SmartStore.Services; +using SmartStore.Collections; + +namespace SmartStore.Web.Controllers +{ + + [SessionState(SessionStateBehavior.ReadOnly)] + public class TaskSchedulerController : Controller + { + private readonly ITaskScheduler _taskScheduler; + private readonly IScheduleTaskService _scheduledTaskService; + private readonly ITaskExecutor _taskExecutor; + private readonly ICommonServices _services; + + public TaskSchedulerController( + ITaskScheduler taskScheduler, + IScheduleTaskService scheduledTaskService, + ITaskExecutor taskExecutor, + ICommonServices services) + { + this._taskScheduler = taskScheduler; + this._scheduledTaskService = scheduledTaskService; + this._taskExecutor = taskExecutor; + this._services = services; + } + + [HttpPost] + public ActionResult Sweep() + { + if (!_taskScheduler.VerifyAuthToken(Request.Headers["X-AUTH-TOKEN"])) + return new HttpUnauthorizedResult(); + + var pendingTasks = _scheduledTaskService.GetPendingTasks(); + var prevTaskStart = DateTime.UtcNow; + var count = 0; + + for (var i = 0; i < pendingTasks.Count; i++) + { + var task = pendingTasks[i]; + + if (i > 0) + { + // Maybe a subsequent Sweep call or another machine in a webfarm executed + // successive tasks already. + // To be able to determine this, we need to reload the entity from the database. + // The TaskExecutor will exit when the task should be in running state then. + _services.DbContext.ReloadEntity(task); + } + + if (task.IsPending) + { + prevTaskStart = DateTime.UtcNow; + _taskExecutor.Execute(task); + count++; + } + } + + return Content("{0} of {1} pending tasks executed".FormatInvariant(count, pendingTasks.Count)); + } + + [HttpPost] + public ActionResult Execute(int id /* taskId */) + { + if (!_taskScheduler.VerifyAuthToken(Request.Headers["X-AUTH-TOKEN"])) + return new HttpUnauthorizedResult(); + + var task = _scheduledTaskService.GetTaskById(id); + if (task == null) + return HttpNotFound(); + + _taskExecutor.Execute(task, QueryString.Current.ToDictionary()); + + return Content("Task '{0}' executed".FormatCurrent(task.Name)); + } + + public ContentResult Noop() + { + return Content("noop"); + } + + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Controllers/ThemeController.cs b/src/Presentation/SmartStore.Web/Controllers/ThemeController.cs index 645c7f19ad..92e8cde656 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ThemeController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ThemeController.cs @@ -3,7 +3,8 @@ using SmartStore.Core.Themes; using SmartStore.Services.Themes; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Themes; +using SmartStore.Web.Framework.Security; +using SmartStore.Web.Framework.Theming; namespace SmartStore.Web.Controllers { diff --git a/src/Presentation/SmartStore.Web/Controllers/TopicController.cs b/src/Presentation/SmartStore.Web/Controllers/TopicController.cs index 7e861fac2e..7c4be750a3 100644 --- a/src/Presentation/SmartStore.Web/Controllers/TopicController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/TopicController.cs @@ -66,7 +66,8 @@ protected TopicModel PrepareTopicModel(string systemName) MetaDescription = topic.GetLocalized(x => x.MetaDescription), MetaTitle = topic.GetLocalized(x => x.MetaTitle), TitleTag = titleTag, - }; + RenderAsWidget = topic.RenderAsWidget + }; return model; } @@ -79,8 +80,9 @@ public ActionResult TopicDetails(string systemName) var cacheKey = string.Format(ModelCacheEventConsumer.TOPIC_MODEL_KEY, systemName, _workContext.WorkingLanguage.Id, _storeContext.CurrentStore.Id); var cacheModel = _cacheManager.Get(cacheKey, () => PrepareTopicModel(systemName)); - if (cacheModel == null) + if (cacheModel == null || (cacheModel.RenderAsWidget && !cacheModel.IncludeInSitemap)) return HttpNotFound(); + return View("TopicDetails", cacheModel); } diff --git a/src/Presentation/SmartStore.Web/Controllers/WidgetController.cs b/src/Presentation/SmartStore.Web/Controllers/WidgetController.cs index 180440df34..74461e92ef 100644 --- a/src/Presentation/SmartStore.Web/Controllers/WidgetController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/WidgetController.cs @@ -9,10 +9,16 @@ namespace SmartStore.Web.Controllers public partial class WidgetController : PublicControllerBase { [ChildActionOnly] - public ActionResult WidgetsByZone(IEnumerable widgets) + public ActionResult WidgetsByZone(WidgetZoneModel zoneModel) { - return PartialView(widgets); + return PartialView(zoneModel); } + [ChildActionOnly] + public ActionResult TabWidgets(object model, string viewDataKey) + { + var widgets = this.ControllerContext.ParentActionViewContext.ViewData[viewDataKey]; + return PartialView(widgets); + } } } diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/placeholder.txt b/src/Presentation/SmartStore.Web/Exchange/placeholder similarity index 100% rename from src/Presentation/SmartStore.Web/Content/Images/Thumbs/placeholder.txt rename to src/Presentation/SmartStore.Web/Exchange/placeholder diff --git a/src/Presentation/SmartStore.Web/Extensions/MappingExtensions.cs b/src/Presentation/SmartStore.Web/Extensions/MappingExtensions.cs index 7198e82d35..fcd098d40b 100644 --- a/src/Presentation/SmartStore.Web/Extensions/MappingExtensions.cs +++ b/src/Presentation/SmartStore.Web/Extensions/MappingExtensions.cs @@ -21,11 +21,13 @@ public static CategoryModel ToModel(this Category entity) if (entity == null) return null; - var model = new CategoryModel() + var model = new CategoryModel { Id = entity.Id, Name = entity.GetLocalized(x => x.Name), + FullName = entity.GetLocalized(x => x.FullName), Description = entity.GetLocalized(x => x.Description), + BottomDescription = entity.GetLocalized(x => x.BottomDescription), MetaKeywords = entity.GetLocalized(x => x.MetaKeywords), MetaDescription = entity.GetLocalized(x => x.MetaDescription), MetaTitle = entity.GetLocalized(x => x.MetaTitle), diff --git a/src/Presentation/SmartStore.Web/Extensions/ProductDetailsExtensions.cs b/src/Presentation/SmartStore.Web/Extensions/ProductDetailsExtensions.cs index 7b82ca9ec0..18356aa3cb 100644 --- a/src/Presentation/SmartStore.Web/Extensions/ProductDetailsExtensions.cs +++ b/src/Presentation/SmartStore.Web/Extensions/ProductDetailsExtensions.cs @@ -11,6 +11,7 @@ namespace SmartStore.Web { public static class ProductDetailsExtensions { + public static string UpdateProductDetailsUrl(this ProductDetailsModel model, string itemType = null) { var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); @@ -24,16 +25,18 @@ public static string UpdateProductDetailsUrl(this ProductDetailsModel model, str return url; } + public static bool RenderBundleTitle(this ProductDetailsModel model) { return model.BundleTitleText.HasValue() && model.BundledItems.Where(x => x.BundleItem.Visible).Count() > 0; } + public static Picture GetAssignedPicture(this ProductDetailsModel model, IPictureService pictureService, IList pictures, int productId = 0) { - if (model != null && model.CombinationSelected != null) + if (model != null && model.SelectedCombination != null) { Picture picture = null; - var combiAssignedImages = model.CombinationSelected.GetAssignedPictureIds(); + var combiAssignedImages = model.SelectedCombination.GetAssignedPictureIds(); if (combiAssignedImages.Length > 0) { @@ -51,6 +54,7 @@ public static Picture GetAssignedPicture(this ProductDetailsModel model, IPictur } return null; } + public static string GetAttributeValueInfo(this ProductDetailsModel.ProductVariantAttributeValueModel model) { string result = ""; @@ -77,6 +81,7 @@ public static bool ShouldBeRendered(this ProductDetailsModel.ProductVariantAttri return true; } } + public static bool ShouldBeRendered(this IEnumerable variantAttributes) { if (variantAttributes != null) diff --git a/src/Presentation/SmartStore.Web/Global.asax.cs b/src/Presentation/SmartStore.Web/Global.asax.cs index affefd8a19..4297e54186 100644 --- a/src/Presentation/SmartStore.Web/Global.asax.cs +++ b/src/Presentation/SmartStore.Web/Global.asax.cs @@ -5,32 +5,30 @@ using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; +using System.Web.Security; using System.Web.WebPages; using FluentValidation.Mvc; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Events; using SmartStore.Core.Infrastructure; -using SmartStore.Core.Logging; +using SmartStore.Services.Customers; using SmartStore.Services.Tasks; -using SmartStore.Web.Controllers; -using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Mvc; -using SmartStore.Web.Framework.Mvc.Bundles; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Bundling; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Localization; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.Plugins; -using SmartStore.Web.Framework.Themes; +using SmartStore.Web.Framework.Routing; +using SmartStore.Web.Framework.Theming; using SmartStore.Web.Framework.Validators; - namespace SmartStore.Web { // Note: For instructions on enabling IIS6 or IIS7 classic mode, // visit http://go.microsoft.com/?LinkId=9394801 - public class MvcApplication : System.Web.HttpApplication { - public static void RegisterGlobalFilters(GlobalFilterCollection filters) { var eventPublisher = EngineContext.Current.Resolve(); @@ -118,9 +116,8 @@ protected void Application_Start() HostingEnvironment.RegisterVirtualPathProvider(new PluginDebugViewVirtualPathProvider()); } - // start scheduled tasks - TaskManager.Instance.Initialize(); - TaskManager.Instance.Start(); + // "throw-away" filter for task scheduler initialization (the filter removes itself when processed) + GlobalFilters.Filters.Add(new InitializeSchedulerFilter()); } else { @@ -138,7 +135,7 @@ public override string GetVaryByCustomString(HttpContext context, string custom) if (DataSettings.DatabaseIsInstalled()) { - custom = custom.ToLower(); + custom = custom.ToLowerInvariant(); switch (custom) { @@ -164,6 +161,19 @@ public override string GetVaryByCustomString(HttpContext context, string custom) return base.GetVaryByCustomString(context, custom); } - } - + public void AnonymousIdentification_Creating(object sender, AnonymousIdentificationEventArgs args) + { + try + { + var customerService = EngineContext.Current.Resolve(); + var customer = customerService.FindGuestCustomerByClientIdent(maxAgeSeconds: 180); + if (customer != null) + { + // We found our anonymous visitor: don't let ASP.NET create a new id. + args.AnonymousID = customer.CustomerGuid.ToString(); + } + } + catch { } + } + } } diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Cache/ModelCacheEventConsumer.cs b/src/Presentation/SmartStore.Web/Infrastructure/Cache/ModelCacheEventConsumer.cs index ce7362d2ec..c17e051277 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Cache/ModelCacheEventConsumer.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Cache/ModelCacheEventConsumer.cs @@ -40,7 +40,7 @@ public partial class ModelCacheEventConsumer: IConsumer>, IConsumer>, //categories - IConsumer>, // codehint: sm-add + IConsumer>, IConsumer>, IConsumer>, //product categories @@ -122,15 +122,16 @@ public partial class ModelCacheEventConsumer: IConsumer>, IConsumer> { - /// - /// Key for ManufacturerNavigationModel caching - /// - /// - /// {0} : current manufacturer id - /// {1} : language id - /// {2} : current store ID - /// - public const string MANUFACTURER_NAVIGATION_MODEL_KEY = "sm.pres.manufacturer.navigation-{0}-{1}-{2}"; + /// + /// Key for ManufacturerNavigationModel caching + /// + /// + /// {0} : current manufacturer id + /// {1} : value indicating whether a default picture is displayed in case if no real picture exists + /// {2} : language id + /// {3} : current store ID + /// + public const string MANUFACTURER_NAVIGATION_MODEL_KEY = "sm.pres.manufacturer.navigation-{0}-{1}-{2}-{3}"; public const string MANUFACTURER_NAVIGATION_PATTERN_KEY = "sm.pres.manufacturer.navigation"; /// @@ -199,15 +200,16 @@ public partial class ModelCacheEventConsumer: public const string PRODUCTTAG_POPULAR_MODEL_KEY = "sm.pres.producttag.popular-{0}-{1}"; public const string PRODUCTTAG_POPULAR_PATTERN_KEY = "sm.pres.producttag.popular"; - /// - /// Key for ProductManufacturers model caching - /// - /// - /// {0} : product id - /// {1} : language id - /// {2} : current store ID - /// - public const string PRODUCT_MANUFACTURERS_MODEL_KEY = "sm.pres.product.manufacturers-{0}-{1}-{2}"; + /// + /// Key for ProductManufacturers model caching + /// + /// + /// {0} : product id + /// {1} : value indicating whether a default picture is displayed in case if no real picture exists + /// {2} : language id + /// {3} : current store ID + /// + public const string PRODUCT_MANUFACTURERS_MODEL_KEY = "sm.pres.product.manufacturers-{0}-{1}-{2}-{3}"; public const string PRODUCT_MANUFACTURERS_PATTERN_KEY = "sm.pres.product.manufacturers"; /// diff --git a/src/Presentation/SmartStore.Web/Infrastructure/DefaultBundles.cs b/src/Presentation/SmartStore.Web/Infrastructure/DefaultBundles.cs index a3ee64e969..d64c2a3d2f 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/DefaultBundles.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/DefaultBundles.cs @@ -4,7 +4,7 @@ using System.Web; using System.Web.Optimization; using BundleTransformer.Core.Bundles; -using SmartStore.Web.Framework.Mvc.Bundles; +using SmartStore.Web.Framework.Bundling; namespace SmartStore.Web.Infrastructure { diff --git a/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs b/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs index 1bf8f34b3c..48ce046bc7 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/DefaultWidgetSelector.cs @@ -82,7 +82,6 @@ public virtual IEnumerable GetWidgets(string widgetZone, object #endregion - #region Topic Widgets // add special "topic widgets" to the list diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Installation/DeDESeedData.cs b/src/Presentation/SmartStore.Web/Infrastructure/Installation/DeDESeedData.cs index 8f6855289a..3896e5c829 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Installation/DeDESeedData.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Installation/DeDESeedData.cs @@ -1,35 +1,28 @@ using System; -using System.Linq; -using System.Linq.Expressions; using System.Collections.Generic; -using System.IO; -using SmartStore.Core; +using System.Linq; 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; using SmartStore.Core.Domain.Discounts; -using SmartStore.Core.Domain.Messages; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Logging; +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.Tax; -using SmartStore.Core.Domain.Topics; using SmartStore.Core.Domain.Seo; -using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Domain.Tasks; -using SmartStore.Core.Domain.Payments; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Domain.Media; +using SmartStore.Core.Domain.Tasks; +using SmartStore.Core.Domain.Tax; +using SmartStore.Core.Domain.Topics; using SmartStore.Data.Setup; namespace SmartStore.Web.Infrastructure.Installation @@ -46,14 +39,18 @@ protected override void Alter(Customer entity) { base.Alter(entity); - if (entity.SystemName == "builtin@search-engine-record.com") + if (entity.SystemName == SystemCustomerNames.SearchEngine) { - entity.AdminComment = "System Gastkonto für Suchmaschinenanfragen."; + entity.AdminComment = "System-Gastkonto für Suchmaschinenanfragen."; } - else if (entity.SystemName == "builtin@background-task-record.com") + else if (entity.SystemName == SystemCustomerNames.BackgroundTask) { - entity.AdminComment = "System Konto für geplante Aufgaben."; + entity.AdminComment = "Systemkonto für geplante Aufgaben."; } + else if (entity.SystemName == SystemCustomerNames.PdfConverter) + { + entity.AdminComment = "Systemkonto für den PDF-Konverter."; + } } protected override void Alter(IList entities) @@ -261,7 +258,7 @@ protected override void Alter(IList entities) .Alter("ReturnRequestStatusChanged.CustomerNotification", x => { x.Subject = "%Store.Name%. Rücksendung - Status-Änderung"; - x.Body = templateHeader + "

    %Store.Name% 

    Hallo %Customer.FullName%,

    der Status Ihrer Rücksendung #%ReturnRequest.ID% wurde aktualisiert.

    Mit freundlichen Grüßen,

    Ihr %Store.Name% - Team

    " + templateFooter; + x.Body = templateHeader + "

    %Store.Name% 

    Hallo %Customer.FullName%,

    der Status Ihrer Rücksendung #%ReturnRequest.ID% wurde aktualisiert: %ReturnRequest.Status%

    Mit freundlichen Grüßen,

    Ihr %Store.Name% - Team

    " + templateFooter; }) .Alter("Service.EmailAFriend", x => { @@ -286,7 +283,7 @@ protected override void Alter(IList entities) .Alter("Product.AskQuestion", x => { x.Subject = "%Store.Name% - Frage zu '%Product.Name%' von %ProductQuestion.SenderName%"; - x.Body = templateHeader + "

    %ProductQuestion.Message%

    %ProductQuestion.Message%

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

    " + templateFooter; + x.Body = templateHeader + "

    %ProductQuestion.Message%

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

    " + templateFooter; }) @@ -2108,12 +2105,6 @@ protected override void Alter(IList settings) x.BaseWeightId = base.DbContext.Set().Where(m => m.SystemKeyword == "kg").Single().Id; }) - .Alter(x => - { - x.PrimaryStoreCurrencyId = base.DbContext.Set().Where(c => c.CurrencyCode == "EUR").Single().Id; - x.PrimaryExchangeRateCurrencyId = base.DbContext.Set().Where(c => c.CurrencyCode == "EUR").Single().Id; - }) - .Alter(x => { x.DefaultTitle = "Mein Shop"; @@ -2521,30 +2512,38 @@ protected override void Alter(IList entities) { base.Alter(entities); - entities.WithKey(x => x.Name) - .Alter("Send emails", x => + entities.WithKey(x => x.Type) + .Alter("SmartStore.Services.Messages.QueuedMessagesSendTask, SmartStore.Services", x => { x.Name = "E-Mail senden"; }) - .Alter("Keep alive", x => - { - x.Name = "Keep alive"; - }) - .Alter("Delete guests", x => + .Alter("SmartStore.Services.Messages.QueuedMessagesClearTask, SmartStore.Services", x => + { + x.Name = "E-Mail Queue bereinigen"; + }) + .Alter("SmartStore.Services.Media.TransientMediaClearTask, SmartStore.Services", x => + { + x.Name = "Temporäre Uploads bereinigen"; + }) + .Alter("SmartStore.Services.Customers.DeleteGuestsTask, SmartStore.Services", x => { x.Name = "Gastbenutzer löschen"; }) - .Alter("Clear cache", x => + .Alter("SmartStore.Services.Caching.ClearCacheTask, SmartStore.Services", x => { x.Name = "Cache bereinigen"; }) - .Alter("Send emails", x => + .Alter("SmartStore.Services.Messages.QueuedMessagesSendTask, SmartStore.Services", x => { x.Name = "E-Mail senden"; }) - .Alter("Update currency exchange rates", x => + .Alter("SmartStore.Services.Directory.UpdateExchangeRateTask, SmartStore.Services", x => { x.Name = "Wechselkurse aktualisieren"; + }) + .Alter("SmartStore.Services.Common.TempFileCleanupTask, SmartStore.Services", x => + { + x.Name = "Temporäre Dateien bereinigen"; }); } @@ -3705,7 +3704,7 @@ protected override void Alter(IList entities) base.Alter(entities); entities.WithKey(x => x.MetaTitle) - .Alter("smartstore.net new release!", x => + .Alter("SmartStore.NET new release!", x => { x.Title = "SmartStore.NET - das clevere Shopsystem!"; x.Short = "SmartStore.NET ist die neue dynamische E-Commerce Lösung von SmartStore. SmartStore.NET bietet alle Funktionen und Möglichkeiten, um schnell und einfach einen leistungsfähigen und funktional kompletten Online-Shop zu erstellen."; diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs index fedf58aa95..4e964cc565 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs @@ -156,7 +156,7 @@ private void PopulateCustomersAndUsers(string defaultUserEmail, string defaultUs SaveRange(customerRoles.Where(x => x != null)); //admin user - var adminUser = new Customer() + var adminUser = new Customer { CustomerGuid = Guid.NewGuid(), Email = defaultUserEmail, @@ -184,7 +184,7 @@ private void PopulateCustomersAndUsers(string defaultUserEmail, string defaultUs this.GenericAttributeService.SaveAttribute(adminUser, SystemCustomerAttributeNames.LastName, adminUser.Addresses.FirstOrDefault().LastName); _ctx.SaveChanges(); - // search engine (crawler) built-in user + // Built-in user for search engines (crawlers) var customer = _data.SearchEngineUser(); customer.CustomerRoles.Add(customerRoles.SingleOrDefault(x => x.SystemName == SystemCustomerRoleNames.Guests)); Save(customer); @@ -193,6 +193,11 @@ private void PopulateCustomersAndUsers(string defaultUserEmail, string defaultUs customer = _data.BackgroundTaskUser(); customer.CustomerRoles.Add(customerRoles.SingleOrDefault(x => x.SystemName == SystemCustomerRoleNames.Guests)); Save(customer); + + // Built-in user for the PDF converter + customer = _data.PdfConverterUser(); + customer.CustomerRoles.Add(customerRoles.SingleOrDefault(x => x.SystemName == SystemCustomerRoleNames.Guests)); + Save(customer); } private void HashDefaultCustomerPassword(string defaultUserEmail, string defaultUserPassword) @@ -534,6 +539,7 @@ public virtual void Seed(SmartObjectContext context) _ctx.Configuration.AutoDetectChangesEnabled = false; _ctx.Configuration.ValidateOnSaveEnabled = false; + _ctx.HooksEnabled = false; _config.ProgressMessageCallback("Progress.CreatingRequiredData"); @@ -544,12 +550,12 @@ public virtual void Seed(SmartObjectContext context) }); Populate("PopulatePictures", _data.Pictures()); + Populate("PopulateCurrencies", PopulateCurrencies); Populate("PopulateStores", PopulateStores); Populate("InstallLanguages", () => PopulateLanguage(_config.Language)); Populate("PopulateMeasureDimensions", _data.MeasureDimensions()); Populate("PopulateMeasureWeights", _data.MeasureWeights()); Populate("PopulateTaxCategories", PopulateTaxCategories); - Populate("PopulateCurrencies", PopulateCurrencies); Populate("PopulateCountriesAndStates", PopulateCountriesAndStates); Populate("PopulateShippingMethods", PopulateShippingMethods); Populate("PopulateDeliveryTimes", _data.DeliveryTimes()); @@ -609,9 +615,11 @@ private void SetModified(TEntity entity) private string ValidateSeName(TEntity entity, string name) where TEntity : BaseEntity, ISlugSupported { + var seoSettings = new SeoSettings { LoadAllUrlAliasesOnStartup = false }; + if (_urlRecordService == null) { - _urlRecordService = new UrlRecordService(NullCache.Instance, new EfRepository(_ctx) { AutoCommitEnabled = false }); + _urlRecordService = new UrlRecordService(NullCache.Instance, new EfRepository(_ctx) { AutoCommitEnabled = false }, seoSettings); } return entity.ValidateSeName("", name, true, _urlRecordService, new SeoSettings()); diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallationLocalizationService.cs b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallationLocalizationService.cs index 1be0d35711..04ba8b6e49 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallationLocalizationService.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallationLocalizationService.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; using System.Web; using System.Xml; using SmartStore.Core; @@ -37,7 +38,7 @@ public string GetResource(string resourceName) .Select(r => r.Value) .FirstOrDefault(); if (String.IsNullOrEmpty(resourceValue)) - //return name + // return name return resourceName; return resourceValue; @@ -52,28 +53,46 @@ public virtual InstallationLanguage GetCurrentLanguage() if (cookie != null && !String.IsNullOrEmpty(cookie.Value)) cookieLanguageCode = cookie.Value; - //ensure it's available (it could be delete since the previous installation) + // ensure it's available (it could be deleted since the previous installation) var availableLanguages = GetAvailableLanguages(); var language = availableLanguages .Where(l => l.Code.Equals(cookieLanguageCode, StringComparison.InvariantCultureIgnoreCase)) - //.Where(l => l.Code.Equals("en", StringComparison.InvariantCultureIgnoreCase)) .FirstOrDefault(); if (language != null) return language; - //if we got here, the language code is not found. let's return the default one - language = availableLanguages - .Where(l => l.IsDefault) - .FirstOrDefault(); + // try to resolve install language from CurrentCulture + language = availableLanguages.Where(MatchLanguageByCurrentCulture).FirstOrDefault(); + if (language != null) + return language; + + // if we got here, the language code is not found. let's return the default one + language = availableLanguages.Where(l => l.IsDefault).FirstOrDefault(); if (language != null) return language; - //return any available language + // return any available language language = availableLanguages.FirstOrDefault(); return language; } + private bool MatchLanguageByCurrentCulture(InstallationLanguage language) + { + var curCulture = Thread.CurrentThread.CurrentUICulture; + + if (language.Code.IsCaseInsensitiveEqual(curCulture.IetfLanguageTag)) + return true; + + curCulture = curCulture.Parent; + if (curCulture != null) + { + return language.Code.IsCaseInsensitiveEqual(curCulture.IetfLanguageTag); + } + + return false; + } + public virtual void SaveCurrentLanguage(string languageCode) { var httpContext = EngineContext.Current.Resolve(); @@ -133,7 +152,7 @@ public virtual IList GetAvailableLanguages() var isRightToLeft = isRightToLeftAttribute != null ? Convert.ToBoolean(isRightToLeftAttribute.InnerText.Trim()) : false; //create language - var language = new InstallationLanguage() + var language = new InstallationLanguage { Code = languageCode, Name = languageName, diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Routes/1_StoreRoutes.cs b/src/Presentation/SmartStore.Web/Infrastructure/Routes/1_StoreRoutes.cs index fb5b9838e8..d5d5d1413d 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Routes/1_StoreRoutes.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Routes/1_StoreRoutes.cs @@ -3,7 +3,7 @@ using System.Web.Mvc.Routing.Constraints; using System.Web.Routing; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.Web.Infrastructure { diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Routes/2_DefaultRoutes.cs b/src/Presentation/SmartStore.Web/Infrastructure/Routes/2_DefaultRoutes.cs index 3a1c185382..352c05e1fb 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Routes/2_DefaultRoutes.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Routes/2_DefaultRoutes.cs @@ -6,9 +6,9 @@ using System.Linq; using SmartStore.Web.Controllers; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc.Routes; using SmartStore.Web.Framework.Seo; using System.Collections.Generic; +using SmartStore.Web.Framework.Routing; namespace SmartStore.Web.Infrastructure { diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Routes/3_GenericRoutes.cs b/src/Presentation/SmartStore.Web/Infrastructure/Routes/3_GenericRoutes.cs index 626f9f95a0..a3abdc0415 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Routes/3_GenericRoutes.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Routes/3_GenericRoutes.cs @@ -1,7 +1,7 @@ using System.Web.Mvc; using System.Web.Routing; using SmartStore.Web.Framework.Localization; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; using SmartStore.Web.Framework.Seo; namespace SmartStore.Web.Infrastructure diff --git a/src/Presentation/SmartStore.Web/Media/Thumbs/placeholder.txt b/src/Presentation/SmartStore.Web/Media/Thumbs/placeholder.txt index 5f282702bb..0eaf54c3d7 100644 --- a/src/Presentation/SmartStore.Web/Media/Thumbs/placeholder.txt +++ b/src/Presentation/SmartStore.Web/Media/Thumbs/placeholder.txt @@ -1 +1 @@ - \ No newline at end of file +placeholder file \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Media/Uploaded/placeholder.txt b/src/Presentation/SmartStore.Web/Media/Uploaded/placeholder.txt index 5f282702bb..0eaf54c3d7 100644 --- a/src/Presentation/SmartStore.Web/Media/Uploaded/placeholder.txt +++ b/src/Presentation/SmartStore.Web/Media/Uploaded/placeholder.txt @@ -1 +1 @@ - \ No newline at end of file +placeholder file \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Blogs/AddBlogCommentModel.cs b/src/Presentation/SmartStore.Web/Models/Blogs/AddBlogCommentModel.cs index 642bb76372..e3208d7ab0 100644 --- a/src/Presentation/SmartStore.Web/Models/Blogs/AddBlogCommentModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Blogs/AddBlogCommentModel.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Blogs { diff --git a/src/Presentation/SmartStore.Web/Models/Blogs/BlogCommentModel.cs b/src/Presentation/SmartStore.Web/Models/Blogs/BlogCommentModel.cs index d7144cb6e0..9de84ec882 100644 --- a/src/Presentation/SmartStore.Web/Models/Blogs/BlogCommentModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Blogs/BlogCommentModel.cs @@ -1,5 +1,5 @@ using System; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Blogs { diff --git a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPagingFilteringModel.cs b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPagingFilteringModel.cs index 052b5938fe..90292d014a 100644 --- a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPagingFilteringModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPagingFilteringModel.cs @@ -1,11 +1,9 @@ using System; -// codehint: sm-delete using SmartStore.Core; namespace SmartStore.Web.Models.Blogs { - // codehint: sm-edit (formerly inherited BasePageableModel) - public partial class BlogPagingFilteringModel : PagedListBase // BasePageableModel + public partial class BlogPagingFilteringModel : PagedListBase { #region Methods diff --git a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostListModel.cs b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostListModel.cs index de22f36422..7487110110 100644 --- a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostListModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostListModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Blogs { diff --git a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostModel.cs b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostModel.cs index 745c2cb1e0..7c8df3e2d6 100644 --- a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostModel.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using FluentValidation.Attributes; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Blogs; namespace SmartStore.Web.Models.Blogs @@ -36,7 +36,6 @@ public BlogPostModel() public IList Comments { get; set; } public AddBlogCommentModel AddNewComment { get; set; } - // codehint: sm-add public int AvatarPictureSize { get; set; } public bool AllowCustomersToUploadAvatars { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostTagListModel.cs b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostTagListModel.cs index 613df18752..7db54f132a 100644 --- a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostTagListModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostTagListModel.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Blogs { diff --git a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostTagModel.cs b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostTagModel.cs index f774e346f7..ddab728568 100644 --- a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostTagModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostTagModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Blogs { diff --git a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostYearMonthModel.cs b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostYearMonthModel.cs index 54b7ed1cfe..89ea5f646d 100644 --- a/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostYearMonthModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Blogs/BlogPostYearMonthModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Blogs { diff --git a/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs b/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs index 034fddec45..c88dc4c6b4 100644 --- a/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Boards/TopicMoveModel.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Web.Mvc; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Boards { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/AddToCompareListModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/AddToCompareListModel.cs index 1bffb79e6e..d493b61c51 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/AddToCompareListModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/AddToCompareListModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/BackInStockSubscribeModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/BackInStockSubscribeModel.cs index 7bcac2f0ba..d403abbf0d 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/BackInStockSubscribeModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/BackInStockSubscribeModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/CatalogPagingFilteringModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/CatalogPagingFilteringModel.cs index 0ab878ee0b..39e3c6be9d 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/CatalogPagingFilteringModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/CatalogPagingFilteringModel.cs @@ -2,19 +2,16 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Services.Catalog; using SmartStore.Services.Localization; -using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; -// codehint: sm-delete +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { - // codehint: sm-edit (formerly inherited BasePageableModel) - public partial class CatalogPagingFilteringModel : PagingFilteringModel //BasePageableModel + + public partial class CatalogPagingFilteringModel : PagingFilteringModel { #region Constructors @@ -74,7 +71,7 @@ protected virtual IList GetPriceRangeList(string priceRangesStr) { string[] fromTo = str1.Trim().Split(new char[] { '-' }); - if (fromTo.Length > 1) { // codehint: sm-edit + if (fromTo.Length > 1) { decimal? from = null; if (!String.IsNullOrEmpty(fromTo[0]) && !String.IsNullOrEmpty(fromTo[0].Trim())) from = decimal.Parse(fromTo[0].Trim(), new CultureInfo("en-US")); diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/CategoryModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/CategoryModel.cs index e078502a7a..a09f815652 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/CategoryModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/CategoryModel.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.UI; using SmartStore.Web.Models.Media; @@ -18,7 +19,9 @@ public CategoryModel() } public string Name { get; set; } + public string FullName { get; set; } public string Description { get; set; } + public string BottomDescription { get; set; } public string MetaKeywords { get; set; } public string MetaDescription { get; set; } public string MetaTitle { get; set; } @@ -32,7 +35,7 @@ public CategoryModel() public IList CategoryBreadcrumb { get; set; } public bool DisplayFilter { get; set; } - public bool ShowSubcategoriesAboveProductLists { get; set; } + public SubCategoryDisplayType SubCategoryDisplayType { get; set; } public IList SubCategories { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/CompareProductsModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/CompareProductsModel.cs index f7661c0956..1b58526ad5 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/CompareProductsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/CompareProductsModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/HomePageBestsellersModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/HomePageBestsellersModel.cs index 3178fc0182..1f6bb34c0e 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/HomePageBestsellersModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/HomePageBestsellersModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/HomePageProductsModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/HomePageProductsModel.cs index 3e206bdae4..e4a7303d2a 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/HomePageProductsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/HomePageProductsModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerModel.cs index 4c9521ba7f..7f8452ad4d 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Media; namespace SmartStore.Web.Models.Catalog diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerNavigationModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerNavigationModel.cs index dcbefd212d..a27b37ba7f 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerNavigationModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerNavigationModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { @@ -13,6 +13,10 @@ public ManufacturerNavigationModel() public IList Manufacturers { get; set; } public int TotalManufacturers { get; set; } + + public bool DisplayManufacturers { get; set; } + + public bool DisplayImages { get; set; } } public partial class ManufacturerBriefInfoModel : EntityModelBase @@ -20,7 +24,9 @@ public partial class ManufacturerBriefInfoModel : EntityModelBase public string Name { get; set; } public string SeName { get; set; } - + + public string PictureUrl { get; set; } + public bool IsActive { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerOverviewModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerOverviewModel.cs index 3715363b32..523526d25e 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerOverviewModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ManufacturerOverviewModel.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Media; namespace SmartStore.Web.Models.Catalog diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/NavigationModelBuiltEvent.cs b/src/Presentation/SmartStore.Web/Models/Catalog/NavigationModelBuiltEvent.cs index 4ba7c59c8b..633a453b1f 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/NavigationModelBuiltEvent.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/NavigationModelBuiltEvent.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using SmartStore.Collections; +using SmartStore.Collections; using SmartStore.Web.Framework.UI; namespace SmartStore.Web.Models.Catalog diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/PagingFilteringModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/PagingFilteringModel.cs index 7879fa6639..b5a339879c 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/PagingFilteringModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/PagingFilteringModel.cs @@ -1,15 +1,11 @@ -// codehint: sm-delete -using System.Collections.Generic; +using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Core; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { - - // codehint: sm-add (whole file) - public partial class ListOptionItem : ModelBase { public string Text { get; set; } @@ -28,13 +24,11 @@ public SelectListItem ToSelectListItem() } } - // codehint: sm-add public partial class PagingFilteringModel : PagedListBase { public PagingFilteringModel() { - // codehint: sm-edit (SelectListItem > ListOptionItem) this.AvailableSortOptions = new List(); this.AvailableViewModes = new List(); this.PageSizeOptions = new List(); diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/PopularProductTagsModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/PopularProductTagsModel.cs index 0efd6badc8..65d46aa59e 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/PopularProductTagsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/PopularProductTagsModel.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductAskQuestionModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductAskQuestionModel.cs index 1d6a96f40f..c8e8facd98 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductAskQuestionModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductAskQuestionModel.cs @@ -1,7 +1,7 @@ using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Catalog; namespace SmartStore.Web.Models.Catalog diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs index 4f092cf362..ab13510f40 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Directory; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.UI; using SmartStore.Web.Models.Media; @@ -16,14 +15,11 @@ public partial class ProductDetailsModel : EntityModelBase public ProductDetailsModel() { - //codehint: sm-edit - //Manufacturers = new List(); Manufacturers = new List(); GiftCard = new GiftCardModel(); ProductPrice = new ProductPriceModel(); AddToCart = new AddToCartModel(); ProductVariantAttributes = new List(); - Combinations = new List(); AssociatedProducts = new List(); BundledItems = new List(); BundleItem = new ProductBundleItemModel(); @@ -102,8 +98,7 @@ public ProductDetailsPictureModel DetailsPictureModel public bool BundlePerItemPricing { get; set; } public bool BundlePerItemShoppingCart { get; set; } - public IList Combinations { get; set; } - public ProductVariantAttributeCombination CombinationSelected { get; set; } + public ProductVariantAttributeCombination SelectedCombination { get; set; } public IList Manufacturers { get; set; } public int ReviewCount { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductEmailAFriendModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductEmailAFriendModel.cs index fe12f24f7c..77054abb8e 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductEmailAFriendModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductEmailAFriendModel.cs @@ -1,7 +1,7 @@ using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Catalog; namespace SmartStore.Web.Models.Catalog diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductOverviewModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductOverviewModel.cs index cfa45be7ec..74d7baecc8 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductOverviewModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductOverviewModel.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; using SmartStore.Web.Models.Media; -using SmartStore.Core; -using SmartStore.Core.Domain.Directory; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { @@ -22,7 +20,7 @@ public ProductOverviewModel() public string Name { get; set; } public string ShortDescription { get; set; } - public string FullDescription { get; set; } + public string FullDescription { get; set; } public string SeName { get; set; } public int ThumbDimension { get; set; } @@ -42,6 +40,7 @@ public ProductOverviewModel() public int TotalReviews { get; set; } public bool ShowReviews { get; set; } public bool ShowDeliveryTimes { get; set; } + public bool InvisibleDeliveryTime { get; set; } public string DeliveryTimeName { get; set; } public string DeliveryTimeHexValue { get; set; } @@ -50,7 +49,10 @@ public ProductOverviewModel() public string StockAvailablity { get; set; } public bool DisplayBasePrice { get; set; } public string BasePriceInfo { get; set; } - public int ProductMinPriceId { get; set; } + /// + /// For internal use + /// + public int MinPriceProductId { get; set; } public bool CompareEnabled { get; set; } public bool IsNew { get; set; } public bool HideBuyButtonInLists { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductReviewModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductReviewModel.cs index 17dd5e7a27..0118abf688 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductReviewModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductReviewModel.cs @@ -2,7 +2,7 @@ using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Catalog; namespace SmartStore.Web.Models.Catalog diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductSpecificationModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductSpecificationModel.cs index 1b4b0c98fb..c6436eb6af 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductSpecificationModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductSpecificationModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductTagModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductTagModel.cs index 155f168818..926a87fa0e 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductTagModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductTagModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductsByTagModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductsByTagModel.cs index a5c5afc51e..4c312cc1d8 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductsByTagModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductsByTagModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/RecentlyAddedProductsModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/RecentlyAddedProductsModel.cs index 5ac18739f5..7c3ef212be 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/RecentlyAddedProductsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/RecentlyAddedProductsModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/SearchBoxModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/SearchBoxModel.cs index 6bcc77a21d..4d784901ee 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/SearchBoxModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/SearchBoxModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/SearchModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/SearchModel.cs index 4c3d9e96cb..91904e47fc 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/SearchModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/SearchModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Catalog { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/SearchPagingFilteringModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/SearchPagingFilteringModel.cs index 54591c656a..086054f61f 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/SearchPagingFilteringModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/SearchPagingFilteringModel.cs @@ -1,14 +1,6 @@ -// codehint: sm-delete -using System.Collections.Generic; -using System.Web.Mvc; -using SmartStore.Core; -using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; - -namespace SmartStore.Web.Models.Catalog +namespace SmartStore.Web.Models.Catalog { - // codehint: sm-edit - public partial class SearchPagingFilteringModel : PagingFilteringModel // BasePageableModel + public partial class SearchPagingFilteringModel : PagingFilteringModel { } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutBillingAddressModel.cs b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutBillingAddressModel.cs index d5e592dca5..bde0e52a42 100644 --- a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutBillingAddressModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutBillingAddressModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Common; namespace SmartStore.Web.Models.Checkout diff --git a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutCompletedModel.cs b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutCompletedModel.cs index 3920514ded..370be66071 100644 --- a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutCompletedModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutCompletedModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Checkout { diff --git a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutConfirmModel.cs b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutConfirmModel.cs index b553051c80..2239a2e298 100644 --- a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutConfirmModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutConfirmModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Checkout { @@ -17,6 +17,7 @@ public CheckoutConfirmModel() public IList Warnings { get; set; } public bool ShowConfirmOrderLegalHint { get; set; } + public bool ShowEsdRevocationWaiverBox { get; set; } public bool BypassPaymentMethodInfo { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutPaymentInfoModel.cs b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutPaymentInfoModel.cs index e08675e71a..2c23a55607 100644 --- a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutPaymentInfoModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutPaymentInfoModel.cs @@ -1,5 +1,5 @@ using System.Web.Routing; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Checkout { diff --git a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutPaymentMethodModel.cs b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutPaymentMethodModel.cs index 585658b0ad..b3eba49ab3 100644 --- a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutPaymentMethodModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutPaymentMethodModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Checkout { @@ -24,12 +24,14 @@ public partial class PaymentMethodModel : ModelBase public string PaymentMethodSystemName { get; set; } public string Name { get; set; } public string Description { get; set; } + public string FullDescription { get; set; } public string BrandUrl { get; set; } public string Fee { get; set; } public bool Selected { get; set; } public RouteInfo PaymentInfoRoute { get; set; } public bool RequiresInteraction { get; set; } } + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutProgressModel.cs b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutProgressModel.cs index 8974f5773d..5ef7e38f32 100644 --- a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutProgressModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutProgressModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Checkout { diff --git a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutShippingAddressModel.cs b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutShippingAddressModel.cs index 5a4cac101f..eae1a17753 100644 --- a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutShippingAddressModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutShippingAddressModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Common; namespace SmartStore.Web.Models.Checkout diff --git a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutShippingMethodModel.cs b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutShippingMethodModel.cs index bb81c51809..a1104b942f 100644 --- a/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutShippingMethodModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Checkout/CheckoutShippingMethodModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Checkout { @@ -19,6 +19,7 @@ public CheckoutShippingMethodModel() public partial class ShippingMethodModel : ModelBase { + public int ShippingMethodId { get; set; } public string ShippingRateComputationMethodSystemName { get; set; } public string Name { get; set; } public string BrandUrl { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/CheckoutBillingAddressModel.cs b/src/Presentation/SmartStore.Web/Models/CheckoutBillingAddressModel.cs new file mode 100644 index 0000000000..d07e120dff --- /dev/null +++ b/src/Presentation/SmartStore.Web/Models/CheckoutBillingAddressModel.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Models.Common; + +namespace SmartStore.Web.Models.Checkout +{ + public partial class CheckoutBillingAddressModel2 : ModelBase + { + public CheckoutBillingAddressModel2() + { + ExistingAddresses = new List(); + NewAddress = new AddressModel(); + } + + public IList ExistingAddresses { get; set; } + + public AddressModel NewAddress { get; set; } + + /// + /// Used on one-page checkout page + /// + public bool NewAddressPreselected { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Common/AccountDropdownModel.cs b/src/Presentation/SmartStore.Web/Models/Common/AccountDropdownModel.cs index 445ff6fc39..46686d6cf9 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/AccountDropdownModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/AccountDropdownModel.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Common/AddressModel.cs b/src/Presentation/SmartStore.Web/Models/Common/AddressModel.cs index 5afdc02f9b..a393eb0873 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/AddressModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/AddressModel.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Common; namespace SmartStore.Web.Models.Common { - [Validator(typeof(AddressValidator))] + [Validator(typeof(AddressValidator))] public partial class AddressModel : EntityModelBase { public AddressModel() @@ -26,9 +27,12 @@ public AddressModel() [SmartResourceDisplayName("Address.Fields.Email")] [AllowHtml] - public string Email { get; set; } + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + [SmartResourceDisplayName("Address.Fields.EmailMatch")] - public string EmailMatch { get; set; } + [DataType(DataType.EmailAddress)] + public string EmailMatch { get; set; } public bool ValidateEmailAddress { get; set; } [SmartResourceDisplayName("Address.Fields.Company")] @@ -79,13 +83,15 @@ public AddressModel() [SmartResourceDisplayName("Address.Fields.PhoneNumber")] [AllowHtml] - public string PhoneNumber { get; set; } + [DataType(DataType.PhoneNumber)] + public string PhoneNumber { get; set; } public bool PhoneEnabled { get; set; } public bool PhoneRequired { get; set; } [SmartResourceDisplayName("Address.Fields.FaxNumber")] [AllowHtml] - public string FaxNumber { get; set; } + [DataType(DataType.PhoneNumber)] + public string FaxNumber { get; set; } public bool FaxEnabled { get; set; } public bool FaxRequired { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Common/CompareDropdownModel.cs b/src/Presentation/SmartStore.Web/Models/Common/CompareDropdownModel.cs index 8e4c279077..d8d32376fc 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/CompareDropdownModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/CompareDropdownModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs b/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs index 4965525cea..db9ab838bc 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs @@ -1,17 +1,24 @@ -using System.Web.Mvc; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Common; namespace SmartStore.Web.Models.Common { - [Validator(typeof(ContactUsValidator))] + [Validator(typeof(ContactUsValidator))] public partial class ContactUsModel : ModelBase { + [SmartResourceDisplayName("ContactUs.PrivacyAgreement")] + public bool PrivacyAgreement { get; set; } + + public bool DisplayPrivacyAgreement { get; set; } + [AllowHtml] [SmartResourceDisplayName("ContactUs.Email")] - public string Email { get; set; } + [DataType(DataType.EmailAddress)] + public string Email { get; set; } [AllowHtml] [SmartResourceDisplayName("ContactUs.Enquiry")] diff --git a/src/Presentation/SmartStore.Web/Models/Common/CurrencyModel.cs b/src/Presentation/SmartStore.Web/Models/Common/CurrencyModel.cs index 730a2ee0fb..f9048e5d37 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/CurrencyModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/CurrencyModel.cs @@ -1,12 +1,10 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { public partial class CurrencyModel : EntityModelBase { public string Name { get; set; } - - // codehint: sm-add public string ISOCode { get; set; } public string Symbol { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Models/Common/CurrencySelectorModel.cs b/src/Presentation/SmartStore.Web/Models/Common/CurrencySelectorModel.cs index 7c5b6e0bab..ec2ff8e876 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/CurrencySelectorModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/CurrencySelectorModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Common/EntityPickerModel.cs b/src/Presentation/SmartStore.Web/Models/Common/EntityPickerModel.cs new file mode 100644 index 0000000000..c83422b7f2 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Models/Common/EntityPickerModel.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Web.Models.Common +{ + public class EntityPickerModel : ModelBase + { + public string AllString { get; set; } + public string PublishedString { get; set; } + public string UnpublishedString { get; set; } + + public string Entity { get; set; } + public bool HighligtSearchTerm { get; set; } + public string DisableIf { get; set; } + public string DisableIds { get; set; } + public string SearchTerm { get; set; } + public string ReturnField { get; set; } + public int MaxReturnValues { get; set; } + public int PageIndex { get; set; } + public int PageSize { get; set; } + + public List SearchResult { get; set; } + + #region Products + + [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] + public string ProductName { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] + public int CategoryId { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] + public int ManufacturerId { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] + public int StoreId { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] + public int ProductTypeId { get; set; } + + public IList AvailableCategories { get; set; } + public IList AvailableManufacturers { get; set; } + public IList AvailableStores { get; set; } + public IList AvailableProductTypes { get; set; } + + #endregion + + public class SearchResultModel : EntityModelBase + { + public string ReturnValue { get; set; } + public string Title { get; set; } + public string Summary { get; set; } + public string SummaryTitle { get; set; } + public bool? Published { get; set; } + public bool Disable { get; set; } + public string ImageUrl { get; set; } + public string LabelText { get; set; } + public string LabelClassName { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Common/FooterModel.cs b/src/Presentation/SmartStore.Web/Models/Common/FooterModel.cs index 67aea71c20..a6911f9e67 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/FooterModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/FooterModel.cs @@ -1,15 +1,10 @@ -using SmartStore.Web.Framework.Mvc; -using System.Collections.Generic; +using System.Collections.Generic; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { public partial class FooterModel : ModelBase { - public FooterModel() - { - Topics = new Dictionary(); - } - public string StoreName { get; set; } public string LegalInfo { get; set; } @@ -20,7 +15,6 @@ public FooterModel() public bool HideNewsletterBlock { get; set; } public bool BlogEnabled { get; set; } public bool ForumEnabled { get; set; } - public Dictionary Topics { get; set; } public bool ShowSocialLinks { get; set; } public string FacebookLink { get; set; } @@ -28,5 +22,7 @@ public FooterModel() public string TwitterLink { get; set; } public string PinterestLink { get; set; } public string YoutubeLink { get; set; } - } + + public Dictionary TopicPageUrls { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Common/HeaderLinksModel.cs b/src/Presentation/SmartStore.Web/Models/Common/HeaderLinksModel.cs index f1eb44279c..a3a794535d 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/HeaderLinksModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/HeaderLinksModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Common/InfoBlockModel.cs b/src/Presentation/SmartStore.Web/Models/Common/InfoBlockModel.cs index 872c97d1d6..5678d3938b 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/InfoBlockModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/InfoBlockModel.cs @@ -1,4 +1,5 @@ -using SmartStore.Web.Framework.Mvc; +using System.Collections.Generic; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { @@ -11,5 +12,7 @@ public partial class InfoBlockModel : ModelBase public bool SitemapEnabled { get; set; } public bool ForumEnabled { get; set; } public bool AllowPrivateMessages { get; set; } - } + + public Dictionary TopicPageUrls { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Common/LanguageModel.cs b/src/Presentation/SmartStore.Web/Models/Common/LanguageModel.cs index f78b449f5b..fa5d5a0590 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/LanguageModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/LanguageModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Common/LanguageSelectorModel.cs b/src/Presentation/SmartStore.Web/Models/Common/LanguageSelectorModel.cs index 0b26742a2c..a869b91166 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/LanguageSelectorModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/LanguageSelectorModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Common/MenuModel.cs b/src/Presentation/SmartStore.Web/Models/Common/MenuModel.cs index 17680fb249..c906dc18ea 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/MenuModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/MenuModel.cs @@ -1,9 +1,10 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { public partial class MenuModel : ModelBase { + public bool NewsEnabled { get; set; } public bool BlogEnabled { get; set; } public bool RecentlyAddedProductsEnabled { get; set; } public bool ForumEnabled { get; set; } @@ -12,8 +13,11 @@ public partial class MenuModel : ModelBase public int UnreadPrivateMessages { get; set; } public bool IsAuthenticated { get; set; } - public bool DisplayAdminLink { get; set; } + public bool DisplayLoginLink { get; set; } + public bool DisplayAdminLink { get; set; } public bool IsCustomerImpersonated { get; set; } public string CustomerEmailUsername { get; set; } - } + + public bool HasContactUsPage { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Common/PagerModel.cs b/src/Presentation/SmartStore.Web/Models/Common/PagerModel.cs index bc416d03be..a55ba04714 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/PagerModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/PagerModel.cs @@ -1,10 +1,5 @@ -using SmartStore.Core.Infrastructure; -using SmartStore.Services.Localization; - -namespace SmartStore.Web.Models.Common +namespace SmartStore.Web.Models.Common { - //codehint: sm-delete (whole PagerModel class) - #region Classes /// diff --git a/src/Presentation/SmartStore.Web/Models/Common/PaymentInfoModel.cs b/src/Presentation/SmartStore.Web/Models/Common/PaymentInfoModel.cs index 3a8460e97f..f550571863 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/PaymentInfoModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/PaymentInfoModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Catalog; using SmartStore.Web.Models.Topics; diff --git a/src/Presentation/SmartStore.Web/Models/Common/ShipmentInfoModel.cs b/src/Presentation/SmartStore.Web/Models/Common/ShipmentInfoModel.cs index b36a289de5..0567d5606a 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/ShipmentInfoModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/ShipmentInfoModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Catalog; using SmartStore.Web.Models.Topics; diff --git a/src/Presentation/SmartStore.Web/Models/Common/ShopBarModel.cs b/src/Presentation/SmartStore.Web/Models/Common/ShopBarModel.cs index 85a41134de..e517dc9c6c 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/ShopBarModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/ShopBarModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Common/ShopHeaderModel.cs b/src/Presentation/SmartStore.Web/Models/Common/ShopHeaderModel.cs index 4aa61f8404..f52360b057 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/ShopHeaderModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/ShopHeaderModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Common/ShopLogoModel.cs b/src/Presentation/SmartStore.Web/Models/Common/ShopLogoModel.cs index 34daebea13..10e4f6e8a4 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/ShopLogoModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/ShopLogoModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Common/SitemapModel.cs b/src/Presentation/SmartStore.Web/Models/Common/SitemapModel.cs index e32142a5ab..0f24e7c67b 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/SitemapModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/SitemapModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Catalog; using SmartStore.Web.Models.Topics; diff --git a/src/Presentation/SmartStore.Web/Models/Common/StoreThemeModel.cs b/src/Presentation/SmartStore.Web/Models/Common/StoreThemeModel.cs index d7472f77f8..e2d777357f 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/StoreThemeModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/StoreThemeModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Common/StoreThemeSelectorModel.cs b/src/Presentation/SmartStore.Web/Models/Common/StoreThemeSelectorModel.cs index f842b79886..51f428e3b9 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/StoreThemeSelectorModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/StoreThemeSelectorModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Common/TaxTypeSelectorModel.cs b/src/Presentation/SmartStore.Web/Models/Common/TaxTypeSelectorModel.cs index 12e6fb41ea..a28efcbc24 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/TaxTypeSelectorModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/TaxTypeSelectorModel.cs @@ -1,5 +1,5 @@ using SmartStore.Core.Domain.Tax; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Common { diff --git a/src/Presentation/SmartStore.Web/Models/Customer/AccountActivationModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/AccountActivationModel.cs index 1405143edc..16596efe64 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/AccountActivationModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/AccountActivationModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { diff --git a/src/Presentation/SmartStore.Web/Models/Customer/BackInStockSubscriptionModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/BackInStockSubscriptionModel.cs index 7709196b2a..2248c1942a 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/BackInStockSubscriptionModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/BackInStockSubscriptionModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { diff --git a/src/Presentation/SmartStore.Web/Models/Customer/ChangePasswordModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/ChangePasswordModel.cs index 4c7eacc63f..cb580e2105 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/ChangePasswordModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/ChangePasswordModel.cs @@ -2,7 +2,7 @@ using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Customer; namespace SmartStore.Web.Models.Customer diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerAddressEditModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerAddressEditModel.cs index e5805e1d0d..370286d602 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerAddressEditModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerAddressEditModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Common; namespace SmartStore.Web.Models.Customer diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerAddressListModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerAddressListModel.cs index 6c405db530..761ad41556 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerAddressListModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerAddressListModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Common; namespace SmartStore.Web.Models.Customer diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerAvatarModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerAvatarModel.cs index 09e79682b4..2aeedc3e16 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerAvatarModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerAvatarModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerBackInStockSubscriptionsModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerBackInStockSubscriptionsModel.cs index c85eada465..1087058c29 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerBackInStockSubscriptionsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerBackInStockSubscriptionsModel.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using SmartStore.Core; -using SmartStore.Web.Models.Common; namespace SmartStore.Web.Models.Customer { diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerDownloadableProductsModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerDownloadableProductsModel.cs index 346e102504..77fffc7a39 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerDownloadableProductsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerDownloadableProductsModel.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { @@ -15,6 +15,7 @@ public CustomerDownloadableProductsModel() public CustomerNavigationModel NavigationModel { get; set; } #region Nested classes + public partial class DownloadableProductsModel : ModelBase { public Guid OrderItemGuid { get; set; } @@ -24,6 +25,7 @@ public partial class DownloadableProductsModel : ModelBase public int ProductId { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } public string ProductAttributes { get; set; } public int DownloadId { get; set; } @@ -31,6 +33,7 @@ public partial class DownloadableProductsModel : ModelBase public DateTime CreatedOn { get; set; } } + #endregion } diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerForumSubscriptionsModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerForumSubscriptionsModel.cs index d1d741cb24..bab1809345 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerForumSubscriptionsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerForumSubscriptionsModel.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using SmartStore.Core; -using SmartStore.Web.Models.Common; namespace SmartStore.Web.Models.Customer { diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerInfoModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerInfoModel.cs index ed98738e60..80e9ddafef 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerInfoModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerInfoModel.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Customer; namespace SmartStore.Web.Models.Customer { - [Validator(typeof(CustomerInfoValidator))] + [Validator(typeof(CustomerInfoValidator))] public partial class CustomerInfoModel : ModelBase { public CustomerInfoModel() @@ -20,7 +21,14 @@ public CustomerInfoModel() [SmartResourceDisplayName("Account.Fields.Email")] [AllowHtml] - public string Email { get; set; } + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + + [SmartResourceDisplayName("Account.Fields.CustomerNumber")] + [AllowHtml] + public string CustomerNumber { get; set; } + public bool CustomerNumberEnabled { get; set; } + public bool DisplayCustomerNumber { get; set; } public bool CheckUsernameAvailabilityEnabled { get; set; } public bool AllowUsersToChangeUsernames { get; set; } @@ -94,13 +102,15 @@ public CustomerInfoModel() public bool PhoneRequired { get; set; } [SmartResourceDisplayName("Account.Fields.Phone")] [AllowHtml] - public string Phone { get; set; } + [DataType(DataType.PhoneNumber)] + public string Phone { get; set; } public bool FaxEnabled { get; set; } public bool FaxRequired { get; set; } [SmartResourceDisplayName("Account.Fields.Fax")] [AllowHtml] - public string Fax { get; set; } + [DataType(DataType.PhoneNumber)] + public string Fax { get; set; } public bool NewsletterEnabled { get; set; } [SmartResourceDisplayName("Account.Fields.Newsletter")] diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerNavigationModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerNavigationModel.cs index 533025a128..aea5297338 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerNavigationModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerNavigationModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs index c3e278616e..eebb536d65 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs @@ -1,19 +1,19 @@ using System; using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Core; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { - public partial class CustomerOrderListModel : ModelBase + public partial class CustomerOrderListModel : ModelBase { public CustomerOrderListModel() { - Orders = new List(); - RecurringOrders = new List(); - CancelRecurringPaymentErrors = new List(); + RecurringOrders = new List(); + CancelRecurringPaymentErrors = new List(); } - public IList Orders { get; set; } + public PagedList Orders { get; set; } public IList RecurringOrders { get; set; } public IList CancelRecurringPaymentErrors { get; set; } @@ -21,6 +21,7 @@ public CustomerOrderListModel() #region Nested classes + public partial class OrderDetailsModel : EntityModelBase { public string OrderNumber { get; set; } @@ -29,6 +30,7 @@ public partial class OrderDetailsModel : EntityModelBase public string OrderStatus { get; set; } public DateTime CreatedOn { get; set; } } + public partial class RecurringOrderModel : EntityModelBase { public string StartDate { get; set; } @@ -39,6 +41,7 @@ public partial class RecurringOrderModel : EntityModelBase public int InitialOrderId { get; set; } public bool CanCancel { get; set; } } + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerReturnRequestsModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerReturnRequestsModel.cs index d41eb9aefa..0e302ec53e 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerReturnRequestsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerReturnRequestsModel.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { @@ -15,12 +15,14 @@ public CustomerReturnRequestsModel() public CustomerNavigationModel NavigationModel { get; set; } #region Nested classes + public partial class ReturnRequestModel : EntityModelBase { public string ReturnRequestStatus { get; set; } public int ProductId { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } public int Quantity { get; set; } public string ReturnReason { get; set; } @@ -29,6 +31,7 @@ public partial class ReturnRequestModel : EntityModelBase public DateTime CreatedOn { get; set; } } + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerRewardPointsModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerRewardPointsModel.cs index dd49358347..c37089ddae 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerRewardPointsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerRewardPointsModel.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { @@ -28,7 +28,7 @@ public partial class RewardPointsHistoryModel : EntityModelBase [SmartResourceDisplayName("RewardPoints.Fields.Message")] public string Message { get; set; } - [SmartResourceDisplayName("RewardPoints.Fields.Date")] + [SmartResourceDisplayName("Common.CreatedOn")] public DateTime CreatedOn { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Models/Customer/ExternalAuthenticationMethodModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/ExternalAuthenticationMethodModel.cs index d85169b975..c9a81bc26b 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/ExternalAuthenticationMethodModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/ExternalAuthenticationMethodModel.cs @@ -1,5 +1,5 @@ using System.Web.Routing; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { diff --git a/src/Presentation/SmartStore.Web/Models/Customer/ForumSubscriptionModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/ForumSubscriptionModel.cs index 499092c518..a899d2cb22 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/ForumSubscriptionModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/ForumSubscriptionModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { diff --git a/src/Presentation/SmartStore.Web/Models/Customer/LoginModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/LoginModel.cs index 12f55b6a38..c99aaf03e2 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/LoginModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/LoginModel.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { diff --git a/src/Presentation/SmartStore.Web/Models/Customer/PasswordRecoveryConfirmModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/PasswordRecoveryConfirmModel.cs index ed22787350..a31e5d72c1 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/PasswordRecoveryConfirmModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/PasswordRecoveryConfirmModel.cs @@ -2,7 +2,7 @@ using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Customer; namespace SmartStore.Web.Models.Customer diff --git a/src/Presentation/SmartStore.Web/Models/Customer/PaswordRecoveryModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/PaswordRecoveryModel.cs index cea324b284..b8581117d4 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/PaswordRecoveryModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/PaswordRecoveryModel.cs @@ -1,17 +1,19 @@ -using System.Web.Mvc; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Customer; namespace SmartStore.Web.Models.Customer { - [Validator(typeof(PasswordRecoveryValidator))] + [Validator(typeof(PasswordRecoveryValidator))] public partial class PasswordRecoveryModel : ModelBase { [AllowHtml] [SmartResourceDisplayName("Account.PasswordRecovery.Email")] - public string Email { get; set; } + [DataType(DataType.EmailAddress)] + public string Email { get; set; } public string Result { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Models/Customer/RegisterModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/RegisterModel.cs index 2a9e1b44a4..4d1a445e2d 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/RegisterModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/RegisterModel.cs @@ -3,7 +3,7 @@ using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Customer; namespace SmartStore.Web.Models.Customer @@ -20,7 +20,8 @@ public RegisterModel() [SmartResourceDisplayName("Account.Fields.Email")] [AllowHtml] - public string Email { get; set; } + [DataType(DataType.EmailAddress)] + public string Email { get; set; } public bool UsernamesEnabled { get; set; } [SmartResourceDisplayName("Account.Fields.Username")] @@ -104,13 +105,15 @@ public RegisterModel() public bool PhoneRequired { get; set; } [SmartResourceDisplayName("Account.Fields.Phone")] [AllowHtml] - public string Phone { get; set; } + [DataType(DataType.PhoneNumber)] + public string Phone { get; set; } public bool FaxEnabled { get; set; } public bool FaxRequired { get; set; } [SmartResourceDisplayName("Account.Fields.Fax")] [AllowHtml] - public string Fax { get; set; } + [DataType(DataType.PhoneNumber)] + public string Fax { get; set; } public bool NewsletterEnabled { get; set; } [SmartResourceDisplayName("Account.Fields.Newsletter")] @@ -127,6 +130,7 @@ public RegisterModel() public string VatNumber { get; set; } public string VatNumberStatusNote { get; set; } public bool DisplayVatNumber { get; set; } + public bool VatRequired { get; set; } public bool DisplayCaptcha { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Models/Customer/RegisterResultModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/RegisterResultModel.cs index 6942031829..9330b13339 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/RegisterResultModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/RegisterResultModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { diff --git a/src/Presentation/SmartStore.Web/Models/Filter/ProductFilterModel.cs b/src/Presentation/SmartStore.Web/Models/Filter/ProductFilterModel.cs index ba8a02bac5..a53bd91670 100644 --- a/src/Presentation/SmartStore.Web/Models/Filter/ProductFilterModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Filter/ProductFilterModel.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using SmartStore.Web.Framework.Mvc; -using SmartStore.Services.Filter; +using SmartStore.Services.Filter; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Filter { diff --git a/src/Presentation/SmartStore.Web/Models/Install/InstallModel.cs b/src/Presentation/SmartStore.Web/Models/Install/InstallModel.cs index c0556266fa..c50cac8d06 100644 --- a/src/Presentation/SmartStore.Web/Models/Install/InstallModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Install/InstallModel.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.Install; namespace SmartStore.Web.Models.Install @@ -13,7 +13,6 @@ public partial class InstallModel : ModelBase public InstallModel() { this.AvailableLanguages = new List(); - // codehint: sm-add this.AvailableAppLanguages = new List(); this.AvailableMediaStorages = new List(); } @@ -53,7 +52,6 @@ public InstallModel() public List AvailableLanguages { get; set; } - // codehint: sm-add public string PrimaryLanguage { get; set; } public List AvailableAppLanguages { get; set; } public string MediaStorage { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Media/PictureModel.cs b/src/Presentation/SmartStore.Web/Models/Media/PictureModel.cs index 8c4b4c48d6..352676256c 100644 --- a/src/Presentation/SmartStore.Web/Models/Media/PictureModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Media/PictureModel.cs @@ -1,13 +1,12 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Media { public partial class PictureModel : ModelBase { - // codehint: sm-add public int PictureId { get; set; } - public string ThumbImageUrl { get; set; } // codehint: sm-add + public string ThumbImageUrl { get; set; } public string ImageUrl { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/News/AddNewsCommentModel.cs b/src/Presentation/SmartStore.Web/Models/News/AddNewsCommentModel.cs index a990d4ba86..8fd2495661 100644 --- a/src/Presentation/SmartStore.Web/Models/News/AddNewsCommentModel.cs +++ b/src/Presentation/SmartStore.Web/Models/News/AddNewsCommentModel.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.News { diff --git a/src/Presentation/SmartStore.Web/Models/News/HomePageNewsItemsModel.cs b/src/Presentation/SmartStore.Web/Models/News/HomePageNewsItemsModel.cs index 4439f524f3..2462025a1d 100644 --- a/src/Presentation/SmartStore.Web/Models/News/HomePageNewsItemsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/News/HomePageNewsItemsModel.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.News { diff --git a/src/Presentation/SmartStore.Web/Models/News/NewsCommentModel.cs b/src/Presentation/SmartStore.Web/Models/News/NewsCommentModel.cs index d6ca5c0f54..0be2e50419 100644 --- a/src/Presentation/SmartStore.Web/Models/News/NewsCommentModel.cs +++ b/src/Presentation/SmartStore.Web/Models/News/NewsCommentModel.cs @@ -1,5 +1,5 @@ using System; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.News { diff --git a/src/Presentation/SmartStore.Web/Models/News/NewsItemListModel.cs b/src/Presentation/SmartStore.Web/Models/News/NewsItemListModel.cs index ca2afa0f8e..ff5ba23165 100644 --- a/src/Presentation/SmartStore.Web/Models/News/NewsItemListModel.cs +++ b/src/Presentation/SmartStore.Web/Models/News/NewsItemListModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.News { diff --git a/src/Presentation/SmartStore.Web/Models/News/NewsItemModel.cs b/src/Presentation/SmartStore.Web/Models/News/NewsItemModel.cs index 3e4eb262e4..3870d06898 100644 --- a/src/Presentation/SmartStore.Web/Models/News/NewsItemModel.cs +++ b/src/Presentation/SmartStore.Web/Models/News/NewsItemModel.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using FluentValidation.Attributes; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.News; namespace SmartStore.Web.Models.News @@ -34,7 +34,6 @@ public NewsItemModel() public IList Comments { get; set; } public AddNewsCommentModel AddNewComment { get; set; } - // codehint: sm-add public int AvatarPictureSize { get; set; } public bool AllowCustomersToUploadAvatars { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Models/News/NewsPagingFilteringModel.cs b/src/Presentation/SmartStore.Web/Models/News/NewsPagingFilteringModel.cs index 326dc22682..e5ffffbacd 100644 --- a/src/Presentation/SmartStore.Web/Models/News/NewsPagingFilteringModel.cs +++ b/src/Presentation/SmartStore.Web/Models/News/NewsPagingFilteringModel.cs @@ -1,9 +1,7 @@ using SmartStore.Core; -// codehint: sm-delete namespace SmartStore.Web.Models.News { - // codehint: sm-edit public partial class NewsPagingFilteringModel : PagedListBase { } diff --git a/src/Presentation/SmartStore.Web/Models/Newsletter/NewsletterBoxModel.cs b/src/Presentation/SmartStore.Web/Models/Newsletter/NewsletterBoxModel.cs index d74ee82698..d0a54a2571 100644 --- a/src/Presentation/SmartStore.Web/Models/Newsletter/NewsletterBoxModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Newsletter/NewsletterBoxModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Newsletter { diff --git a/src/Presentation/SmartStore.Web/Models/Newsletter/SubscriptionActivationModel.cs b/src/Presentation/SmartStore.Web/Models/Newsletter/SubscriptionActivationModel.cs index 3494a8dbdf..582c127a29 100644 --- a/src/Presentation/SmartStore.Web/Models/Newsletter/SubscriptionActivationModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Newsletter/SubscriptionActivationModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Newsletter { diff --git a/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs b/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs index 5a79ee2706..c4b4773f3a 100644 --- a/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Common; namespace SmartStore.Web.Models.Order @@ -89,6 +89,7 @@ public OrderItemModel() public int ProductId { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } public ProductType ProductType { get; set; } public string UnitPrice { get; set; } public string SubTotal { get; set; } @@ -106,6 +107,7 @@ public partial class BundleItemModel : ModelBase public string Sku { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } public bool VisibleIndividually { get; set; } public int Quantity { get; set; } public int DisplayOrder { get; set; } @@ -124,7 +126,8 @@ public partial class GiftCard : ModelBase { public string CouponCode { get; set; } public string Amount { get; set; } - } + public string Remaining { get; set; } + } public partial class OrderNote : ModelBase { diff --git a/src/Presentation/SmartStore.Web/Models/Order/ShipmentDetailsModel.cs b/src/Presentation/SmartStore.Web/Models/Order/ShipmentDetailsModel.cs index 1ffdd4270e..c935a596f1 100644 --- a/src/Presentation/SmartStore.Web/Models/Order/ShipmentDetailsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Order/ShipmentDetailsModel.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Order { @@ -30,6 +30,7 @@ public partial class ShipmentItemModel : EntityModelBase public int ProductId { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } public string AttributeInfo { get; set; } public int QuantityOrdered { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Order/SubmitReturnRequestModel.cs b/src/Presentation/SmartStore.Web/Models/Order/SubmitReturnRequestModel.cs index 7592a3c785..5e5e837a55 100644 --- a/src/Presentation/SmartStore.Web/Models/Order/SubmitReturnRequestModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Order/SubmitReturnRequestModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Order { @@ -44,6 +44,8 @@ public partial class OrderItemModel : EntityModelBase public string ProductSeName { get; set; } + public string ProductUrl { get; set; } + public string AttributeInfo { get; set; } public string UnitPrice { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Polls/PollModel.cs b/src/Presentation/SmartStore.Web/Models/Polls/PollModel.cs index 0475127251..f4fa173939 100644 --- a/src/Presentation/SmartStore.Web/Models/Polls/PollModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Polls/PollModel.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Polls { diff --git a/src/Presentation/SmartStore.Web/Models/PrivateMessages/PrivateMessageListModel.cs b/src/Presentation/SmartStore.Web/Models/PrivateMessages/PrivateMessageListModel.cs index 07e7275191..ebe3930685 100644 --- a/src/Presentation/SmartStore.Web/Models/PrivateMessages/PrivateMessageListModel.cs +++ b/src/Presentation/SmartStore.Web/Models/PrivateMessages/PrivateMessageListModel.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using SmartStore.Core; -using SmartStore.Web.Models.Common; namespace SmartStore.Web.Models.PrivateMessages { diff --git a/src/Presentation/SmartStore.Web/Models/PrivateMessages/PrivateMessageModel.cs b/src/Presentation/SmartStore.Web/Models/PrivateMessages/PrivateMessageModel.cs index a383d89518..f30fc6ea1f 100644 --- a/src/Presentation/SmartStore.Web/Models/PrivateMessages/PrivateMessageModel.cs +++ b/src/Presentation/SmartStore.Web/Models/PrivateMessages/PrivateMessageModel.cs @@ -1,6 +1,6 @@ using System; using FluentValidation.Attributes; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.PrivateMessages; namespace SmartStore.Web.Models.PrivateMessages diff --git a/src/Presentation/SmartStore.Web/Models/PrivateMessages/SendPrivateMessageModel.cs b/src/Presentation/SmartStore.Web/Models/PrivateMessages/SendPrivateMessageModel.cs index da8fdf3241..0736872b33 100644 --- a/src/Presentation/SmartStore.Web/Models/PrivateMessages/SendPrivateMessageModel.cs +++ b/src/Presentation/SmartStore.Web/Models/PrivateMessages/SendPrivateMessageModel.cs @@ -1,6 +1,6 @@ using System.Web.Mvc; using FluentValidation.Attributes; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.PrivateMessages; namespace SmartStore.Web.Models.PrivateMessages diff --git a/src/Presentation/SmartStore.Web/Models/Profile/ProfilePostsModel.cs b/src/Presentation/SmartStore.Web/Models/Profile/ProfilePostsModel.cs index 6c8b5fb70e..be4da001b8 100644 --- a/src/Presentation/SmartStore.Web/Models/Profile/ProfilePostsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Profile/ProfilePostsModel.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using SmartStore.Core; -using SmartStore.Web.Models.Common; namespace SmartStore.Web.Models.Profile { diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ButtonPaymentMethodModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ButtonPaymentMethodModel.cs index 8949d991a4..1282a50501 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ButtonPaymentMethodModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ButtonPaymentMethodModel.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.ShoppingCart { diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/EstimateShippingModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/EstimateShippingModel.cs index aed18dadf1..d0f241a1e1 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/EstimateShippingModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/EstimateShippingModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.ShoppingCart { @@ -18,6 +18,8 @@ public EstimateShippingModel() public bool Enabled { get; set; } + public string ShippingInfoUrl { get; set; } + public IList ShippingOptions { get; set; } public IList Warnings { get; set; } @@ -36,6 +38,8 @@ public EstimateShippingModel() public partial class ShippingOptionModel : ModelBase { + public int ShippingMethodId { get; set; } + public string Name { get; set; } public string Description { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/MiniShoppingCartModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/MiniShoppingCartModel.cs index d7093304de..6f69774664 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/MiniShoppingCartModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/MiniShoppingCartModel.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Media; namespace SmartStore.Web.Models.ShoppingCart @@ -40,6 +39,8 @@ public ShoppingCartItemModel() public string ProductSeName { get; set; } + public string ProductUrl { get; set; } + public int Quantity { get; set; } public string UnitPrice { get; set; } @@ -57,6 +58,7 @@ public partial class ShoppingCartItemBundleItem : ModelBase public string PictureUrl { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } } #endregion diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/OrderTotalsModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/OrderTotalsModel.cs index c5fefda507..b1f0826ee6 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/OrderTotalsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/OrderTotalsModel.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.ShoppingCart { diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs index 6416310328..e8c08f9810 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Core.Domain.Catalog; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Core.Domain.Orders; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Common; using SmartStore.Web.Models.Media; @@ -50,10 +51,20 @@ public ShoppingCartModel() public bool DisplayCommentBox { get; set; } public string CustomerComment { get; set; } + public string MeasureUnitName { get; set; } + + public CheckoutNewsLetterSubscription NewsLetterSubscription { get; set; } + public bool? SubscribeToNewsLetter { get; set; } + + public CheckoutThirdPartyEmailHandOver ThirdPartyEmailHandOver { get; set; } + public string ThirdPartyEmailHandOverLabel { get; set; } + public bool? AcceptThirdPartyEmailHandOver { get; set; } + + public bool DisplayEsdRevocationWaiverBox { get; set; } #region Nested Classes - public partial class ShoppingCartItemModel : EntityModelBase + public partial class ShoppingCartItemModel : EntityModelBase { public ShoppingCartItemModel() { @@ -73,6 +84,8 @@ public ShoppingCartItemModel() public string ProductSeName { get; set; } + public string ProductUrl { get; set; } + public bool VisibleIndividually { get; set; } public ProductType ProductType { get; set; } @@ -106,6 +119,11 @@ public ShoppingCartItemModel() public string BasePrice { get; set; } + public bool IsDownload { get; set; } + public bool HasUserAgreement { get; set; } + + public bool IsEsd { get; set; } + public bool BundlePerItemPricing { get; set; } public bool BundlePerItemShoppingCart { get; set; } public BundleItemModel BundleItem { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistEmailAFriendModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistEmailAFriendModel.cs index aa515a6d0b..4c87a4991b 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistEmailAFriendModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistEmailAFriendModel.cs @@ -1,7 +1,7 @@ using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Validators.ShoppingCart; namespace SmartStore.Web.Models.ShoppingCart diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistModel.cs index a38cc246df..2ceba4c312 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistModel.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Core.Domain.Catalog; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Media; namespace SmartStore.Web.Models.ShoppingCart @@ -32,12 +32,12 @@ public WishlistModel() public IList Warnings { get; set; } - // codehint: sm-add public int ThumbSize { get; set; } public int BundleThumbSize { get; set; } public int IgnoredProductsCount { get; set; } public bool DisplayShortDesc { get; set; } public bool ShowProductBundleImages { get; set; } + public bool ShowItemsFromWishlistToCartButton { get; set; } #region Nested Classes @@ -51,6 +51,7 @@ public ShoppingCartItemModel() ChildItems = new List(); BundleItem = new BundleItemModel(); } + public string Sku { get; set; } public PictureModel Picture {get;set;} @@ -61,6 +62,8 @@ public ShoppingCartItemModel() public string ProductSeName { get; set; } + public string ProductUrl { get; set; } + public bool VisibleIndividually { get; set; } public ProductType ProductType { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Topics/TopicModel.cs b/src/Presentation/SmartStore.Web/Models/Topics/TopicModel.cs index 0b226b4318..96548a4559 100644 --- a/src/Presentation/SmartStore.Web/Models/Topics/TopicModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Topics/TopicModel.cs @@ -1,4 +1,4 @@ -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Topics { @@ -21,5 +21,7 @@ public partial class TopicModel : EntityModelBase public string MetaTitle { get; set; } public string TitleTag { get; set; } - } + + public bool RenderAsWidget { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Topics/TopicWidgetModel.cs b/src/Presentation/SmartStore.Web/Models/Topics/TopicWidgetModel.cs index a0567bcef5..07d465763c 100644 --- a/src/Presentation/SmartStore.Web/Models/Topics/TopicWidgetModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Topics/TopicWidgetModel.cs @@ -1,5 +1,4 @@ -using System.Web.Routing; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Topics { diff --git a/src/Presentation/SmartStore.Web/Scripts/_references.js b/src/Presentation/SmartStore.Web/Scripts/_references.js index ea01b5303a..54be21233f 100644 Binary files a/src/Presentation/SmartStore.Web/Scripts/_references.js and b/src/Presentation/SmartStore.Web/Scripts/_references.js differ diff --git a/src/Presentation/SmartStore.Web/Scripts/public.product-detail.js b/src/Presentation/SmartStore.Web/Scripts/public.product-detail.js index 23a2bd410b..90e9d734aa 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.product-detail.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.product-detail.js @@ -25,15 +25,25 @@ // update product data and gallery $(el).find(':input').change(function () { + var inputType = $(this).attr('type'); + if (inputType && (inputType === 'file' || inputType === 'submit')) + return this; + var context = $(this).closest('.update-container'); - if (context[0]) { // associated or bundled item + if (context[0]) { } else { context = el; } - context.doAjax({ + var url = context.attr('data-url'); + if (!url) { + return this; + } + + $({}).doAjax({ + url: url, data: context.find(':input').serialize(), callbackSuccess: function (response) { self.updateDetailData(response, context); @@ -62,7 +72,7 @@ if (data.GalleryHtml) { var cnt = $('#pd-gallery-container'); cnt.stop(true, true).transition({ opacity: 0 }, 300, "ease-out", function () { - gallery.reset(); + gallery.reset(); cnt.html(data.GalleryHtml); self.createGallery(data.GalleryStartIndex); @@ -130,7 +140,15 @@ updateAttrLine(".attr-length", data.Measure.Length.Text); updateAttrLine(".attr-width", data.Measure.Width.Text); updateAttrLine(".attr-height", data.Measure.Height.Text); - updateAttrLine(".attr-stock", data.Stock.Availability.Text); + + if (data.Stock.Quantity.Show) + { + updateAttrLine(".attr-stock", data.Stock.Availability.Text); + } + else + { + updateAttrLine(".attr-stock", ""); + } context.find('.add-to-cart .form-inline').toggle(data.Stock.Availability.Available); @@ -161,7 +179,8 @@ zoomType: opts.zoomType }, box: { - enabled: true + enabled: true, + hidePageScrollbars: false } }); } diff --git a/src/Presentation/SmartStore.Web/Scripts/public.product-detail.mobile.js b/src/Presentation/SmartStore.Web/Scripts/public.product-detail.mobile.js index 30d3475e29..3f4d6c1aa3 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.product-detail.mobile.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.product-detail.mobile.js @@ -16,17 +16,27 @@ var opts = this.options; - // update product data and gallery - $(el).find(':input').change(function () { + // update product data. grouped product not supported because associated product has no mobile view. + $(el).find(':input').change(function () { + var inputType = $(this).attr('type'); + if (inputType && (inputType === 'file' || inputType === 'submit')) + return this; + var context = $(this).closest('.update-container'); - if (context[0]) { // associated or bundled item + if (context[0]) { } else { context = el; } - context.doAjax({ + var url = context.attr('data-url'); + if (!url) { + return this; + } + + $({}).doAjax({ + url: url, data: context.find(':input').serialize(), callbackSuccess: function (response) { self.updateDetailData(response, context); @@ -106,7 +116,7 @@ updateAttrLine(".attr-height", data.Measure.Height.Text); updateAttrLine(".attr-stock", data.Stock.Availability.Text); - console.log(data.Stock.Availability.Text); + UpdateMainImage(imageUrls[data.GalleryStartIndex]); context.find('.add-to-cart .form-inline').toggle(data.Stock.Availability.Available); diff --git a/src/Presentation/SmartStore.Web/Scripts/public.product-list-scroller.js b/src/Presentation/SmartStore.Web/Scripts/public.product-list-scroller.js index c0a489774d..0878cdc0b8 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.product-list-scroller.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.product-list-scroller.js @@ -16,7 +16,7 @@ list.evenIfHidden(function(el) { var visibleElemnts = parseInt(list.outerWidth() / list.find(".item-box:first").outerWidth()); - visibleElemnts = (visibleElemnts == 0) ? 1 : (visibleElemnts - 1); + visibleElemnts = (visibleElemnts >= 1) ? 1 : (visibleElemnts - 1); list.find('.pl-row').wrap('
    ') ; diff --git a/src/Presentation/SmartStore.Web/Scripts/public.shopbar.js b/src/Presentation/SmartStore.Web/Scripts/public.shopbar.js index 1ae9feb17b..f28bf1c9a5 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.shopbar.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.shopbar.js @@ -117,8 +117,8 @@ var ShopBar = (function($) { var tool = tools[data.type]; var items = $(data.src).closest(".items"); if (items.length) { - // deletion occuured within the dropdown itself, so reload html! - items.throbber({ white: true, small: true }); + // deletion occured within the dropdown itself, so reload html! + items.throbber({ white: true, small: true, message: '' }); ShopBar.loadHtml(tool, true, function () { //items.data("throbber").hide(); }); @@ -179,19 +179,28 @@ var ShopBar = (function($) { var tool = _.isString(type) ? tools[type] : type; if (!tool) return; + var cnt = tool.find('.shopbar-flyout'); + var spinner = cnt.find('.spinner-container'); + if (spinner.length === 0) { + spinner = $('
    ').appendTo(cnt).append(createCircularSpinner(32)); + } + if (!keepOpen) { tool.removeClass("loaded").addClass("loading"); + spinner.addClass("active"); } - var cnt = tool.find('.shopbar-flyout'); + $.ajax({ cache: false, type: "POST", url: cnt.data("href"), success: function (data) { - cnt.empty().html(data); + cnt.find('.shopbar-flyout-inner').remove(); + cnt.append(data); }, complete: function (jqXHR, textStatus) { tool.removeClass("loading").addClass("loaded"); + spinner.removeClass("active"); if (_.isFunction(fn)) { fn.apply(this); } diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js index 3233a2c1d0..ff2f5be51f 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js @@ -37,7 +37,7 @@ EventBroker.publish("message", { text: msg, type: type, - delay: delay || 5000, + delay: delay || (type == "success" ? 2000 : 5000), hide: !sticky }) }; @@ -52,6 +52,54 @@ } } + window.createCircularSpinner = function (size, active, strokeWidth, boxed, white) { + var spinner = $('
    '); + if (active) spinner.addClass('active'); + if (boxed) spinner.addClass('spinner-boxed').css('font-size', size + 'px'); + if (white) spinner.addClass('white'); + + if (!_.isNumber(strokeWidth)) { + strokeWidth = 6; + } + + var svg = ''.format(size, 32 - strokeWidth, strokeWidth); + spinner.append($(svg)); + + return spinner; + } + + window.copyTextToClipboard = function (text) { + var result = false; + + if (window.clipboardData && window.clipboardData.setData) { + result = clipboardData.setData('Text', text); + } + else if (document.queryCommandSupported && document.queryCommandSupported('copy')) { + var textarea = document.createElement('textarea'), + focusElement = document.activeElement; + + textarea.textContent = text; + textarea.style.position = 'fixed'; + document.body.appendChild(textarea); + textarea.focus(); + textarea.setSelectionRange(0, textarea.value.length); + + try { + result = document.execCommand('copy'); + } + catch (e) { + } + finally { + document.body.removeChild(textarea); + if (focusElement) { + focusElement.focus(); + } + } + } + return result; + } + + // on document ready $(function () { @@ -96,11 +144,11 @@ // global notification subscriber if (window.EventBroker && window._ && $.pnotify) { //var stack_bottomright = { "dir1": "up", "dir2": "left", "firstpos1": 25, "firstpos2": 25 }; - var stack_topcenter = { "dir1": "down", "dir2": "right", "firstpos1": 60, "firstpos2": 10 }; + var stack_bottomcenter = { "dir1": "up", "dir2": "right", "firstpos1": 100, "firstpos2": 10 }; EventBroker.subscribe("message", function (message, data) { var opts = _.isString(data) ? { text: data } : data; - opts.stack = stack_topcenter; - opts.addclass = "stack-topcenter"; + opts.stack = stack_bottomcenter; + opts.addclass = "stack-bottomcenter"; $.pnotify(opts); }); } @@ -126,6 +174,31 @@ } }); + // Telerik grid smart AJAX state preserving + $('.t-grid.grid-preservestate').on('dataBound', function (e) { + var grid = $(this).data("tGrid"), + href = $(this).data("statepreserver-href"), + gridId = $(this).data("statepreserver-key"); + + if (href) { + $.ajax({ + type: "POST", + url: href, + async: true, + data: { + gridId: gridId, + path: location.pathname + location.search, + page: grid.currentPage, + size: grid.pageSize, + orderBy: grid.orderBy, + groupBy: grid.groupBy, + filter: grid.filterBy + }, + global: false + }); + } + }); + // AJAX tabs $('.nav a[data-ajax-url]').on('show', function (e) { var newTab = $(e.target), @@ -194,6 +267,10 @@ if ($({}).moreLess) { $('.more-less').moreLess(); } + + // fixes bootstrap 2 bug: non functional links on mobile devices + // https://github.com/twbs/bootstrap/issues/4550 + $('body').on('touchstart.dropdown', '.dropdown-menu a', function (e) { e.stopPropagation(); }); }); })( jQuery, this, document ); diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.doAjax.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.doAjax.js index 0aba22bf44..7521d4c677 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.doAjax.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.doAjax.js @@ -22,6 +22,38 @@ /* [...] */ }; + $.fn.doPostData = function (options) { + function createAndSubmitForm() { + var id = 'DynamicForm_' + Math.random().toString().substring(2), + form = '
    '; + + if (!_.isUndefined(options.data)) { + $.each(options.data, function (key, val) { + form += ''; + }); + } + + form += '
    '; + + $('body').append(form); + $('#' + id).submit(); + } + + normalizeOptions(this, options); + + if (_.isEmpty(options.url)) { + console.log('doPostData can\'t find the url!'); + } + else if (_.isEmpty(options.ask)) { + createAndSubmitForm(); + } + else if (confirm(options.ask)) { + createAndSubmitForm(); + } + + return this.each(function () { }); + } + function normalizeOptions(element, opt) { opt.ask = (_.isUndefined(opt.ask) ? $(element).attr('data-ask') : opt.ask); @@ -65,10 +97,10 @@ $.throbber.show(opt.curtainTitle); } else if (opt.throbber) { - $(opt.throbber).removeData('throbber').throbber({ white: true, small: true }); + $(opt.throbber).removeData('throbber').throbber({ white: true, small: true, message: '' }); } else if (opt.smallIcon) { - $(opt.smallIcon).append(''); + $(opt.smallIcon).append(window.createCircularSpinner(16, true)); } } @@ -77,8 +109,9 @@ $.throbber.hide(true); if (opt.throbber) $(opt.throbber).data('throbber').hide(true); - if (opt.smallIcon) - $(opt.smallIcon).find('span.ajax-loader-small').remove(); + if (opt.smallIcon) { + $(opt.smallIcon).find('.spinner').remove(); + } } function doRequest(opt) { diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js new file mode 100644 index 0000000000..2bfc9fc193 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js @@ -0,0 +1,345 @@ +/* +* Project: SmartStore entity picker +* Author: Marcus Gesing, SmartStore AG +*/ + +; (function ($, window, document, undefined) { + + var methods = { + loadDialog: function (options) { + options = normalizeOptions(options, this); + + return this.each(function () { + loadDialog(options); + }); + }, + + initDialog: function () { + return this.each(function () { + initDialog(this); + }); + }, + + fillList: function (options) { + return this.each(function () { + fillList(this, options); + }); + }, + + itemClick: function () { + return this.each(function () { + itemClick(this); + }); + } + }; + + $.fn.entityPicker = function (method) { + return main.apply(this, arguments); + }; + + $.entityPicker = function () { + return main.apply($('.entity-picker:first'), arguments); + }; + + + function main(method) { + if (methods[method]) + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + + if (typeof method === 'object' || !method) + return methods.init.apply(this, arguments); + + EventBroker.publish("message", { title: 'Method "' + method + '" does not exist on jQuery.entityPicker', type: "error" }); + return null; + } + + function normalizeOptions(options, context) { + var self = $(context), + selector = self.selector; + + var defaults = { + url: '', + entity: 'product', + caption: ' ', + disableIf: '', + disableIds: '', + thumbZoomer: false, + highligtSearchTerm: true, + returnField: 'id', + returnValueDelimiter: ',', + returnSelector: '', + maxReturnValues: 0, + onLoadDialogBefore: null, + onLoadDialogComplete: null, + onOkClicked: null + }; + + options = $.extend({}, defaults, options); + + if (_.isEmpty(options.url)) { + options.url = self.attr('data-url'); + } + + if (_.isEmpty(options.url)) { + console.log('entityPicker cannot find the url for entity picker!'); + } + + if (_.isString(selector) && !_.isEmpty(selector) && $(selector).is('input')) { + options.returnSelector = selector; + } + + return options; + } + + function ajaxErrorHandler(objXml) { + try { + if (objXml != null && objXml.responseText != null && objXml.responseText !== '') { + EventBroker.publish("message", { title: objXml.responseText, type: "error" }); + } + } + catch (e) { } + } + + function showStatus(dialog, noteClass, condition) { + var footerNote = $(dialog).find('.footer-note'); + footerNote.find('span').hide(); + footerNote.find('.' + (noteClass || 'default')).show(); + } + + function loadDialog(opt) { + var dialog = $('#entity-picker-' + opt.entity + '-dialog'); + + function showAndFocusDialog() { + dialog = $('#entity-picker-' + opt.entity + '-dialog'); + dialog.find('.caption').html(opt.caption || ' '); + dialog.data('entitypicker', opt); + dialog.modal('show'); + + fillList(dialog, { append: false }); + + setTimeout(function () { + dialog.find('.modal-body :input:visible:enabled:first').focus(); + }, 800); + } + + if (dialog.length) { + showAndFocusDialog(); + } + else { + $.ajax({ + cache: false, + type: 'GET', + data: { + "Entity": opt.entity, + "HighligtSearchTerm": opt.highligtSearchTerm, + "ReturnField": opt.returnField, + "MaxReturnValues": opt.maxReturnValues, + "DisableIf": opt.disableIf, + "DisableIds": opt.disableIds + }, + url: opt.url, + beforeSend: function () { + if (_.isFunction(opt.onLoadDialogBefore)) { + return opt.onLoadDialogBefore(); + } + }, + success: function (response) { + $('body').append(response); + showAndFocusDialog(); + }, + complete: function () { + if (_.isFunction(opt.onLoadDialogComplete)) { + opt.onLoadDialogComplete(); + } + }, + error: ajaxErrorHandler + }); + } + } + + function initDialog(context) { + var dialog = $(context), + keyUpTimer = null, + currentValue = ''; + + // search entities + dialog.find('button[name=SearchEntities]').click(function (e) { + e.preventDefault(); + fillList(this, { append: false }); + return false; + }); + + // toggle filters + dialog.find('button[name=FilterEntities]').click(function () { + dialog.find('.entity-picker-filter').slideToggle(); + }); + + // hit enter or key up starts searching + dialog.find('input.entity-picker-searchterm').keydown(function (e) { + if (e.keyCode == 13) { + e.preventDefault(); + return false; + } + }).bind('keyup change paste', function (e) { + try { + var val = $(this).val(); + + if (val !== currentValue) { + if (keyUpTimer) { + keyUpTimer = clearTimeout(keyUpTimer); + } + + keyUpTimer = setTimeout(function () { + fillList(dialog, { + append: false, + onSuccess: function () { + currentValue = val; + } + }); + }, 500); + } + } + catch (err) { } + }); + + // filter change starts searching + dialog.find('.entity-picker-filter .item').change(function () { + fillList(this, { append: false }); + }); + + // lazy loading + dialog.find('.modal-body').on('scroll', function (e) { + if ($('.load-more:not(.loading)').visible(true, false, 'vertical')) { + fillList(this, { append: true }); + } + }); + + // item select and item hover + dialog.find('.entity-picker-list').on('click', '.item', function (e) { + var item = $(this); + + if (item.hasClass('disable')) + return false; + + var dialog = item.closest('.entity-picker'), + list = item.closest('.entity-picker-list'), + data = dialog.data('entitypicker'); + + if (data.maxReturnValues === 1) { + list.find('.item').removeClass('selected'); + item.addClass('selected'); + } + else if (item.hasClass('selected')) { + item.removeClass('selected'); + } + else if (data.maxReturnValues === 0 || list.find('.selected').length < data.maxReturnValues) { + item.addClass('selected'); + } + + dialog.find('.modal-footer .btn-primary').prop('disabled', list.find('.selected').length <= 0); + }).on({ + mouseenter: function () { + if ($(this).hasClass('disable')) + showStatus($(this).closest('.entity-picker'), 'not-selectable'); + }, + mouseleave: function () { + if ($(this).hasClass('disable')) + showStatus($(this).closest('.entity-picker')); + } + }, '.item'); + + // return value(s) + dialog.find('.modal-footer .btn-primary').click(function () { + var dialog = $(this).closest('.entity-picker'), + items = dialog.find('.entity-picker-list .selected'), + data = dialog.data('entitypicker'), + result = ''; + + items.each(function (index, elem) { + var val = $(elem).attr('data-returnvalue'); + if (!_.isEmpty(val)) { + result = (_.isEmpty(result) ? val : (result + data.returnValueDelimiter + val)); + } + }); + + if (!_.isEmpty(data.returnSelector)) { + $(data.returnSelector).val(result).focus().blur(); + } + + if (_.isFunction(data.onOkClicked)) { + if (data.onOkClicked(result)) { + dialog.modal('hide'); + } + } + else { + dialog.modal('hide'); + } + }); + + // cancel + dialog.find('button[class=btn][data-dismiss=modal]').click(function () { + dialog.find('.entity-picker-list').empty(); + dialog.find('.footer-note span').hide(); + dialog.find('.modal-footer .btn-primary').prop('disabled', true); + }); + } + + function fillList(context, opt) { + var dialog = $(context).closest('.entity-picker'); + + if (_.isTrue(opt.append)) { + var pageElement = dialog.find('input[name=PageIndex]'), + pageIndex = parseInt(pageElement.val()); + + pageElement.val(pageIndex + 1); + } + else { + dialog.find('input[name=PageIndex]').val('0'); + } + + $.ajax({ + cache: false, + type: 'POST', + data: dialog.find('form:first').serialize(), + url: dialog.find('form:first').attr('action'), + beforeSend: function () { + if (_.isTrue(opt.append)) { + dialog.find('.load-more').addClass('loading'); + } + else { + dialog.find('.entity-picker-list').empty(); + dialog.find('.modal-footer .btn-primary').prop('disabled', true); + } + + dialog.find('button[name=SearchEntities]').button('loading').prop('disabled', true); + dialog.find('.load-more').append(createCircularSpinner(20, true)); + }, + success: function (response) { + var list = dialog.find('.entity-picker-list'), + data = dialog.data('entitypicker'); + + list.stop().append(response); + + if (_.isFalse(opt.append)) { + dialog.find('.entity-picker-filter').slideUp(); + showStatus(dialog); + } + + if (list.thumbZoomer && _.isTrue(data.thumbZoomer)) { + list.find('.thumb img:not(.zoomable-thumb)').addClass('zoomable-thumb'); + list.thumbZoomer(); + } + + if (_.isFunction(opt.onSuccess)) { + opt.onSuccess(); + } + }, + complete: function () { + dialog.find('button[name=SearchEntities]').prop('disabled', false).button('reset'); + dialog.find('.load-more.loading').parent().remove(); + }, + error: ajaxErrorHandler + }); + } + +})(jQuery, window, document); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.instantsearch.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.instantsearch.js new file mode 100644 index 0000000000..957e35f1a9 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.instantsearch.js @@ -0,0 +1,97 @@ +(function ($, window, document, undefined) { + + // on document ready + $(function () { + + var _baseHighlighter = $.fn.typeahead.Constructor.prototype.highlighter; + + var searchBox = $('#instantsearch'); + if (searchBox.length == 0) + return; + + var searchResult, + minLength = searchBox.data("minlength"), + showThumbs = searchBox.data("showthumbs"), + url = searchBox.data("url"); + + searchBox.typeahead({ + items: 16, + minLength: minLength, + keyUpDelay: 400, + menu: ''.format(showThumbs ? " rich" : ""), + autoSelectFirstItem: false, + source: function (query, process) { + var spinner = $('#instantsearch-progress'); + if (spinner.length === 0) { + spinner = createCircularSpinner(20).attr('id', 'instantsearch-progress').appendTo(searchBox.parent()); + } + spinner.addClass('active'); + + return $.ajax({ + dataType: "json", + url: url, + data: { term: query }, + type: 'GET', + success: function (json) { + searchResult = json; + var items = $.map(json, function (val, i) { + return val.label; + }); + process(items); + }, + error: function () { + searchResult = null; + }, + complete: function () { + spinner.removeClass('active'); + } + }); + }, + updater: function (label) { + if (!searchResult) + return; + + var item = _.find(searchResult, function (x) { return x.label == label }); + setLocation(item.producturl); + }, + matcher: function (item) { + // items are filtered already. Do not filter again! + return true; + }, + highlighter: function (label) { + var inner = _baseHighlighter.call(this, label); + if (!showThumbs) { + return inner; + } + + var item = _.find(searchResult, function (x) { return x.label == label }); + + var html = '' + + "
    " + + "
    " + + "
    " + + "
    " + inner + "
    " + + "
    " + item.secondary + "
    " + + "
    " + + "
    "; + + return html; + } + }); + + searchBox.closest("form").on("submit", function (e) { + if (!searchBox.val()) { + var frm = $(this); + var shakeOpts = { direction: "right", distance: 4, times: 2 }; + frm.stop(true, true).effect("shake", shakeOpts, 400, function () { + searchBox.trigger("focus").removeClass("placeholder") + }); + return false; + } + + return true; + }); + }); + +})( jQuery, this, document ); + diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.smartgallery.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.smartgallery.js index 0552e31a90..56fbff1fc5 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.smartgallery.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.smartgallery.js @@ -88,6 +88,10 @@ this.imageWrapperWidth = this.imageWrapper.width(); this.imageWrapperHeight = opts.height || 300; + + if (this.imageWrapperHeight > 300) { + this.imageWrapper.css({ height: this.imageWrapperHeight + 'px' }) + } this.navDisplayWidth = this.nav.width(); this.currentIndex = 0; @@ -128,13 +132,16 @@ }; }; - this.loading(true); + if (this.images[startAt]) { + this.loading(true); + } this.showImage(startAt); if (opts.responsive && !isRefresh) { EventBroker.subscribe("page.resized", function (data) { - self.reset(); + self.reset(); + self.inTransition = false; self.init(true); }); } @@ -152,7 +159,7 @@ loader: null, preloads: null, thumbsWrapper: null, - box: null, + box: null, // (blueImp) image gallery element imageWrapperWidth: 0, imageWrapperHeight: 0, @@ -179,9 +186,8 @@ this.nav = el.find('.sg-nav').css("opacity", "0"); this.thumbsWrapper = this.nav.find('.sg-thumbs'); this.preloads = $('
    '); - this.loader = $('
    '); + this.loader = $('
    ').append(createCircularSpinner(24, false, null, true)); this.imageWrapper.append(this.loader); - this.loader.hide(); $(document.body).append(this.preloads); }, @@ -209,14 +215,20 @@ } self.imageWrapper.find('.sg-image').remove(); + + $('.smartgallery-overlay').remove(); + if (self.box) { + self.box.remove(); + } + self.box = null; }, loading: function(value) { if (value) { - this.loader.show(); + this.loader.addClass('active'); } else { - this.loader.hide(); + this.loader.removeClass('active'); }; }, @@ -425,6 +437,8 @@ .append('
      '); widget.appendTo('body'); + self.box = widget; + var prevY = null; var onMouseMove = function (e) { @@ -452,6 +466,14 @@ return widget; }; + var getOverlay = function (widget) { + var gov = $('.smartgallery-overlay'); + if (gov.length == 0) { + gov = $('').insertBefore(widget[0]); + } + return gov; + }; + if (this.options.displayImage) { // Global click handler to open links with data-gallery attribute // in the Gallery lightbox: @@ -462,10 +484,15 @@ widget = getWidget(id == 'default' ? 'image-gallery-default' : id), container = (widget.length && widget) || $(Gallery.prototype.options.container), callbacks = { - onopen: function () { - container.data('gallery', this).trigger('open'); + onopen: function () { + var gov = getOverlay(widget); + gov.on('click', function (e) { + widget.data('gallery').close(); + }); + gov.show().addClass("in"); + container.data('gallery', this).trigger('open'); }, - onopened: function () { + onopened: function () { container.trigger('opened'); }, onslide: function () { @@ -478,9 +505,11 @@ container.trigger('slidecomplete', arguments); }, onclose: function () { - container.trigger('close'); + getOverlay(widget).removeClass("in"); + container.trigger('close'); }, onclosed: function () { + getOverlay(widget).css('display', 'none'); container.trigger('closed').removeData('gallery'); } }, @@ -494,7 +523,7 @@ event: e }, callbacks, - self.options + self.options.box || {} ), // Select all links with the same data-gallery attribute: links = $('[data-gallery="' + id + '"]').not($(this)); @@ -862,6 +891,7 @@ // full size image box options box: { enabled: true, + closeOnSlideClick: false /* {...} blueimp image gallery options are passed through */ }, callbacks: { diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.throbber.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.throbber.js index a72c2f60da..0929bab9bd 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.throbber.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.throbber.js @@ -17,7 +17,7 @@ opts = this.options = options, throbber = this.throbber = null, throbberContent = this.throbberContent = null; - + this.visible = false; this.init = function () { @@ -29,31 +29,21 @@ this.initialized = false; this.init(); - + } Throbber.prototype = { - - _reposition: function() { - var self = this, - size = { - left: (self.el.width() - self.throbberContent.outerWidth()) / 2, - top: (self.el.height() - self.throbberContent.outerHeight()) / 2 - } - self.throbberContent.css(size); - }, show: function (o) { - if (this.visible) return; - var self = this, - opts = $.extend( { }, this.options, o); - + var self = this, + opts = $.extend({}, this.options, o); + // create throbber if not avail if (!self.throbber) { - self.throbber = $('
      ') + self.throbber = $('
      ') .addClass(opts.cssClass) .addClass(opts.small ? "small" : "large") .appendTo(opts._global ? 'body' : self.el); @@ -63,44 +53,51 @@ if (opts._global) { self.throbber.addClass("global"); } - + else { + if (/static/.test(self.el.css("position"))) { + self.el.css("position", "relative"); + } + } + self.throbberContent = self.throbber.find(".throbber-content"); + var spinner = window.createCircularSpinner(opts.small ? 50 : 100, true, 3); + spinner.insertAfter(self.throbberContent); self.initialized = true; } - // set text and reposition + // set text self.throbber.css({ visibility: 'hidden', display: 'block' }); self.throbberContent.html(opts.message); - self._reposition(); + self.throbberContent.toggleClass('hide', !(_.isString(opts.message) && opts.message.trim().length > 0)); self.throbber.css({ visibility: 'visible', opacity: 0 }); - var show = function() { - if (_.isFunction(opts.callback)) { - opts.callback.apply(this); - } - if (!self.visible) { + var show = function () { + if (_.isFunction(opts.callback)) { + opts.callback.apply(this); + } + if (!self.visible) { // could have been set to false in 'hide'. // this can happen in very short running processes. self.hide(); - } + } } - + self.visible = true; - self.throbber.delay(opts.delay).transition({opacity: 1}, opts.speed || 0, "linear", show); + self.throbber.delay(opts.delay).transition({ opacity: 1 }, opts.speed || 0, "linear", show); - if (opts.timeout) { - var hideFn = _.bind(self.hide, this); - window.setTimeout(hideFn, opts.timeout + opts.delay); + if (opts.timeout) { + var hideFn = _.bind(self.hide, this); + window.setTimeout(hideFn, opts.timeout + opts.delay); } }, - hide: function(immediately) { + hide: function (immediately) { var self = this, opts = this.options; if (self.throbber && self.visible) { - var hide = function() { - self.throbber.css('display', 'none'); + var hide = function () { + self.throbber.css('display', 'none'); } self.visible = false; @@ -116,10 +113,9 @@ // A really lightweight plugin wrapper around the constructor, // preventing against multiple instantiations $.fn[pluginName] = function (options) { - return this.each(function () { if (!$.data(this, pluginName)) { - options = $.extend( {}, $[pluginName].defaults, options ); + options = $.extend({}, $[pluginName].defaults, options); $.data(this, pluginName, new Throbber(this, options)); } }); @@ -142,40 +138,30 @@ // internal _global: false }; - - // global resize event - $(window).on('resize.throbber', function() { - // resize all active/visible throbbers - $.each(throbbers, function(i, throbber) { - if (throbber.initialized && throbber.visible) { - throbber._reposition(); - } - }) - }); $[pluginName] = { - + // the global, default plugin options defaults: defaults, // options: a message string || options object - show: function(options) { - var opts = $.extend( defaults, _.isString(options) ? { message: options } : options, { show: false, _global: true } ); + show: function (options) { + var opts = $.extend(defaults, _.isString(options) ? { message: options } : options, { show: false, _global: true }); if (!globalThrobber) { globalThrobber = $(window).throbber(opts).data("throbber"); } globalThrobber.show(opts); - }, - hide: function() { + hide: function (immediately) { if (globalThrobber) { - globalThrobber.hide(); + globalThrobber.hide(immediately); } } } // $.throbber })(jQuery, window, document); + diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index ee9742f6b6..8340a62bfb 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -1,5 +1,5 @@  - + Debug @@ -13,7 +13,7 @@ Properties SmartStore.Web SmartStore.Web - v4.5 + v4.5.1 false true @@ -48,6 +48,7 @@ 4 false AnyCPU + AllFilesInProjectFolder pdbonly @@ -57,26 +58,27 @@ prompt 4 false - AllFilesInTheProject + AllFilesInProjectFolder false false + AnyCPU - - False - ..\..\packages\AjaxMin.5.8.5172.27710\lib\net40\AjaxMin.dll + + ..\..\packages\AjaxMin.5.14.5506.26202\lib\net40\AjaxMin.dll + True False ..\..\packages\Antlr.3.5.0.2\lib\Antlr3.Runtime.dll - - False - ..\..\packages\Autofac.3.4.1\lib\net40\Autofac.dll + + ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + True - False - ..\..\packages\Autofac.Mvc5.3.3.1\lib\net45\Autofac.Integration.Mvc.dll + ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll + True False @@ -95,34 +97,31 @@ False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll False - ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll False - ..\..\packages\EntityFramework.SqlServerCompact.6.1.0\lib\net45\EntityFramework.SqlServerCompact.dll + ..\..\packages\EntityFramework.SqlServerCompact.6.1.3\lib\net45\EntityFramework.SqlServerCompact.dll - - False - ..\..\packages\51Degrees.mobi.3.1.4.5\lib\NET40\FiftyOne.Foundation.dll - - - False - ..\..\packages\FluentValidation.5.0.0.1\lib\Net40\FluentValidation.dll + + ..\..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll + True - - False - ..\..\packages\FluentValidation.MVC4.5.0.0.1\lib\Net40\FluentValidation.Mvc.dll + + ..\..\packages\FluentValidation.MVC5.5.6.2.0\lib\Net45\FluentValidation.Mvc.dll + True - - False - ..\..\packages\JavaScriptEngineSwitcher.Core.1.1.3\lib\net40\JavaScriptEngineSwitcher.Core.dll + + ..\..\packages\JavaScriptEngineSwitcher.Core.1.2.4\lib\net40\JavaScriptEngineSwitcher.Core.dll + True - - ..\..\packages\JavaScriptEngineSwitcher.Msie.1.1.4\lib\net40\JavaScriptEngineSwitcher.Msie.dll + + ..\..\packages\JavaScriptEngineSwitcher.Msie.1.2.11\lib\net40\JavaScriptEngineSwitcher.Msie.dll + True False @@ -132,15 +131,13 @@ True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\MsieJavaScriptEngine.1.4.2\lib\net40\MsieJavaScriptEngine.dll - - - False - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\MsieJavaScriptEngine.1.5.6\lib\net40\MsieJavaScriptEngine.dll + True - - ..\..\packages\recaptcha.1.0.5.0\lib\.NetFramework 4.0\Recaptcha.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True @@ -155,7 +152,6 @@ - @@ -165,13 +161,14 @@ + False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - + False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll False @@ -179,20 +176,20 @@ False - ..\..\packages\Microsoft.AspNet.Razor.3.2.2\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.2\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll @@ -256,10 +253,10 @@ - + @@ -340,6 +337,7 @@ + @@ -349,6 +347,7 @@ + @@ -646,292 +645,19 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ResXFileCodeGenerator MvcLocalization.de.Designer.cs Designer + Designer - - - - - - - - - - - @@ -945,11 +671,11 @@ Designer - - - - - + + + + + @@ -975,77 +701,85 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - - + + + + + + + - + + + + + + + + + - - - - - + + + + + + + + + + + + + - - - - + @@ -1057,7 +791,6 @@ - @@ -1074,6 +807,8 @@ + + @@ -1191,6 +926,8 @@ + + @@ -1213,7 +950,6 @@ - @@ -1515,14 +1251,6 @@ - - - - - - - - @@ -1546,97 +1274,14 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1659,15 +1304,12 @@ - - - - + @@ -1934,9 +1576,15 @@ + + + + + + @@ -1986,9 +1634,6 @@ - - - @@ -2054,7 +1699,6 @@ - @@ -2214,25 +1858,12 @@ - - if not exist "$(TargetDir)x86" md "$(TargetDir)x86" - xcopy /s /y "$(SolutionDir)..\lib\sqlce\x86\*.*" "$(TargetDir)x86" - xcopy /s /y "$(SolutionDir)..\lib\ClearScript.V8\Native\x86\*.*" "$(TargetDir)x86" -if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64" - xcopy /s /y "$(SolutionDir)..\lib\sqlce\amd64\*.*" "$(TargetDir)amd64" - xcopy /s /y "$(SolutionDir)..\lib\ClearScript.V8\Native\amd64\*.*" "$(TargetDir)amd64" - -if not exist "$(TargetDir)ClearScript.V8" md "$(TargetDir)ClearScript.V8" - xcopy /s /y "$(SolutionDir)..\lib\ClearScript.V8\lib\*.*" "$(TargetDir)ClearScript.V8" + + @@ -2243,5 +1874,80 @@ if not exist "$(TargetDir)ClearScript.V8" md "$(TargetDir)ClearScript.V8" + + + + false + + + + + **\*.cs; + **\*.orig; + **\*.bak; + **\*.log; + **\*.csproj; + **\*.csproj.user; + **\*.DotSettings.user; + **\packages.config; + **\*.Debug.config; + **\*.Release.config; + **\obj\**; + **\bin\*.xml; + **\bin\HostRestart\**; + Administration\bin\**; + App_Data\_Backup\**; + App_Data\_temp\**; + App_Data\ExportProfiles\**; + App_Data\ImportProfiles\**; + App_Data\Migrations\**; + App_Data\InstalledPlugins.txt; + App_Data\Licenses.lic; + App_Data\Settings.txt; + App_Data\SmartStore.Db.sdf; + Exchange\**; + Content\Files\ExportImport\**; + Content\Images\Thumbs\**; + Media\**\*.png; + Media\**\*.jpg; + Media\**\*.jpeg; + Media\**\*.gif; + Media\**\*.pdf; + Media\**\*.docx; + Properties\**; + Plugins\*\bin\**; + + + + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)Media\Thumbs\placeholder.txt + + + + + + + + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/bootstrap.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/bootstrap.less index 1398710a14..d4c3a5acd4 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/bootstrap.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/bootstrap.less @@ -63,6 +63,7 @@ @import "~/Content/bootstrap/carousel.less"; // (MC) extra 3rd party or own components +@import "~/Content/bootstrap/custom/spinner.less"; @import "~/Content/bootstrap/custom/throbber.less"; @import "~/Content/bootstrap/custom/select2.less"; @import "~/Content/bootstrap/custom/datetimepicker.less"; diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/cart.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/cart.less index 7fb08a94db..1db6a5e082 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/cart.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/cart.less @@ -2,7 +2,6 @@ // ShoppingCart, OrderSummary & WishList Styles // -------------------------------------------------- - .order-summary-content .delivery-time { text-align: left; } diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/catalog.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/catalog.less index fb1afad5d5..d9d5aff4e6 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/catalog.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/catalog.less @@ -92,5 +92,15 @@ .text-overflow(); } +// +// Search panel +// -------------------------------------------------- + +.search-page .basic-search { + border-bottom: 1px solid #ddd; + border-bottom-color: rgba(0,0,0, 0.12); + margin-bottom: 6px; +} + diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/checkout.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/checkout.less index ea2111083c..60e01fb235 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/checkout.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/checkout.less @@ -21,7 +21,8 @@ background-color: #f9f9f9; border-color: #f9f9f9; } - .opt-info { + .opt-info, + .opt-info-item { margin-top: 6px; margin-left: 32px; } @@ -167,6 +168,11 @@ text-align:left; } +.address-required-hint { + margin-bottom: 12px; + font-size: 12px; +} + .checkout-data .payment-info { padding: 10px 0; border-top: 1px solid #d7d7d7; @@ -234,24 +240,14 @@ /* Terms of service ================================================ */ - -#terms-of-service-modal { - width: 650px; -} -#terms-of-service-modal .modal-body { - min-height: 300px; - overflow: hidden; -} -#iframe-terms-of-service { - min-height: 400px; - width: 100%; -} - .terms-of-service.alert { - padding-bottom: 4px !important; - a.read { - font-weight: bold; - } + padding-bottom: 4px !important; + a.read { + font-weight: bold; + &:hover { + cursor: pointer; + } + } } diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/common.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/common.less index 5de3a398a9..327e7c7ea5 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/common.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/common.less @@ -130,16 +130,6 @@ nav ul, nav li { /* Misc ================================================ */ -.ajax-loader-small { - display: inline-block; - position: relative; - width: 16px; - height: 16px; - padding: 0; - margin: 0; - background: transparent url('images/ajax_loader_small.gif') 50% 50% no-repeat; -} - #popup-content { margin: 20px; } diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/datalist.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/datalist.less index 43fd4b4ccb..6cd5cfe191 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/datalist.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/datalist.less @@ -89,7 +89,7 @@ .item-box figure.picture { position: relative; z-index: 0; - padding: 4px; + padding: 12px 12px 4px 12px; text-align: center; margin: 0; min-height: 100px; @@ -138,7 +138,8 @@ } .item-box .data { - padding: 8px; + padding: 12px; + padding-top: 8px; } .item-box.details .data { @@ -257,6 +258,9 @@ text-decoration: none; } } + .fa { + font-size: 13px; + } } .item-box:hover .quicklinks { .opacity(100); diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/header.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/header.less index c783048936..012a75ee71 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/header.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/header.less @@ -7,6 +7,10 @@ padding: 46px 0 0 0; } +.store-closed #header { + padding-top: 0; +} + #logobar { position: relative; padding: 10px 0; diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ajax_loader_large.gif b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ajax_loader_large.gif deleted file mode 100644 index fb2c7a3ddb..0000000000 Binary files a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ajax_loader_large.gif and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/gradient.png b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/gradient.png deleted file mode 100644 index 7c0307f62c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/gradient.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ico-delete.gif b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ico-delete.gif deleted file mode 100644 index 2ca8b23523..0000000000 Binary files a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ico-delete.gif and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ico-edit.gif b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ico-edit.gif deleted file mode 100644 index 137a605679..0000000000 Binary files a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ico-edit.gif and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ico-move-topic.gif b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ico-move-topic.gif deleted file mode 100644 index a5ee14c907..0000000000 Binary files a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/images/ico-move-topic.gif and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/layout.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/layout.less index ae6bc7a7fd..b6cb2e661c 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/layout.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/layout.less @@ -11,6 +11,7 @@ #content-body { margin-top: 10px; padding-bottom: 10px; + min-height: 400px; } /* one column */ diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/pages.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/pages.less index e36d2725df..4df8f7751d 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/pages.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/pages.less @@ -67,6 +67,7 @@ } .compare-products-table tr.product-name { background-color: #e5e5e5; + font-weight: bold; } .compare-products-table tr.product-name a { color: #888; @@ -119,6 +120,9 @@ .news-items .view-all { font-weight: bold; } +.news-item-page .news-content { + margin-bottom: 12px +} /* Poll & Vote Styles ================================================ */ @@ -164,3 +168,17 @@ vertical-align: top; } +/* Manufacturers on homepage +================================================ */ + +.block-manufacturer-navigation .manufacturer-pic { + float: left; + width: 50px; + height: 50px; + margin: 0 0 10px 10px; + + img { + max-width: 50px; + max-height: 50px; + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/paginator.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/paginator.less index 61d9b69454..868f34a6b9 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/paginator.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/paginator.less @@ -21,7 +21,8 @@ border-bottom: none; border-left: none; padding-left: 4px; - padding-right: 4px + padding-right: 4px; + background: transparent; } .pagination .pagination-summary { display: inline-block; diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less index 9edce6806f..0a0fde971a 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less @@ -138,6 +138,10 @@ border: 0; } +.manufacturer-pics-container { + min-height: 20px; +} + #product-detail-tabs { margin-top: 50px; } @@ -200,7 +204,7 @@ .product-details-page .add-to-cart { padding-top: 10px; } -.product-details-page #details-cnt .add-to-cart .fa-plus-circle { +.product-details-page #details-cnt .add-to-cart .fa-cart-plus { position: absolute; right: 6px; top: 50%; @@ -391,15 +395,15 @@ border-top: none !important; } +.attributes .image-has-value { + display: table-cell; + padding-top: 10px; +} + .attribute-value-image { margin-right: 5px; vertical-align: middle; } - -.attributes .image-has-value { - margin-left: 28px; -} - .mb-bundle-pictures .fa-plus { padding: 4px 10px 0 10px; } @@ -409,6 +413,10 @@ .attributes .attribute-value-image-checkbox { float: left; } +.attributes .attribute-value-image-checkbox { + margin-right: 10px; + padding-top: 10px; +} .mb-bundle-pictures img { max-width: 24px; diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/productfilter.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/productfilter.less index 13866fddad..eafccc84d9 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/productfilter.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/productfilter.less @@ -14,7 +14,7 @@ position: relative; min-height: 32px; - .ajax-loader-small { + .spinner { position: absolute; left: 50%; top: 50%; diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/profile.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/profile.less index 2af25a3deb..19cd3e181b 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/profile.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/profile.less @@ -88,14 +88,18 @@ /* Return Requests ================================================ */ -.return-request-list-page .request-item .title{ - font-size: 13px; +.return-request-list-page .request-item { + margin-bottom: 20px; + + .title { + margin-bottom: 10px; + } } -.reward-points-page .reward-points-overview{ +.reward-points-page .reward-points-overview { padding: 10px 10px 5px 0; width: 100%; } -.return-request-page .why .comment{ +.return-request-page .why .comment { width: 350px; height: 150px; } diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/shopbar.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/shopbar.less index b0ed358d44..474543e015 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/shopbar.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/shopbar.less @@ -27,18 +27,9 @@ @bg: top, @shopBarBackgroundColor, @shopBarBackgroundColor 40%, darken(@shopBarBackgroundColor, 7%) 100%; background-color: @shopBarBackgroundColor; background-image: -webkit-linear-gradient(@bg); - background-image: -o-linear-gradient(@bg); background-image: linear-gradient(@bg); } -.no-cssgradients #shopbar { - // fix the ie9 "gradient dropdown transparent" bug - background-color: @shopBarBackgroundColor; - background-image: url('images/shopbar-bg.png'); - background-position: 0 100%; - background-repeat: repeat-x; -} - #shopbar.sticky { .opacity(75); &:hover { @@ -52,24 +43,61 @@ padding: 8px 0; } -#quicksearch { +#instantsearch { width: 250px; } @media (min-width: 1200px) { - #quicksearch { + #instantsearch { width: 450px; } } -#quicksearch-progress { +#instantsearch-progress { position: absolute; right: 45px; top: 50%; - margin-top: -8px; + margin-top: -10px; z-index: 10; } +.typeahead { + @baseHeight: 36px; + &.dropdown-instantsearch.rich li { + border-bottom: 1px solid rgba(0,0,0, 0.12); + &:last-child { border-bottom: none } + } + .item-wrapper { + display: block; + height: @baseHeight; + padding: 5px 0; + } + .item-thumb { + float: left; + width: @baseHeight; + height: @baseHeight; + margin-right: 8px; + text-align: center; + } + .item-thumb > img { + max-height: 100% ; + } + .item-labels { + float: left; + height: @baseHeight; + } + .item-primary { + font-size: 13px; + line-height: @baseHeight / 2; + } + .item-secondary { + font-size: 11px; + line-height: (@baseHeight / 2) - 2px; + padding-top: 2px; + opacity: 0.7 + } +} + #shopbar-account .shopbar-flyout-container .register { text-align: center; } @@ -102,7 +130,7 @@ width: 1px; background-color: rgba(0,0,0, .15); margin: 8px; - padding: none; + padding: 0; border-right: 1px solid @shopBarBackgroundColor; .no-rgba & { @@ -135,8 +163,6 @@ position: relative; display: inline-block; margin: 2px 4px 0 0; - width: 24px; - height: 24px; span.label { position: absolute; @@ -148,19 +174,18 @@ } } -.shopbar-tool [class^="sm-icon-"] { +.shopbar-button-icon .fa { display: inline-block; - width: 24px; - height: 24px; - font-size: 24px; + font-size: 24px !important; font-style: normal; - margin-top: 5px; + line-height: 28px; + vertical-align: middle; color: @shopBarIconColor; .transition(color .1s linear); } -.shopbar-button:hover [class^="sm-icon-"] { +.shopbar-button:hover .shopbar-button-icon .fa { color: darken(@shopBarIconColor, 22%); } @@ -217,7 +242,6 @@ margin: 0; list-style: none; -webkit-background-clip: padding-box; - -moz-background-clip: padding; background-clip: padding-box; height: 50px; @@ -236,10 +260,18 @@ .box-shadow(0 2px 8px rgba(0,0,0,.2)); .shopbar-tool.loading & { - background-image: url('images/loading.gif'); + //background-image: url('images/loading.gif'); } - .shopbar-tool.loading & > * { + .spinner-container { + height: 100%; + } + + .shopbar-tool:not(.loading) & > .spinner-container { + display: none; + } + + .shopbar-tool.loading & > .shopbar-flyout-inner { display: none; } diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/slider.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/slider.less index 6528c573b0..22a5a10f90 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/slider.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/slider.less @@ -71,7 +71,7 @@ Aside from these comments, you may modify and distribute this file as you please .transition(background-position 1.5s ease-out); .transform(translate3d(0, 0, 0)); - -webkit-perspective: 1000; -moz-perspective: 100; perspective: 1000; + -webkit-perspective: 1000; -moz-perspective: 1000; perspective: 1000; -webkit-transform-style: preserve-3d; transform-style: preserve-3d; // backface-visibility prevents graphical glitches when frames are animating @@ -297,7 +297,7 @@ Aside from these comments, you may modify and distribute this file as you please height: 100%; top: 0; .opacity(0); - -webkit-perspective: 1000; -moz-perspective: 100; perspective: 1000; + -webkit-perspective: 1000; -moz-perspective: 1000; perspective: 1000; -webkit-transform-style: preserve-3d; transform-style: preserve-3d; } } diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/theme.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/theme.less index 346d8148ab..a1c1b682af 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/theme.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/theme.less @@ -7,7 +7,6 @@ // Fonts @import "typo.less"; -@import "~/Content/fonts/fontastic.less"; // Bootstrap @import "bootstrap.less"; diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Shared/ConfigureTheme.cshtml b/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Shared/ConfigureTheme.cshtml index f611bc9347..7b282093c4 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Shared/ConfigureTheme.cshtml +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Shared/ConfigureTheme.cshtml @@ -1,9 +1,6 @@ @model IDictionary -@using System.Dynamic; -@using SmartStore.Core.Themes; -@using SmartStore.Web.Framework.Themes; -@using SmartStore.Utilities; +@using SmartStore.Web.Framework.Theming; @using SmartStore.Web.Framework.UI; @{ diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Shared/Head.cshtml b/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Shared/Head.cshtml index 2442d88fa6..5ac574bc9a 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Shared/Head.cshtml +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Shared/Head.cshtml @@ -1,6 +1,4 @@ -@using SmartStore.Core; -@using SmartStore.Core.Infrastructure; -@using SmartStore.Web.Framework.Themes; +@using SmartStore.Web.Framework.Theming; @{ Html.AppendCssFileParts(false, "~/Themes/Alpha/Content/theme.less"); diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Web.config b/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Web.config index 3f3148484e..bd9d09444f 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Web.config +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Views/Web.config @@ -10,7 +10,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/theme.config b/src/Presentation/SmartStore.Web/Themes/Alpha/theme.config index 95ffec663d..6a5fcfc2eb 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/theme.config +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/theme.config @@ -1,5 +1,5 @@  - + diff --git a/src/Presentation/SmartStore.Web/Themes/AlphaBlack/Views/Web.config b/src/Presentation/SmartStore.Web/Themes/AlphaBlack/Views/Web.config index 3f3148484e..bd9d09444f 100644 --- a/src/Presentation/SmartStore.Web/Themes/AlphaBlack/Views/Web.config +++ b/src/Presentation/SmartStore.Web/Themes/AlphaBlack/Views/Web.config @@ -10,7 +10,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/Themes/AlphaBlack/theme.config b/src/Presentation/SmartStore.Web/Themes/AlphaBlack/theme.config index c8c571f3bc..7e4c2cea86 100644 --- a/src/Presentation/SmartStore.Web/Themes/AlphaBlack/theme.config +++ b/src/Presentation/SmartStore.Web/Themes/AlphaBlack/theme.config @@ -1,5 +1,5 @@  - + diff --git a/src/Presentation/SmartStore.Web/Themes/AlphaBlue/Views/Web.config b/src/Presentation/SmartStore.Web/Themes/AlphaBlue/Views/Web.config index 3f3148484e..bd9d09444f 100644 --- a/src/Presentation/SmartStore.Web/Themes/AlphaBlue/Views/Web.config +++ b/src/Presentation/SmartStore.Web/Themes/AlphaBlue/Views/Web.config @@ -10,7 +10,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/Themes/AlphaBlue/theme.config b/src/Presentation/SmartStore.Web/Themes/AlphaBlue/theme.config index 63e4151b39..7c9c6de296 100644 --- a/src/Presentation/SmartStore.Web/Themes/AlphaBlue/theme.config +++ b/src/Presentation/SmartStore.Web/Themes/AlphaBlue/theme.config @@ -1,5 +1,5 @@  - + diff --git a/src/Presentation/SmartStore.Web/Themes/Mobile/Content/styles.css b/src/Presentation/SmartStore.Web/Themes/Mobile/Content/styles.css index bbc2b1f597..dbf4b3d68c 100644 --- a/src/Presentation/SmartStore.Web/Themes/Mobile/Content/styles.css +++ b/src/Presentation/SmartStore.Web/Themes/Mobile/Content/styles.css @@ -156,7 +156,7 @@ img { max-width: 100% } .checkout-data .use-reward-points{margin:0;padding:10px;background:#F7F5E8;border:dotted 1px #d3d3d3;} .checkout-data .payment-methods{text-align:left;height:auto;} .checkout-data .payment-methods .payment-method-item{text-align:left;vertical-align:text-top;} -.checkout-data .payment-methods .payment-method-item .payment-method-desc{margin:0 0 14px 46px;} +.checkout-data .payment-methods .payment-method-item .payment-method-description{margin:0 0 15px 15px;} .checkout-data .payment-methods .select-button{text-align:left;} .checkout-data .payment-methods .message-error{text-align:left;} .checkout-data .payment-info{text-align:left;height:auto;} @@ -689,3 +689,10 @@ pre{white-space:pre-wrap;/* css-3 */white-space:0;/* Mozilla, since 1999 */white .legal-hints .footer-legal { color: green; } +.change-device button{ + display: inline; + margin: 0; + border: 0; + background: none; + padding: 7px; +} diff --git a/src/Presentation/SmartStore.Web/Themes/Mobile/Content/user.css b/src/Presentation/SmartStore.Web/Themes/Mobile/Content/user.css new file mode 100644 index 0000000000..dac21231b8 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Themes/Mobile/Content/user.css @@ -0,0 +1,6 @@ +// +// Customize the (child) theme here with css overrides +// or new selectors (for users) +// -------------------------------------------------- + + diff --git a/src/Presentation/SmartStore.Web/Themes/Mobile/Views/Shared/Head.Mobile.cshtml b/src/Presentation/SmartStore.Web/Themes/Mobile/Views/Shared/Head.Mobile.cshtml index 31e743b55d..fd125c4cef 100644 --- a/src/Presentation/SmartStore.Web/Themes/Mobile/Views/Shared/Head.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Themes/Mobile/Views/Shared/Head.Mobile.cshtml @@ -2,5 +2,5 @@ Html.AppendCssFileParts( "~/Themes/Mobile/Content/jquery.mobile.structure-1.3.2.css", "~/Themes/Mobile/Content/jquery.mobile.theme-1.3.2.css"); - Html.AppendCssFileParts("~/Themes/Mobile/Content/styles.css"); + Html.AppendCssFileParts("~/Themes/Mobile/Content/styles.css", "~/Themes/Mobile/Content/user.css"); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Themes/Mobile/Views/Web.config b/src/Presentation/SmartStore.Web/Themes/Mobile/Views/Web.config index 3f3148484e..bd9d09444f 100644 --- a/src/Presentation/SmartStore.Web/Themes/Mobile/Views/Web.config +++ b/src/Presentation/SmartStore.Web/Themes/Mobile/Views/Web.config @@ -10,7 +10,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/Themes/Mobile/preview.png b/src/Presentation/SmartStore.Web/Themes/Mobile/preview.png index 0fce6c4c0b..69d34f4d8b 100644 Binary files a/src/Presentation/SmartStore.Web/Themes/Mobile/preview.png and b/src/Presentation/SmartStore.Web/Themes/Mobile/preview.png differ diff --git a/src/Presentation/SmartStore.Web/Themes/Mobile/theme.config b/src/Presentation/SmartStore.Web/Themes/Mobile/theme.config index 5b9b41beee..42c0d992eb 100644 --- a/src/Presentation/SmartStore.Web/Themes/Mobile/theme.config +++ b/src/Presentation/SmartStore.Web/Themes/Mobile/theme.config @@ -2,7 +2,7 @@ \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Themes/MobileLight/Content/user.css b/src/Presentation/SmartStore.Web/Themes/MobileLight/Content/user.css new file mode 100644 index 0000000000..cffe7dcfd7 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Themes/MobileLight/Content/user.css @@ -0,0 +1,10 @@ +/* +// Customize the (child) theme here with css overrides +// or new selectors (for users) +// -------------------------------------------------- +*/ + +.legal-hints { + background: none !important; + border-bottom: 1px solid #aaa !important; +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Themes/MobileLight/Views/Web.config b/src/Presentation/SmartStore.Web/Themes/MobileLight/Views/Web.config index 3f3148484e..bd9d09444f 100644 --- a/src/Presentation/SmartStore.Web/Themes/MobileLight/Views/Web.config +++ b/src/Presentation/SmartStore.Web/Themes/MobileLight/Views/Web.config @@ -10,7 +10,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/Themes/MobileLight/preview.png b/src/Presentation/SmartStore.Web/Themes/MobileLight/preview.png index 0fce6c4c0b..0139e74853 100644 Binary files a/src/Presentation/SmartStore.Web/Themes/MobileLight/preview.png and b/src/Presentation/SmartStore.Web/Themes/MobileLight/preview.png differ diff --git a/src/Presentation/SmartStore.Web/Themes/MobileLight/theme.config b/src/Presentation/SmartStore.Web/Themes/MobileLight/theme.config index 167dc5d493..4712434fa8 100644 --- a/src/Presentation/SmartStore.Web/Themes/MobileLight/theme.config +++ b/src/Presentation/SmartStore.Web/Themes/MobileLight/theme.config @@ -1,3 +1,3 @@  - + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Themes/Web.config b/src/Presentation/SmartStore.Web/Themes/Web.config index 293d3e8831..c182d25b01 100644 --- a/src/Presentation/SmartStore.Web/Themes/Web.config +++ b/src/Presentation/SmartStore.Web/Themes/Web.config @@ -1,8 +1,11 @@  - - + + + + + diff --git a/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs b/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs index 7e9b037037..9e42f7ed02 100644 --- a/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs +++ b/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs @@ -8,6 +8,11 @@ public class ContactUsValidator : AbstractValidator { public ContactUsValidator(ILocalizationService localizationService) { + RuleFor(x => x.PrivacyAgreement) + .Must(x => x == true) + .WithMessage(localizationService.GetResource("ContactUs.PrivacyAgreement.MustBeAccepted")) + .When(x => x.DisplayPrivacyAgreement == true); + RuleFor(x => x.Email).NotEmpty().WithMessage(localizationService.GetResource("ContactUs.Email.Required")); RuleFor(x => x.Email).EmailAddress().WithMessage(localizationService.GetResource("Common.WrongEmail")); RuleFor(x => x.FullName).NotEmpty().WithMessage(localizationService.GetResource("ContactUs.FullName.Required")); diff --git a/src/Presentation/SmartStore.Web/Validators/Customer/RegisterValidator.cs b/src/Presentation/SmartStore.Web/Validators/Customer/RegisterValidator.cs index b04dd7468b..3c9516c656 100644 --- a/src/Presentation/SmartStore.Web/Validators/Customer/RegisterValidator.cs +++ b/src/Presentation/SmartStore.Web/Validators/Customer/RegisterValidator.cs @@ -1,5 +1,6 @@ using FluentValidation; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Tax; using SmartStore.Services.Localization; using SmartStore.Web.Models.Customer; @@ -7,7 +8,7 @@ namespace SmartStore.Web.Validators.Customer { public class RegisterValidator : AbstractValidator { - public RegisterValidator(ILocalizationService localizationService, CustomerSettings customerSettings) + public RegisterValidator(ILocalizationService localizationService, CustomerSettings customerSettings, TaxSettings taxSettings) { RuleFor(x => x.Email).NotEmpty().WithMessage(localizationService.GetResource("Account.Fields.Email.Required")); RuleFor(x => x.Email).EmailAddress().WithMessage(localizationService.GetResource("Common.WrongEmail")); @@ -50,6 +51,11 @@ public RegisterValidator(ILocalizationService localizationService, CustomerSetti { RuleFor(x => x.Fax).NotEmpty().WithMessage(localizationService.GetResource("Account.Fields.Fax.Required")); } + + if (taxSettings.EuVatEnabled && taxSettings.VatRequired) + { + RuleFor(x => x.VatNumber).NotEmpty().WithMessage(localizationService.GetResource("Account.Fields.Vat.Required")); + } } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Validators/Install/InstallValidator.cs b/src/Presentation/SmartStore.Web/Validators/Install/InstallValidator.cs index 0d1d042e9f..c7fc19f99b 100644 --- a/src/Presentation/SmartStore.Web/Validators/Install/InstallValidator.cs +++ b/src/Presentation/SmartStore.Web/Validators/Install/InstallValidator.cs @@ -14,7 +14,6 @@ public InstallValidator(IInstallationLocalizationService locService) RuleFor(x => x.ConfirmPassword).NotEmpty().WithMessage(locService.GetResource("ConfirmPasswordRequired")); RuleFor(x => x.AdminPassword).Equal(x => x.ConfirmPassword).WithMessage(locService.GetResource("PasswordsDoNotMatch")); RuleFor(x => x.DataProvider).NotEmpty().WithMessage(locService.GetResource("DataProviderRequired")); - // codehint: sm-add RuleFor(x => x.PrimaryLanguage).NotEmpty().WithMessage(locService.GetResource("PrimaryLanguageRequired")); } } diff --git a/src/Presentation/SmartStore.Web/Views/Blog/BlogPost.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Blog/BlogPost.Mobile.cshtml index 15f3a51e38..3c49bafc7a 100644 --- a/src/Presentation/SmartStore.Web/Views/Blog/BlogPost.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Blog/BlogPost.Mobile.cshtml @@ -50,13 +50,6 @@ @Html.TextAreaFor(model => model.AddNewComment.CommentText) @Html.ValidationMessageFor(model => model.AddNewComment.CommentText)
      - string result = TempData["sm.blog.addcomment.result"] as string; - if (!String.IsNullOrEmpty(result)) - { -
      - @result -
      - }
      @Html.ValidationSummary(true)
      diff --git a/src/Presentation/SmartStore.Web/Views/Blog/BlogPost.cshtml b/src/Presentation/SmartStore.Web/Views/Blog/BlogPost.cshtml index 7e3cc38336..02b64785ba 100644 --- a/src/Presentation/SmartStore.Web/Views/Blog/BlogPost.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Blog/BlogPost.cshtml @@ -56,27 +56,13 @@ @using (Html.BeginForm()) {
      - @if (!ViewData.ModelState.IsValid) - { - @Html.ValidationSummary(true) - } - @{ - string result = TempData["sm.blog.addcomment.result"] as string; - } - @if (!String.IsNullOrEmpty(result)) - { -
      - - @result -
      - } + @Html.ValidationSummary()
      @Html.LabelFor(model => model.AddNewComment.CommentText)
      @Html.TextAreaFor(model => model.AddNewComment.CommentText, new { @class = "comment-text" })
      - @Html.ValidationMessageFor(model => model.AddNewComment.CommentText)
      @if (Model.AddNewComment.DisplayCaptcha) { diff --git a/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.Mobile.cshtml index c9ad06e2b3..ca23d8a705 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/TopicMove.Mobile.cshtml @@ -16,7 +16,7 @@
      @T("Forum.SelectTheForumToMoveTopic"): - @Html.DropDownList("ForumSelected", new SelectList(Model.ForumList, "Value", "Text")) + @Html.DropDownList("ForumSelected", new SelectList(Model.ForumList, "Value", "Text"), new { data_native_menu = "false" })
      @T("Forum.Cancel") diff --git a/src/Presentation/SmartStore.Web/Views/Boards/_CreateUpdateTopic.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Boards/_CreateUpdateTopic.Mobile.cshtml index d0e2084883..1b13d8dda7 100644 --- a/src/Presentation/SmartStore.Web/Views/Boards/_CreateUpdateTopic.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Boards/_CreateUpdateTopic.Mobile.cshtml @@ -41,7 +41,7 @@ @if (Model.IsCustomerAllowedToSetTopicPriority) {
      - @Html.DropDownList("TopicTypeId", new SelectList(@Model.TopicPriorities, "Value", "Text", @Model.TopicTypeId)) + @Html.DropDownList("TopicTypeId", new SelectList(@Model.TopicPriorities, "Value", "Text", @Model.TopicTypeId), new { data_native_menu = "false" })
      } @if (Model.IsCustomerAllowedToSubscribe) diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/CategoryTemplate.ProductsInGridOrLines.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/CategoryTemplate.ProductsInGridOrLines.Mobile.cshtml index b197142d0a..01adaed1c6 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/CategoryTemplate.ProductsInGridOrLines.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/CategoryTemplate.ProductsInGridOrLines.Mobile.cshtml @@ -13,7 +13,7 @@ @Html.Action("SearchBox", "Catalog")
      -

      @Model.Name

      +

      @(Model.FullName.HasValue() ? Model.FullName : Model.Name)

      @Html.Widget("mobile_categorydetails_top") @@ -76,6 +76,13 @@ @(Html.SmartStore().Pager(Model.PagingFilteringContext).WithRenderer().Name("pagination-bottom").QueryParam("pagenumber")) + @if (Model.BottomDescription.HasValue()) + { +
      + @Html.Raw(Html.CollapsedText(Model.BottomDescription)) +
      + } + @Html.Widget("mobile_categorydetails_bottom")
      diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/CategoryTemplate.ProductsInGridOrLines.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/CategoryTemplate.ProductsInGridOrLines.cshtml index f49561fa37..cbe8111e78 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/CategoryTemplate.ProductsInGridOrLines.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/CategoryTemplate.ProductsInGridOrLines.cshtml @@ -15,6 +15,7 @@ } @using SmartStore.Core; +@using SmartStore.Core.Domain.Catalog; @using SmartStore.Core.Domain.Common; @using SmartStore.Core.Domain.Seo; @using SmartStore.Core.Infrastructure; @@ -22,8 +23,10 @@ @using SmartStore.Web.Models.Catalog; @using SmartStore.Web.Framework.UI; -@functions{ - private bool ShowListOptions() { +@functions +{ + private bool ShowListOptions() + { return Model.Products.Count > 0 && (Model.PagingFilteringContext.AllowProductViewModeChanging || Model.PagingFilteringContext.AllowProductSorting || @@ -31,6 +34,29 @@ } } +@helper SubCategoryList() +{ +
      + @(Html.DataList(Model.SubCategories, 3, + @
      +
      +

      + @item.Name +

      +
      + @if (item.PictureModel.ImageUrl.HasValue()) + { + + @item.PictureModel.AlternateText + + } +
      +
      +
      + )) +
      +} +
      @if (Model.DisplayCategoryBreadcrumb) {
      -@section right_top { +@section right_top +{ @Html.Widget("categorydetails_before_filters") @*filtering*@ diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.Mobile.cshtml index 77cb8c034f..69605ebe6e 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.Mobile.cshtml @@ -1,13 +1,12 @@ -@model CompareProductsModel +@using SmartStore.Core; +@using SmartStore.Core.Infrastructure; +@using SmartStore.Web.Models.Catalog; +@model CompareProductsModel @{ Layout = "~/Views/Shared/_Root.cshtml"; - //title Html.AddTitleParts(T("PageTitle.CompareProducts").Text); } -@using SmartStore.Core; -@using SmartStore.Core.Infrastructure; -@using SmartStore.Web.Models.Catalog; @{ string columnWidth = ""; if (Model.Products.Count > 0) @@ -15,7 +14,6 @@ columnWidth = Math.Round((decimal)(90M / Model.Products.Count), 0).ToString() + "%"; } - var specificationAttributes = new List(); foreach (var product in Model.Products) { @@ -51,7 +49,15 @@ @product.Name
      - @T("Products.Compare.Price"): @product.ProductPrice.Price +
      + @T("Products.Compare.Price"): @product.ProductPrice.Price +
      + @if (product.BasePriceInfo.HasValue()) + { +
      + @product.BasePriceInfo +
      + }
      @foreach (var specificationAttribute in specificationAttributes) diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.cshtml index c1c9f6bdf8..8202ddd22c 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.cshtml @@ -1,13 +1,12 @@ -@model CompareProductsModel +@using SmartStore.Core; +@using SmartStore.Core.Infrastructure; +@using SmartStore.Web.Models.Catalog; +@model CompareProductsModel @{ Layout = "~/Views/Shared/_ColumnsTwo.cshtml"; - //title Html.AddTitleParts(T("PageTitle.CompareProducts").Text); } -@using SmartStore.Core; -@using SmartStore.Core.Infrastructure; -@using SmartStore.Web.Models.Catalog; @{ string columnWidth = ""; if (Model.Products.Count > 0) @@ -15,7 +14,6 @@ columnWidth = Math.Round((decimal)(90M / Model.Products.Count), 0).ToString() + "%"; } - var specificationAttributes = new List(); foreach (var product in Model.Products) { @@ -52,9 +50,14 @@ {
      -

      - @product.DefaultPictureModel.AlternateText -

      + @if (product.DefaultPictureModel.ImageUrl.HasValue()) + { +

      + + @product.DefaultPictureModel.AlternateText + +

      + }

      @T("Common.Remove") @@ -84,7 +87,15 @@ @foreach (var product in Model.Products) { - @product.ProductPrice.Price +

      + @if (product.BasePriceInfo.HasValue()) + { +
      + @product.BasePriceInfo +
      + } } diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/CompareProductsButton.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/CompareProductsButton.cshtml index 0254f49751..0141d7c45b 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/CompareProductsButton.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/CompareProductsButton.cshtml @@ -7,7 +7,7 @@ data-href="@Url.Action("AddProductToCompare", new { id = Model.ProductId })" data-type="compare" data-action="add"> - + @T("Products.Compare.AddToCompareList")
      \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/FlyoutCompare.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/FlyoutCompare.cshtml index 42d9889f80..f10c3ec1e2 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/FlyoutCompare.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/FlyoutCompare.cshtml @@ -19,9 +19,12 @@
    1. - - @item.DefaultPictureModel.AlternateText - + @if (item.DefaultPictureModel.ImageUrl.HasValue()) + { + + @item.DefaultPictureModel.AlternateText + + }
      @item.Name diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/HomepageCategories.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/HomepageCategories.cshtml index 66d489bb87..92a35f55c8 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/HomepageCategories.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/HomepageCategories.cshtml @@ -8,14 +8,16 @@ @ )) diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml index 9962eceb67..9e207b9861 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerAll.cshtml @@ -1,13 +1,12 @@ -@model IList +@using SmartStore.Core; +@using SmartStore.Core.Infrastructure; +@using SmartStore.Web.Models.Catalog; +@model IList @{ Layout = "~/Views/Shared/_ColumnsThree.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Manufacturers").Text); } -@using SmartStore.Core; -@using SmartStore.Core.Infrastructure; -@using SmartStore.Web.Models.Catalog;

      @T("Manufacturers.List")

      @@ -21,12 +20,14 @@ @(Html.DataList(Model, 3, @

      - - @item.Name + @item.Name

      - - @item.PictureModel.AlternateText + @if (item.PictureModel.ImageUrl.HasValue()) + { + + @item.PictureModel.AlternateText + }
      )) diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerNavigation.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerNavigation.cshtml index f408a7bf41..e94c1df66b 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerNavigation.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/ManufacturerNavigation.cshtml @@ -1,8 +1,8 @@ -@model ManufacturerNavigationModel -@using SmartStore.Core.Domain.Catalog; +@using SmartStore.Core.Domain.Catalog; @using SmartStore.Core.Infrastructure; @using SmartStore.Services.Catalog; @using SmartStore.Web.Models.Catalog; +@model ManufacturerNavigationModel @if (Model.Manufacturers.Count > 0) {
      @@ -13,10 +13,28 @@ + + @if (Model.DisplayImages) + { +
       
      + } + @if (Model.TotalManufacturers > Model.Manufacturers.Count) {
      diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyAddedProducts.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyAddedProducts.Mobile.cshtml index 34bcc4772a..1f19e9cadf 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyAddedProducts.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyAddedProducts.Mobile.cshtml @@ -1,4 +1,4 @@ -@model IList +@model RecentlyAddedProductsModel @{ Layout = "~/Views/Shared/_Root.cshtml"; @@ -13,11 +13,11 @@

      @T("Products.NewProducts")

      - @if (Model.Count > 0) + @if (Model.Products.Count > 0) {
        - @foreach (var product in Model) + @foreach (var product in Model.Products) {
      • @Html.Partial("_ProductBox", product) diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyAddedProducts.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyAddedProducts.cshtml index db5b531560..f4aa453cce 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyAddedProducts.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyAddedProducts.cshtml @@ -1,5 +1,4 @@ -@*codehint: sm-edit*@ -@model RecentlyAddedProductsModel +@model RecentlyAddedProductsModel @{ Layout = "~/Views/Shared/_ColumnsThree.cshtml"; @@ -13,27 +12,18 @@ @using SmartStore.Core.Domain.Catalog; @using SmartStore.Web.Framework.UI; -@*@functions{ - private bool ShowListOptions() { - return Model.Products.Count > 0 && - (Model.PagingFilteringContext.AllowProductViewModeChanging || - Model.PagingFilteringContext.AllowProductSorting || - Model.PagingFilteringContext.AllowCustomersToSelectPageSize); - } -}*@ -
        -
        -
        + +
        +
        - - @*@if (ShowListOptions()) { - @Html.Partial("_ProductListOptions", Model.PagingFilteringContext) - }*@ @if (Model.Products.Count > 0) { @@ -65,7 +55,5 @@
        - @*@Html.SmartStore().Pager(Model.PagingFilteringContext).Name("pagination-bottom").QueryParam("pagenumber")*@ -
        diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyViewedProductsBlock.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyViewedProductsBlock.cshtml index 19d35797c0..d374c9044b 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyViewedProductsBlock.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/RecentlyViewedProductsBlock.cshtml @@ -1,5 +1,4 @@ -@*codehint: sm-edit nearly completly*@ -@model IList +@model IList @using SmartStore.Core; @using SmartStore.Core.Infrastructure; @using SmartStore.Web.Models.Catalog; @@ -27,8 +26,11 @@
      • - - @product.DefaultPictureModel.AlternateText + @if (product.DefaultPictureModel.ImageUrl.HasValue()) + { + + @product.DefaultPictureModel.AlternateText + }
        @product.Name diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/Search.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/Search.cshtml index 8d85b0e00e..8809946c25 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/Search.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/Search.cshtml @@ -1,15 +1,11 @@ -@model SearchModel +@using SmartStore.Web.Models.Catalog; +@using SmartStore.Web.Framework.UI; +@model SearchModel @{ Layout = "~/Views/Shared/_ColumnsThree.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Search").Text); } -@using SmartStore.Core; -@using SmartStore.Core.Infrastructure; -@using SmartStore.Web; -@using SmartStore.Web.Models.Catalog; -@using SmartStore.Web.Framework.UI; @functions{ private bool ShowListOptions() { @@ -32,11 +28,12 @@ - @*codehint: sm-edit begin *@ - @if (!String.IsNullOrEmpty(Model.Warning)) {
        @@ -74,7 +69,7 @@
      -